From 6fc0952fcafd54a656fdc8257ebb44bad58af163 Mon Sep 17 00:00:00 2001 From: dvlierop2 Date: Mon, 21 Jul 2008 20:38:09 +0000 Subject: [PATCH] Implement snapping of clipping paths and masks --- src/object-snapper.cpp | 147 ++++++++++++++++++++++++++---------- src/object-snapper.h | 126 ++++++++++++++++++------------- src/selection-chemistry.cpp | 8 +- src/sp-image.cpp | 1 + src/sp-item.cpp | 29 ++++++- src/splivarot.cpp | 22 +++--- src/splivarot.h | 2 +- 7 files changed, 228 insertions(+), 107 deletions(-) diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 2c4151e8c..2e9f1b8de 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -11,6 +11,7 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include "svg/svg.h" #include "libnr/n-art-bpath.h" #include "libnr/nr-path.h" #include "libnr/nr-rect-ops.h" @@ -32,13 +33,24 @@ #include "sp-text.h" #include "sp-flowtext.h" #include "text-editing.h" +#include "sp-clippath.h" +#include "sp-mask.h" + +Inkscape::SnapCandidate::SnapCandidate(SPItem* item, bool clip_or_mask, NR::Matrix additional_affine) + : item(item), clip_or_mask(clip_or_mask), additional_affine(additional_affine) +{ +} + +Inkscape::SnapCandidate::~SnapCandidate() +{ +} Inkscape::ObjectSnapper::ObjectSnapper(SPNamedView const *nv, NR::Coord const d) : Snapper(nv, d), _snap_to_itemnode(true), _snap_to_itempath(true), _snap_to_bboxnode(true), _snap_to_bboxpath(true), _snap_to_page_border(false), _strict_snapping(true), _include_item_center(false) { - _candidates = new std::vector; + _candidates = new std::vector; _points_to_snap_to = new std::vector; _bpaths_to_snap_to = new std::vector; _paths_to_snap_to = new std::vector; @@ -46,7 +58,7 @@ Inkscape::ObjectSnapper::ObjectSnapper(SPNamedView const *nv, NR::Coord const d) Inkscape::ObjectSnapper::~ObjectSnapper() { - _candidates->clear(); //Don't delete the candidates themselves, as these are not ours! + _candidates->clear(); delete _candidates; _points_to_snap_to->clear(); @@ -59,18 +71,20 @@ Inkscape::ObjectSnapper::~ObjectSnapper() /** * Find all items within snapping range. - * \param r Pointer to the current document + * \param parent Pointer to the document's root, or to a clipped path or mask object * \param it List of items to ignore * \param first_point If true then this point is the first one from a whole bunch of points * \param bbox_to_snap Bounding box hulling the whole bunch of points, all from the same selection and having the same transformation * \param DimensionToSnap Snap in X, Y, or both directions. */ -void Inkscape::ObjectSnapper::_findCandidates(SPObject* r, +void Inkscape::ObjectSnapper::_findCandidates(SPObject* parent, std::vector const *it, bool const &first_point, NR::Rect const &bbox_to_snap, - DimensionToSnap const snap_dim) const + DimensionToSnap const snap_dim, + bool const clip_or_mask, + NR::Matrix const additional_affine) const // transformation of the item being clipped / masked { bool const c1 = (snap_dim == TRANSL_SNAP_XY) && ThisSnapperMightSnap(); bool const c2 = (snap_dim != TRANSL_SNAP_XY) && GuidesMightSnap(); @@ -89,9 +103,10 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* r, NR::Rect bbox_to_snap_incl = bbox_to_snap; // _incl means: will include the snapper tolerance bbox_to_snap_incl.growBy(getSnapperTolerance()); // see? - for (SPObject* o = sp_object_first_child(r); o != NULL; o = SP_OBJECT_NEXT(o)) { - if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !desktop->itemIsHidden(SP_ITEM(o))) { - + for (SPObject* o = sp_object_first_child(parent); o != NULL; o = SP_OBJECT_NEXT(o)) { + if (SP_IS_ITEM(o) && !SP_ITEM(o)->isLocked() && !(desktop->itemIsHidden(SP_ITEM(o)) && !clip_or_mask)) { + // Don't snap to locked items, and + // don't snap to hidden objects, unless they're a clipped path or a mask /* See if this item is on the ignore list */ std::vector::const_iterator i; if (it != NULL) { @@ -100,17 +115,51 @@ void Inkscape::ObjectSnapper::_findCandidates(SPObject* r, i++; } } - + if (it == NULL || i == it->end()) { - /* See if the item is within range */ + SPItem *item = SP_ITEM(o); + NR::Matrix transform = NR::identity(); + if (item) { + SPObject *obj = NULL; + if (clip_or_mask) { // If the current item is a clipping path or a mask + // then store the transformation of the clipped path or mask itself + // but also take into account the additional affine of the object + // being clipped / masked + transform = item->transform * additional_affine; + } else { // cannot clip or mask more than once + // The current item is not a clipping path or a mask, but might + // still be the subject of clipping or masking itself ; if so, then + // we should also consider that path or mask for snapping to + obj = SP_OBJECT(item->clip_ref->getObject()); + if (obj) { + _findCandidates(obj, it, false, bbox_to_snap, snap_dim, true, item->transform); + } + obj = SP_OBJECT(item->mask_ref->getObject()); + if (obj) { + _findCandidates(obj, it, false, bbox_to_snap, snap_dim, true, item->transform); + } + } + } + if (SP_IS_GROUP(o)) { - _findCandidates(o, it, false, bbox_to_snap, snap_dim); + _findCandidates(o, it, false, bbox_to_snap, snap_dim, false, NR::identity()); } else { - bbox_of_item = sp_item_bbox_desktop(SP_ITEM(o)); + if (clip_or_mask) { + // Oh oh, this will get ugly. We cannot use sp_item_i2d_affine directly because we need to + // insert an additional transformation in document coordinates (code copied from sp_item_i2d_affine) + sp_item_invoke_bbox(item, + &bbox_of_item, + from_2geom(sp_item_i2doc_affine(item) * matrix_to_desktop(to_2geom(additional_affine), item)), + true); + + } else { + sp_item_invoke_bbox(item, &bbox_of_item, from_2geom(sp_item_i2d_affine(item)), true); + } + // See if the item is within range if (bbox_of_item) { if (bbox_to_snap_incl.intersects(*bbox_of_item)) { - //This item is within snapping range, so record it as a candidate - _candidates->push_back(SP_ITEM(o)); + // This item is within snapping range, so record it as a candidate + _candidates->push_back(SnapCandidate(item, clip_or_mask, additional_affine)); } } } @@ -141,15 +190,15 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::Snapper::PointType const & if (_snap_to_bboxnode) { int prefs_bbox = prefs_get_int_attribute("tools", "bounding_box", 0); - bbox_type = (prefs_bbox ==0)? + bbox_type = (prefs_bbox == 0)? SPItem::APPROXIMATE_BBOX : SPItem::GEOMETRIC_BBOX; } - for (std::vector::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { + for (std::vector::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { //NR::Matrix i2doc(NR::identity()); - SPItem *root_item = *i; - if (SP_IS_USE(*i)) { - root_item = sp_use_root(SP_USE(*i)); + SPItem *root_item = (*i).item; + if (SP_IS_USE((*i).item)) { + root_item = sp_use_root(SP_USE((*i).item)); } g_return_if_fail(root_item); @@ -163,10 +212,14 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::Snapper::PointType const & //Collect the bounding box's corners so we can snap to them if (_snap_to_bboxnode) { if (!(_strict_snapping && !p_is_a_bbox) || p_is_a_guide) { - NR::Maybe b = sp_item_bbox_desktop(root_item, bbox_type); - if (b) { - for ( unsigned k = 0 ; k < 4 ; k++ ) { - _points_to_snap_to->push_back(b->corner(k)); + // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox + // of the item AND the bbox of the clipping path at the same time + if (!(*i).clip_or_mask) { + NR::Maybe b = sp_item_bbox_desktop(root_item, bbox_type); + if (b) { + for ( unsigned k = 0 ; k < 4 ; k++ ) { + _points_to_snap_to->push_back(b->corner(k)); + } } } } @@ -280,21 +333,21 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::Snapper::PointType const & _bpaths_to_snap_to->push_back(new_bpath); } - for (std::vector::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { + for (std::vector::const_iterator i = _candidates->begin(); i != _candidates->end(); i++) { /* Transform the requested snap point to this item's coordinates */ NR::Matrix i2doc(NR::identity()); SPItem *root_item = NULL; /* We might have a clone at hand, so make sure we get the root item */ - if (SP_IS_USE(*i)) { - i2doc = sp_use_get_root_transform(SP_USE(*i)); - root_item = sp_use_root(SP_USE(*i)); + if (SP_IS_USE((*i).item)) { + i2doc = sp_use_get_root_transform(SP_USE((*i).item)); + root_item = sp_use_root(SP_USE((*i).item)); g_return_if_fail(root_item); } else { - i2doc = from_2geom(sp_item_i2doc_affine(*i)); - root_item = *i; + i2doc = from_2geom(sp_item_i2doc_affine((*i).item)); + root_item = (*i).item; } - + //Build a list of all paths considered for snapping to //Add the item's path to snap to @@ -322,8 +375,20 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::Snapper::PointType const & if (!very_lenghty_prose && !very_complex_path) { SPCurve *curve = curve_for_item(root_item); if (curve) { - NArtBpath *bpath = bpath_for_curve(root_item, curve, true, true); // perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. apply transformation on the snap points, instead of generating whole new path + NArtBpath *bpath = bpath_for_curve(root_item, curve, true, true, + NR::identity(), + (*i).additional_affine); + // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. _bpaths_to_snap_to->push_back(bpath); // we will get a dupe of the path, which must be freed at some point + + /*std::vector pathv; + char *svgpath = sp_svg_write_path(bpath); + if (svgpath) { + std::cout << "path = " << svgpath << std::endl; + } + g_free(svgpath); + */ + curve->unref(); } } @@ -333,10 +398,14 @@ void Inkscape::ObjectSnapper::_collectPaths(Inkscape::Snapper::PointType const & //Add the item's bounding box to snap to if (_snap_to_bboxpath) { if (!(_strict_snapping && p_is_a_node)) { - NRRect rect; - sp_item_invoke_bbox(root_item, &rect, i2doc, TRUE, bbox_type); - NArtBpath *bpath = nr_path_from_rect(rect); - _bpaths_to_snap_to->push_back(bpath); + // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox + // of the item AND the bbox of the clipping path at the same time + if (!(*i).clip_or_mask) { + NRRect rect; + sp_item_invoke_bbox(root_item, &rect, i2doc, TRUE, bbox_type); + NArtBpath *bpath = nr_path_from_rect(rect); + _bpaths_to_snap_to->push_back(bpath); + } } } } @@ -375,7 +444,7 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, if (node_tool_active) { SPCurve *curve = curve_for_item(SP_ITEM(selected_path)); if (curve) { - NArtBpath *bpath = bpath_for_curve(SP_ITEM(selected_path), curve, true, true); + NArtBpath *bpath = bpath_for_curve(SP_ITEM(selected_path), curve, true, true, NR::identity(), NR::identity()); _bpaths_to_snap_to->push_back(bpath); // we will get a dupe of the path, which must be freed at some point curve->unref(); } @@ -575,7 +644,7 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, /* Get a list of all the SPItems that we will try to snap to */ if (first_point) { NR::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : NR::Rect(p, p); - _findCandidates(sp_document_root(_named_view->document), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY); + _findCandidates(sp_document_root(_named_view->document), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, NR::identity()); } if (_snap_to_itemnode || _snap_to_bboxnode) { @@ -621,7 +690,7 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, /* Get a list of all the SPItems that we will try to snap to */ if (first_point) { NR::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : NR::Rect(p, p); - _findCandidates(sp_document_root(_named_view->document), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY); + _findCandidates(sp_document_root(_named_view->document), it, first_point, local_bbox_to_snap, TRANSL_SNAP_XY, false, NR::identity()); } // A constrained snap, is a snap in only one degree of freedom (specified by the constraint line). @@ -671,7 +740,7 @@ void Inkscape::ObjectSnapper::guideSnap(SnappedConstraints &sc, // second time on an object; but should this point then be constrained to the // line, or can it be located anywhere?) - _findCandidates(sp_document_root(_named_view->document), &it, true, NR::Rect(p, p), snap_dim); + _findCandidates(sp_document_root(_named_view->document), &it, true, NR::Rect(p, p), snap_dim, false, NR::identity()); _snapTranslatingGuideToNodes(sc, Inkscape::Snapper::SNAPPOINT_GUIDE, p, guide_normal); // _snapRotatingGuideToNodes has not been implemented yet. diff --git a/src/object-snapper.h b/src/object-snapper.h index 95470ed45..5d09930c0 100644 --- a/src/object-snapper.h +++ b/src/object-snapper.h @@ -26,42 +26,58 @@ struct SPObject; namespace Inkscape { +class SnapCandidate + +{ +public: + SnapCandidate(SPItem* item, bool clip_or_mask, NR::Matrix _additional_affine); + ~SnapCandidate(); + + SPItem* item; // An item that is to be considered for snapping to + bool clip_or_mask; // If true, then item refers to a clipping path or a mask + + /* To find out the absolute position of a clipping path or mask, we not only need to know + * the transformation of the clipping path or mask itself, but also the transformation of + * the object to which the clip or mask is being applied; that transformation is stored here + */ + NR::Matrix additional_affine; +}; + class ObjectSnapper : public Snapper { public: - ObjectSnapper(SPNamedView const *nv, NR::Coord const d); - ~ObjectSnapper(); + ObjectSnapper(SPNamedView const *nv, NR::Coord const d); + ~ObjectSnapper(); - enum DimensionToSnap { - GUIDE_TRANSL_SNAP_X, // For snapping a vertical guide (normal in the X-direction) to objects, - GUIDE_TRANSL_SNAP_Y, // For snapping a horizontal guide (normal in the Y-direction) to objects - ANGLED_GUIDE_TRANSL_SNAP, // For snapping an angled guide, while translating it accross the desktop - ANGLED_GUIDE_ROT_SNAP, // For snapping an angled guide, while rotating it around some pivot point - TRANSL_SNAP_XY}; // All other cases; for snapping to objects, other than guides + enum DimensionToSnap { + GUIDE_TRANSL_SNAP_X, // For snapping a vertical guide (normal in the X-direction) to objects, + GUIDE_TRANSL_SNAP_Y, // For snapping a horizontal guide (normal in the Y-direction) to objects + ANGLED_GUIDE_TRANSL_SNAP, // For snapping an angled guide, while translating it accross the desktop + ANGLED_GUIDE_ROT_SNAP, // For snapping an angled guide, while rotating it around some pivot point + TRANSL_SNAP_XY}; // All other cases; for snapping to objects, other than guides - void setSnapToItemNode(bool s) {_snap_to_itemnode = s;} - bool getSnapToItemNode() const {return _snap_to_itemnode;} - void setSnapToItemPath(bool s) {_snap_to_itempath = s;} - bool getSnapToItemPath() const {return _snap_to_itempath;} - void setSnapToBBoxNode(bool s) {_snap_to_bboxnode = s;} - bool getSnapToBBoxNode() const {return _snap_to_bboxnode;} - void setSnapToBBoxPath(bool s) {_snap_to_bboxpath = s;} - bool getSnapToBBoxPath() const {return _snap_to_bboxpath;} - void setSnapToPageBorder(bool s) {_snap_to_page_border = s;} - bool getSnapToPageBorder() const {return _snap_to_page_border;} - void setIncludeItemCenter(bool s) {_include_item_center = s;} - bool getIncludeItemCenter() const {return _include_item_center;} - void setStrictSnapping(bool enabled) {_strict_snapping = enabled;} - - void guideSnap(SnappedConstraints &sc, + void setSnapToItemNode(bool s) {_snap_to_itemnode = s;} + bool getSnapToItemNode() const {return _snap_to_itemnode;} + void setSnapToItemPath(bool s) {_snap_to_itempath = s;} + bool getSnapToItemPath() const {return _snap_to_itempath;} + void setSnapToBBoxNode(bool s) {_snap_to_bboxnode = s;} + bool getSnapToBBoxNode() const {return _snap_to_bboxnode;} + void setSnapToBBoxPath(bool s) {_snap_to_bboxpath = s;} + bool getSnapToBBoxPath() const {return _snap_to_bboxpath;} + void setSnapToPageBorder(bool s) {_snap_to_page_border = s;} + bool getSnapToPageBorder() const {return _snap_to_page_border;} + void setIncludeItemCenter(bool s) {_include_item_center = s;} + bool getIncludeItemCenter() const {return _include_item_center;} + void setStrictSnapping(bool enabled) {_strict_snapping = enabled;} + void guideSnap(SnappedConstraints &sc, NR::Point const &p, NR::Point const &guide_normal) const; - bool ThisSnapperMightSnap() const; - bool GuidesMightSnap() const; + bool ThisSnapperMightSnap() const; + bool GuidesMightSnap() const; - void freeSnap(SnappedConstraints &sc, + void freeSnap(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, bool const &first_point, @@ -69,7 +85,7 @@ public: std::vector const *it, std::vector *unselected_nodes) const; - void constrainedSnap(SnappedConstraints &sc, + void constrainedSnap(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, bool const &first_point, @@ -78,33 +94,35 @@ public: std::vector const *it) const; private: - //store some lists of candidates, points and paths, so we don't have to rebuild them for each point we want to snap - std::vector *_candidates; - std::vector *_points_to_snap_to; - std::vector *_bpaths_to_snap_to; - std::vector *_paths_to_snap_to; + //store some lists of candidates, points and paths, so we don't have to rebuild them for each point we want to snap + std::vector *_candidates; + std::vector *_points_to_snap_to; + std::vector *_bpaths_to_snap_to; + std::vector *_paths_to_snap_to; - void _findCandidates(SPObject* r, + void _findCandidates(SPObject* parent, std::vector const *it, bool const &first_point, NR::Rect const &bbox_to_snap, - DimensionToSnap snap_dim) const; + DimensionToSnap snap_dim, + bool const _clip_or_mask, + NR::Matrix const additional_affine) const; - void _snapNodes(SnappedConstraints &sc, + void _snapNodes(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, bool const &first_point, std::vector *unselected_nodes) const; - void _snapTranslatingGuideToNodes(SnappedConstraints &sc, + void _snapTranslatingGuideToNodes(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, NR::Point const &guide_normal) const; - void _collectNodes(Inkscape::Snapper::PointType const &t, + void _collectNodes(Inkscape::Snapper::PointType const &t, bool const &first_point) const; - void _snapPaths(SnappedConstraints &sc, + void _snapPaths(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, bool const &first_point, @@ -112,33 +130,33 @@ private: SPPath const *selected_path, NArtBpath const *border_bpath) const; - void _snapPathsConstrained(SnappedConstraints &sc, + void _snapPathsConstrained(SnappedConstraints &sc, Inkscape::Snapper::PointType const &t, NR::Point const &p, bool const &first_point, ConstraintLine const &c) const; - bool isUnselectedNode(NR::Point const &point, std::vector const *unselected_nodes) const; + bool isUnselectedNode(NR::Point const &point, std::vector const *unselected_nodes) const; - void _collectPaths(Inkscape::Snapper::PointType const &t, + void _collectPaths(Inkscape::Snapper::PointType const &t, bool const &first_point, NArtBpath const *border_bpath = NULL) const; - void _clear_paths() const; - NArtBpath const* _getBorderBPath() const; + void _clear_paths() const; + NArtBpath const* _getBorderBPath() const; - bool _snap_to_itemnode; - bool _snap_to_itempath; - bool _snap_to_bboxnode; - bool _snap_to_bboxpath; - bool _snap_to_page_border; + bool _snap_to_itemnode; + bool _snap_to_itempath; + bool _snap_to_bboxnode; + bool _snap_to_bboxpath; + bool _snap_to_page_border; - //If enabled, then bbox corners will only snap to bboxes, - //and nodes will only snap to nodes and paths. We will not - //snap bbox corners to nodes, or nodes to bboxes. - //(snapping to grids and guides is not affected by this) - bool _strict_snapping; - bool _include_item_center; + //If enabled, then bbox corners will only snap to bboxes, + //and nodes will only snap to nodes and paths. We will not + //snap bbox corners to nodes, or nodes to bboxes. + //(snapping to grids and guides is not affected by this) + bool _strict_snapping; + bool _include_item_center; }; } diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index eebec434d..a5f270389 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -2751,7 +2751,9 @@ void sp_selection_unset_mask(bool apply_clip_path) { sp_document_ensure_up_to_date(doc); gchar const *attributeName = apply_clip_path ? "clip-path" : "mask"; - std::map referenced_objects; + std::map referenced_objects; + // SPObject* refers to a group containing the clipped path or mask itself, + // whereas SPItem* refers to the item being clipped or masked for (GSList const *i = selection->itemList(); NULL != i; i = i->next) { if (remove_original) { // remember referenced mask/clippath, so orphaned masks can be moved back to document @@ -2775,9 +2777,10 @@ void sp_selection_unset_mask(bool apply_clip_path) { // restore mask objects into a document for ( std::map::iterator it = referenced_objects.begin() ; it != referenced_objects.end() ; ++it) { - SPObject *obj = (*it).first; + SPObject *obj = (*it).first; // Group containing the clipped paths or masks GSList *items_to_move = NULL; for (SPObject *child = sp_object_first_child(obj) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + // Collect all clipped paths and masks within a single group Inkscape::XML::Node *copy = SP_OBJECT_REPR(child)->duplicate(xml_doc); items_to_move = g_slist_prepend (items_to_move, copy); } @@ -2791,6 +2794,7 @@ void sp_selection_unset_mask(bool apply_clip_path) { Inkscape::XML::Node *parent = SP_OBJECT_REPR((*it).second)->parent(); gint pos = SP_OBJECT_REPR((*it).second)->position(); + // Iterate through all clipped paths / masks for (GSList *i = items_to_move; NULL != i; i = i->next) { Inkscape::XML::Node *repr = (Inkscape::XML::Node *)i->data; diff --git a/src/sp-image.cpp b/src/sp-image.cpp index 2dc3bfb27..f1e645d6f 100644 --- a/src/sp-image.cpp +++ b/src/sp-image.cpp @@ -1306,6 +1306,7 @@ static void sp_image_snappoints(SPItem const *item, SnapPointsIter p) if (item->clip_ref->getObject()) { //We are looking at a clipped image: do not return any snappoints, as these might be //far far away from the visible part from the clipped image + //TODO Do return snappoints, but only when within visual bounding box } else { // The image has not been clipped: return its corners, which might be rotated for example SPImage &image = *SP_IMAGE(item); diff --git a/src/sp-item.cpp b/src/sp-item.cpp index 81e75ad7c..c888bc195 100644 --- a/src/sp-item.cpp +++ b/src/sp-item.cpp @@ -933,16 +933,43 @@ static void sp_item_private_snappoints(SPItem const *item, SnapPointsIter p) void sp_item_snappoints(SPItem const *item, bool includeItemCenter, SnapPointsIter p) { - g_assert (item != NULL); + g_assert (item != NULL); g_assert (SP_IS_ITEM(item)); + // Get the snappoints of the item SPItemClass const &item_class = *(SPItemClass const *) G_OBJECT_GET_CLASS(item); if (item_class.snappoints) { item_class.snappoints(item, p); } + // Get the snappoints at the item's center if (includeItemCenter) { *p = item->getCenter(); + } + + // Get the snappoints of clipping paths and mask, if any + std::list clips_and_masks; + + clips_and_masks.push_back(SP_OBJECT(item->clip_ref->getObject())); + clips_and_masks.push_back(SP_OBJECT(item->mask_ref->getObject())); + + for (std::list::const_iterator o = clips_and_masks.begin(); o != clips_and_masks.end(); o++) { + if (*o) { + // obj is a group object, the children are the actual clippers + for (SPObject *child = (*o)->children ; child ; child = child->next) { + if (SP_IS_ITEM(child)) { + std::vector p_clip_or_mask; + // Please note the recursive call here! + sp_item_snappoints(SP_ITEM(child), includeItemCenter, SnapPointsIter(p_clip_or_mask)); + // Take into account the transformation of the item being clipped or masked + for (std::vector::const_iterator p_orig = p_clip_or_mask.begin(); p_orig != p_clip_or_mask.end(); p_orig++) { + // All snappoints are in desktop coordinates, but the item's transformation is + // in document coordinates. Hence the awkward construction below + *p = (*p_orig) * from_2geom(matrix_to_desktop (matrix_from_desktop (to_2geom(item->transform), item), item)); + } + } + } + } } } diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 42fb45109..47a365c68 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1755,7 +1755,9 @@ Path * Path_for_item(SPItem *item, bool doTransformation, bool transformFull) { SPCurve *curve = curve_for_item(item); - if (curve == NULL) { + NArtBpath *bpath = bpath_for_curve(item, curve, doTransformation, transformFull, NR::identity(), NR::identity()); + + if (bpath == NULL) { return NULL; } @@ -1773,7 +1775,7 @@ Path_for_item(SPItem *item, bool doTransformation, bool transformFull) * This function always returns a new NArtBpath, the caller must g_free the returned path! */ NArtBpath * -bpath_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull) +bpath_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull, NR::Matrix extraPreAffine, NR::Matrix extraPostAffine) { if (curve == NULL) return NULL; @@ -1786,12 +1788,12 @@ bpath_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transf NArtBpath *new_bpath; // we will get a duplicate which has to be freed at some point! if (doTransformation) { if (transformFull) { - new_bpath = nr_artpath_affine(bpath, from_2geom(sp_item_i2doc_affine(item))); + new_bpath = nr_artpath_affine(bpath, extraPreAffine * from_2geom(sp_item_i2doc_affine(item)) * extraPostAffine); } else { - new_bpath = nr_artpath_affine(bpath, item->transform); + new_bpath = nr_artpath_affine(bpath, extraPreAffine * item->transform * extraPostAffine); } } else { - new_bpath = nr_artpath_affine(bpath, NR::identity()); + new_bpath = nr_artpath_affine(bpath, extraPreAffine * NR::identity() * extraPostAffine); } g_free(bpath); @@ -1821,14 +1823,14 @@ pathvector_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool t SPCurve* curve_for_item(SPItem *item) { - if (!item) - return NULL; + if (!item) { + return NULL; + } SPCurve *curve = NULL; - if (SP_IS_SHAPE(item)) { if (SP_IS_PATH(item)) { - curve = sp_path_get_curve_for_edit(SP_PATH(item)); + curve = sp_path_get_curve_for_edit(SP_PATH(item)); } else { curve = sp_shape_get_curve(SP_SHAPE(item)); } @@ -1839,7 +1841,7 @@ SPCurve* curve_for_item(SPItem *item) } else if (SP_IS_IMAGE(item)) { - curve = sp_image_get_curve(SP_IMAGE(item)); + curve = sp_image_get_curve(SP_IMAGE(item)); } return curve; // do not forget to unref the curve at some point! diff --git a/src/splivarot.h b/src/splivarot.h index ce31dd8c4..eaf242599 100644 --- a/src/splivarot.h +++ b/src/splivarot.h @@ -48,7 +48,7 @@ void sp_selected_path_outline (); void sp_selected_path_simplify (); Path *Path_for_item(SPItem *item, bool doTransformation, bool transformFull = true); -NArtBpath *bpath_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull); +NArtBpath *bpath_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull, NR::Matrix extraPreAffine, NR::Matrix extraPostAffine); Geom::PathVector pathvector_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool transformFull); SPCurve *curve_for_item(SPItem *item); NR::Maybe get_nearest_position_on_Path(Path *path, NR::Point p, unsigned seg = 0); -- 2.30.2