Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
object-snapper.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Snapping things to objects.
4 *
5 * Authors:
6 * Carl Hetherington <inkscape@carlh.net>
7 * Diederik van Lierop <mail@diedenrezi.nl>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2005 - 2012 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16#include <2geom/circle.h>
17#include <2geom/line.h>
19#include <2geom/path-sink.h>
20#include <memory>
21
22#include "desktop.h"
23#include "display/curve.h"
24#include "document.h"
25#include "preferences.h"
26#include "snap-enums.h"
27#include "text-editing.h"
28#include "page-manager.h"
29
30#include "object/sp-flowtext.h"
31#include "object/sp-item.h"
32#include "object/sp-path.h"
33#include "object/sp-page.h"
34#include "object/sp-root.h"
35#include "object/sp-shape.h"
36#include "object/sp-use.h"
37#include "object/sp-text.h"
38#include "path/path-util.h" // curve_for_item
39
41 : Snapper(sm, d)
42{
43 _points_to_snap_to = std::make_unique<std::vector<SnapCandidatePoint>>();
44 _paths_to_snap_to = std::make_unique<std::vector<SnapCandidatePath>>();
45}
46
48{
49 _points_to_snap_to->clear();
50 _clear_paths();
51}
52
54{
55 SPDesktop const *dt = _snapmanager->getDesktop();
56 double const zoom = dt ? dt->current_zoom() : 1;
57 return _snapmanager->snapprefs.getObjectTolerance() / zoom;
58}
59
61{
62 return Preferences::get()->getBool("/options/snap/object/always", false);
63}
64
66 bool const &first_point) const
67{
68 // Now, let's first collect all points to snap to. If we have a whole bunch of points to snap,
69 // e.g. when translating an item using the selector tool, then we will only do this for the
70 // first point and store the collection for later use. This significantly improves the performance
71 if (first_point) {
72 _points_to_snap_to->clear();
73
74 // Determine the type of bounding box we should snap to
76
77 bool p_is_a_node = t & SNAPSOURCE_NODE_CATEGORY;
78 bool p_is_a_bbox = t & SNAPSOURCE_BBOX_CATEGORY;
79 bool p_is_other = (t & SNAPSOURCE_OTHERS_CATEGORY) || (t & SNAPSOURCE_DATUMS_CATEGORY);
80
81 // A point considered for snapping should be either a node, a bbox corner or a guide/other. Pick only ONE!
82 if (((p_is_a_node && p_is_a_bbox) || (p_is_a_bbox && p_is_other) || (p_is_a_node && p_is_other))) {
83 g_warning("Snap warning: node type is ambiguous");
84 }
85
86 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_CORNER, SNAPTARGET_BBOX_EDGE_MIDPOINT, SNAPTARGET_BBOX_MIDPOINT)) {
88 bool prefs_bbox = prefs->getBool("/tools/bounding_box");
89 bbox_type = !prefs_bbox ?
91 }
92
93 // Consider the page border for snapping to
94 if (auto document = _snapmanager->getDocument()) {
95 auto ignore_page = _snapmanager->getPageToIgnore();
96 for (auto page : document->getPageManager().getPages()) {
97 if (ignore_page == page)
98 continue;
99 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_EDGE_CORNER)) {
100 getBBoxPoints(page->getDesktopRect(), _points_to_snap_to.get(), true,
104 }
105 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_MARGIN_CORNER)) {
106 getBBoxPoints(page->getDesktopMargin(), _points_to_snap_to.get(), true,
110 getBBoxPoints(page->getDesktopBleed(), _points_to_snap_to.get(), true,
112 SNAPSOURCE_UNDEFINED, SNAPTARGET_UNDEFINED, // No edges or center
114 }
115 }
116 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_EDGE_CORNER)) {
117 // Only the corners get added here.
118 getBBoxPoints(document->preferredBounds(), _points_to_snap_to.get(), false,
122 }
123 }
124
125 for (const auto & _candidate : *_snapmanager->_obj_snapper_candidates) {
126 SPItem *root_item = _candidate.item;
127 g_return_if_fail(root_item);
128
129 //Collect all nodes so we can snap to them
130 if (p_is_a_node || p_is_other || (p_is_a_bbox && !_snapmanager->snapprefs.getStrictSnapping())) {
131 // Note: there are two ways in which intersections are considered:
132 // Method 1: Intersections are calculated for each shape individually, for both the
133 // snap source and snap target (see sp_shape_snappoints)
134 // Method 2: Intersections are calculated for each curve or line that we've snapped to, i.e. only for
135 // the target (see the intersect() method in the SnappedCurve and SnappedLine classes)
136 // Some differences:
137 // - Method 1 doesn't find intersections within a set of multiple objects
138 // - Method 2 only works for targets
139 // When considering intersections as snap targets:
140 // - Method 1 only works when snapping to nodes, whereas
141 // - Method 2 only works when snapping to paths
142 // - There will be performance differences too!
143 // If both methods are being used simultaneously, then this might lead to duplicate targets!
144
145 // Well, here we will be looking for snap TARGETS. Both methods can therefore be used.
146 // When snapping to paths, we will get a collection of snapped lines and snapped curves. findBestSnap() will
147 // go hunting for intersections (but only when asked to in the prefs of course). In that case we can just
148 // temporarily block the intersections in sp_item_snappoints, we don't need duplicates. If we're not snapping to
149 // paths though but only to item nodes then we should still look for the intersections in sp_item_snappoints()
150 bool old_pref = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH_INTERSECTION);
151 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH)) {
152 // So if we snap to paths, then findBestSnap will find the intersections
153 // and therefore we temporarily disable SNAPTARGET_PATH_INTERSECTION, which will
154 // avoid root_item->getSnappoints() below from returning intersections
155 _snapmanager->snapprefs.setTargetSnappable(SNAPTARGET_PATH_INTERSECTION, false);
156 }
157
158 // We should not snap a transformation center to any of the centers of the items in the
159 // current selection (see the comment in SelTrans::centerRequest())
160 bool old_pref2 = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_ROTATION_CENTER);
161 if (old_pref2) {
162 std::vector<SPItem*> rotationSource=_snapmanager->getRotationCenterSource();
163 for (auto itemlist : rotationSource) {
164 if (_candidate.item == itemlist) {
165 // don't snap to this item's rotation center
166 _snapmanager->snapprefs.setTargetSnappable(SNAPTARGET_ROTATION_CENTER, false);
167 break;
168 }
169 }
170 }
171
172 root_item->getSnappoints(*_points_to_snap_to, &_snapmanager->snapprefs);
173
174 // restore the original snap preferences
175 _snapmanager->snapprefs.setTargetSnappable(SNAPTARGET_PATH_INTERSECTION, old_pref);
176 _snapmanager->snapprefs.setTargetSnappable(SNAPTARGET_ROTATION_CENTER, old_pref2);
177 }
178
179 //Collect the bounding box's corners so we can snap to them
180 if (p_is_a_bbox || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node) || p_is_other) {
181 // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox
182 // of the item AND the bbox of the clipping path at the same time
183 if (!_candidate.clip_or_mask) {
184 Geom::OptRect b = root_item->desktopBounds(bbox_type);
185 getBBoxPoints(b, _points_to_snap_to.get(), true,
186 _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_CORNER),
187 _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE_MIDPOINT),
188 _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_MIDPOINT));
189 }
190 }
191 }
192 }
193}
194
196 SnapCandidatePoint const &p,
197 std::vector<SnapCandidatePoint> *unselected_nodes,
198 SnapConstraint const &c,
199 Geom::Point const &p_proj_on_constraint) const
200{
201 // Iterate through all nodes, find out which one is the closest to p, and snap to it!
202
203 _collectNodes(p.getSourceType(), p.getSourceNum() <= 0);
204
205 if (unselected_nodes != nullptr && unselected_nodes->size() > 0) {
206 g_assert(_points_to_snap_to != nullptr);
207 _points_to_snap_to->insert(_points_to_snap_to->end(), unselected_nodes->begin(), unselected_nodes->end());
208 }
209
210 SnappedPoint s;
211 bool success = false;
212 bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping();
213
214 for (const auto & k : *_points_to_snap_to) {
215 if (_allowSourceToSnapToTarget(p.getSourceType(), k.getTargetType(), strict_snapping)) {
216 Geom::Point target_pt = k.getPoint();
217 Geom::Coord dist = Geom::L2(target_pt - p.getPoint()); // Default: free (unconstrained) snapping
218 if (!c.isUndefined()) {
219 // We're snapping to nodes along a constraint only, so find out if this node
220 // is at the constraint, while allowing for a small margin
221 if (Geom::L2(target_pt - c.projection(target_pt)) > 1e-9) {
222 // The distance from the target point to its projection on the constraint
223 // is too large, so this point is not on the constraint. Skip it!
224 continue;
225 }
226 dist = Geom::L2(target_pt - p_proj_on_constraint);
227 }
228
229 if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) {
230 bool always = getSnapperAlwaysSnap(p.getSourceType());
231 s = SnappedPoint(target_pt, p.getSourceType(), p.getSourceNum(), k.getTargetType(), dist, getSnapperTolerance(), always, false, true, k.getTargetBBox());
232 success = true;
233 }
234 }
235 }
236
237 if (success) {
238 isr.points.push_back(s);
239 }
240}
241
243 Geom::Point const &p,
244 Geom::Point const &guide_normal) const
245{
246 // Iterate through all nodes, find out which one is the closest to this guide, and snap to it!
247 _collectNodes(SNAPSOURCE_GUIDE, true);
248
250 _collectPaths(p, SNAPSOURCE_GUIDE, true);
251 _snapPaths(isr, SnapCandidatePoint(p, SNAPSOURCE_GUIDE), nullptr, nullptr);
252 }
253
254 SnappedPoint s;
255
256 Geom::Coord tol = getSnapperTolerance();
257 bool always = getSnapperAlwaysSnap(SNAPSOURCE_GUIDE);
258
259 for (const auto & k : *_points_to_snap_to) {
260 Geom::Point target_pt = k.getPoint();
261 // Project each node (*k) on the guide line (running through point p)
262 Geom::Point p_proj = Geom::projection(target_pt, Geom::Line(p, p + Geom::rot90(guide_normal)));
263 Geom::Coord dist = Geom::L2(target_pt - p_proj); // distance from node to the guide
264 Geom::Coord dist2 = Geom::L2(p - p_proj); // distance from projection of node on the guide, to the mouse location
265 if ((dist < tol && dist2 < tol) || always) {
266 s = SnappedPoint(target_pt, SNAPSOURCE_GUIDE, 0, k.getTargetType(), dist, tol, always, false, true, k.getTargetBBox());
267 isr.points.push_back(s);
268 }
269 }
270}
271
274 SnapSourceType const source_type,
275 bool const &first_point) const
276{
277 // Now, let's first collect all paths to snap to. If we have a whole bunch of points to snap,
278 // e.g. when translating an item using the selector tool, then we will only do this for the
279 // first point and store the collection for later use. This significantly improves the performance
280 if (first_point) {
281 _clear_paths();
282
283 // Determine the type of bounding box we should snap to
285
286 bool p_is_a_node = source_type & SNAPSOURCE_NODE_CATEGORY;
287 bool p_is_a_bbox = source_type & SNAPSOURCE_BBOX_CATEGORY;
288 bool p_is_other = (source_type & SNAPSOURCE_OTHERS_CATEGORY) || (source_type & SNAPSOURCE_DATUMS_CATEGORY);
289
290 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) {
291 Preferences *prefs = Preferences::get();
292 int prefs_bbox = prefs->getBool("/tools/bounding_box", false);
293 bbox_type = !prefs_bbox ?
295 }
296
297 auto document = _snapmanager->getDocument();
298 auto &pm = document->getPageManager();
299 for (auto page : document->getPageManager().getPages()) {
300 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_EDGE_BORDER) && _snapmanager->snapprefs.isAnyCategorySnappable()) {
301 auto pathv = _getPathvFromRect(page->getDesktopRect());
302 _paths_to_snap_to->emplace_back(pathv, SNAPTARGET_PAGE_EDGE_BORDER, Geom::OptRect());
303 }
304 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_MARGIN_BORDER) && _snapmanager->snapprefs.isAnyCategorySnappable()) {
305 auto margin = _getPathvFromRect(page->getDesktopMargin());
306 _paths_to_snap_to->emplace_back(margin, SNAPTARGET_PAGE_MARGIN_BORDER, Geom::OptRect());
307 auto bleed = _getPathvFromRect(page->getDesktopBleed());
308 _paths_to_snap_to->emplace_back(bleed, SNAPTARGET_PAGE_BLEED_BORDER, Geom::OptRect());
309 }
310 }
311
312 if (!pm.hasPages()) {
313 // Consider the page border for snapping
314 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PAGE_EDGE_BORDER) && _snapmanager->snapprefs.isAnyCategorySnappable()) {
315 auto pathv = _getPathvFromRect(*(_snapmanager->getDocument()->preferredBounds()));
316 _paths_to_snap_to->emplace_back(pathv, SNAPTARGET_PAGE_EDGE_BORDER, Geom::OptRect());
317 }
318 }
319
320 for (const auto & _candidate : *_snapmanager->_obj_snapper_candidates) {
321 /* Transform the requested snap point to this item's coordinates */
323 SPItem *root_item = nullptr;
324 /* We might have a clone at hand, so make sure we get the root item */
325 auto use = cast<SPUse>(_candidate.item);
326 if (use) {
327 i2doc = use->get_root_transform();
328 root_item = use->root();
329 g_return_if_fail(root_item);
330 } else {
331 i2doc = _candidate.item->i2doc_affine();
332 root_item = _candidate.item;
333 }
334
335 //Build a list of all paths considered for snapping to
336
337 //Add the item's path to snap to
338 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION, SNAPTARGET_TEXT_BASELINE)) {
339 if (p_is_other || p_is_a_node || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_bbox)) {
340 if (is<SPText>(root_item) || is<SPFlowtext>(root_item)) {
341 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_TEXT_BASELINE)) {
342 // Snap to the text baselines
343 Text::Layout const *layout = te_get_layout(static_cast<SPItem *>(root_item));
344 if (layout != nullptr && layout->outputExists()) {
345 Geom::Affine transform = root_item->i2dt_affine() * _candidate.additional_affine * _snapmanager->getDesktop()->doc2dt();
347 for (auto const& baseline : layout->getBaselines()) {
348 std::array<Geom::LineSegment, 1> const segments{baseline};
349 Geom::Path const baseline_path{segments.begin(), segments.end()};
350 pv.push_back(baseline_path * transform);
351 }
352 _paths_to_snap_to->emplace_back(std::move(pv), SNAPTARGET_TEXT_BASELINE, Geom::OptRect());
353 }
354 }
355 } else {
356 // Snapping for example to a traced bitmap is very stressing for
357 // the CPU, so we'll only snap to paths having no more than 500 nodes
358 // This also leads to a lag of approx. 500 msec (in my lousy test set-up).
359 bool very_complex_path = false;
360 auto path = cast<SPPath>(root_item);
361 if (path) {
362 very_complex_path = path->nodesInPath() > 500;
363 }
364
365 if (!very_complex_path && root_item && _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION)) {
366 if (auto const shape = cast<SPShape>(root_item)) {
367 if (auto const curve = shape->curve()) {
368 Geom::Affine transform = use ? use->get_xy_offset(): Geom::Affine(); // If we're dealing with an SPUse, then account for any X/Y offset
369 transform *= root_item->i2dt_affine(); // Because all snapping calculations are done in desktop coordinates
370 transform *= _candidate.additional_affine; // Only used for snapping to masks or clips; see SnapManager::_findCandidates()
371 transform *= _snapmanager->getDesktop()->doc2dt(); // Account for inverted y-axis
372 auto pv = curve->get_pathvector();
373 pv *= transform;
374 _paths_to_snap_to->emplace_back(std::move(pv), SNAPTARGET_PATH, Geom::OptRect()); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides t
375 }
376 }
377 }
378 }
379 }
380 }
381
382 //Add the item's bounding box to snap to
383 if (_snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_BBOX_EDGE)) {
384 if (p_is_other || p_is_a_bbox || (!_snapmanager->snapprefs.getStrictSnapping() && p_is_a_node)) {
385 // Discard the bbox of a clipped path / mask, because we don't want to snap to both the bbox
386 // of the item AND the bbox of the clipping path at the same time
387 if (!_candidate.clip_or_mask) {
388 if (auto rect = root_item->bounds(bbox_type, i2doc)) {
389 auto path = _getPathvFromRect(*rect);
390 rect = root_item->desktopBounds(bbox_type);
391 _paths_to_snap_to->emplace_back(std::move(path), SNAPTARGET_BBOX_EDGE, rect);
392 }
393 }
394 }
395 }
396 }
397 }
398}
399
401 SnapCandidatePoint const &p,
402 std::vector<SnapCandidatePoint> *unselected_nodes,
403 SPPath const *selected_path) const
404{
405 _collectPaths(p.getPoint(), p.getSourceType(), p.getSourceNum() <= 0);
406 // Now we can finally do the real snapping, using the paths collected above
407
408 SPDesktop const *dt = _snapmanager->getDesktop();
409 g_assert(dt != nullptr);
410 Geom::Point const p_doc = dt->dt2doc(p.getPoint());
411
412 bool const node_tool_active = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION) && selected_path != nullptr;
413
414 if (p.getSourceNum() <= 0) {
415 /* findCandidates() is used for snapping to both paths and nodes. It ignores the path that is
416 * currently being edited, because that path requires special care: when snapping to nodes
417 * only the unselected nodes of that path should be considered, and these will be passed on separately.
418 * This path must not be ignored however when snapping to the paths, so we add it here
419 * manually when applicable.
420 * */
421 if (node_tool_active) {
422 // TODO fix the function to be const correct:
423 if (auto curve = curve_for_item(const_cast<SPPath *>(selected_path))) {
424 _paths_to_snap_to->emplace_back(curve->get_pathvector() * selected_path->i2doc_affine(),
426 }
427 }
428 }
429
430 int num_path = 0; // _paths_to_snap_to contains multiple path_vectors, each containing multiple paths.
431 // num_path will count the paths, and will not be zeroed for each path_vector. It will
432 // continue counting
433
434 bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping();
435 bool snap_perp = _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_PATH_PERPENDICULAR);
436 bool snap_tang = _snapmanager->snapprefs.isTargetSnappable(Inkscape::SNAPTARGET_PATH_TANGENTIAL);
437
438 //dt->getSnapIndicator()->remove_debugging_points();
439 for (const auto & it_p : *_paths_to_snap_to) {
440 if (_allowSourceToSnapToTarget(p.getSourceType(), it_p.target_type, strict_snapping)) {
441 bool const being_edited = node_tool_active && it_p.currently_being_edited;
442 //if true then this pathvector it_pv is currently being edited in the node tool
443
444 for (auto &it_pv : it_p.path_vector) {
445 // Find a nearest point for each curve within this path
446 // n curves will return n time values with 0 <= t <= 1
447 std::vector<double> anp = it_pv.nearestTimePerCurve(p_doc);
448
449 //std::cout << "#nearest points = " << anp.size() << " | p = " << p.getPoint() << std::endl;
450 // Now we will examine each of the nearest points, and determine whether it's within snapping range and if we should snap to it
451 std::vector<double>::const_iterator np = anp.begin();
452 unsigned int index = 0;
453 for (; np != anp.end(); ++np, index++) {
454 Geom::Curve const *curve = &it_pv.at(index);
455 Geom::Point const sp_doc = curve->pointAt(*np);
456 //dt->getSnapIndicator()->set_new_debugging_point(sp_doc*dt->doc2dt());
457 bool c1 = true;
458 bool c2 = true;
459 if (being_edited) {
460 /* If the path is being edited, then we should only snap though to stationary pieces of the path
461 * and not to the pieces that are being dragged around. This way we avoid
462 * self-snapping. For this we check whether the nodes at both ends of the current
463 * piece are unselected; if they are then this piece must be stationary
464 */
465 g_assert(unselected_nodes != nullptr);
466 Geom::Point start_pt = dt->doc2dt(curve->pointAt(0));
467 Geom::Point end_pt = dt->doc2dt(curve->pointAt(1));
468 c1 = isUnselectedNode(start_pt, unselected_nodes);
469 c2 = isUnselectedNode(end_pt, unselected_nodes);
470 /* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly
471 * snap to path segments that are not stationary. There are at least two possible ways to overcome this:
472 * - Linking the individual nodes of the SPPath we have here, to the nodes of the NodePath::SubPath class as being
473 * used in sp_nodepath_selected_nodes_move. This class has a member variable called "selected". For this the nodes
474 * should be in the exact same order for both classes, so we can index them
475 * - Replacing the SPPath being used here by the NodePath::SubPath class; but how?
476 */
477 }
478
479 Geom::Point const sp_dt = dt->doc2dt(sp_doc);
480 if (!being_edited || (c1 && c2)) {
481 Geom::Coord dist = Geom::distance(sp_doc, p_doc);
482 // std::cout << " dist -> " << dist << std::endl;
483 if (dist < getSnapperTolerance()) {
484 // Add the curve we have snapped to
485 Geom::Point sp_tangent_dt = Geom::Point(0,0);
487 // We currently only use the tangent when snapping guides, so only in this case we will
488 // actually calculate the tangent to avoid wasting CPU cycles
489 Geom::Point sp_tangent_doc = curve->unitTangentAt(*np);
490 sp_tangent_dt = dt->doc2dt(sp_tangent_doc) - dt->doc2dt(Geom::Point(0,0));
491 }
492 bool always = getSnapperAlwaysSnap(p.getSourceType());
493 isr.curves.emplace_back(sp_dt, sp_tangent_dt, num_path, index, dist, getSnapperTolerance(), always, false, curve, p.getSourceType(), p.getSourceNum(), it_p.target_type, it_p.target_bbox);
494 if (snap_tang || snap_perp) {
495 // For each curve that's within snapping range, we will now also search for tangential and perpendicular snaps
496 _snapPathsTangPerp(snap_tang, snap_perp, isr, p, curve, dt);
497 }
498 }
499 }
500 }
501 num_path++;
502 } // End of: for (Geom::PathVector::iterator ....)
503 }
504 }
505}
506
507/* Returns true if point is coincident with one of the unselected nodes */
508bool Inkscape::ObjectSnapper::isUnselectedNode(Geom::Point const &point, std::vector<SnapCandidatePoint> const *unselected_nodes) const
509{
510 if (unselected_nodes == nullptr) {
511 return false;
512 }
513
514 if (unselected_nodes->size() == 0) {
515 return false;
516 }
517
518 for (const auto & unselected_node : *unselected_nodes) {
519 if (Geom::L2(point - unselected_node.getPoint()) < 1e-4) {
520 return true;
521 }
522 }
523
524 return false;
525}
526
528 SnapCandidatePoint const &p,
529 SnapConstraint const &c,
530 Geom::Point const &p_proj_on_constraint,
531 std::vector<SnapCandidatePoint> *unselected_nodes,
532 SPPath const *selected_path) const
533{
534
535 _collectPaths(p_proj_on_constraint, p.getSourceType(), p.getSourceNum() <= 0);
536
537 // Now we can finally do the real snapping, using the paths collected above
538
539 SPDesktop const *dt = _snapmanager->getDesktop();
540 g_assert(dt != nullptr);
541
542 Geom::Point direction_vector = c.getDirection();
543 if (!is_zero(direction_vector)) {
544 direction_vector = Geom::unit_vector(direction_vector);
545 }
546
547 // The intersection point of the constraint line with any path, must lie within two points on the
548 // SnapConstraint: p_min_on_cl and p_max_on_cl. The distance between those points is twice the snapping tolerance
549 Geom::Point const p_min_on_cl = dt->dt2doc(p_proj_on_constraint - getSnapperTolerance() * direction_vector);
550 Geom::Point const p_max_on_cl = dt->dt2doc(p_proj_on_constraint + getSnapperTolerance() * direction_vector);
551 Geom::Coord tolerance = getSnapperTolerance();
552
553 // PS: Because the paths we're about to snap to are all expressed relative to document coordinate system, we will have
554 // to convert the snapper coordinates from the desktop coordinates to document coordinates
555
556 Geom::PathVector constraint_path;
557 if (c.isCircular()) {
558 Geom::Circle constraint_circle(dt->dt2doc(c.getPoint()), c.getRadius());
560 pb.feed(constraint_circle);
561 pb.flush();
562 constraint_path = pb.peek();
563 } else {
564 Geom::Path constraint_line;
565 constraint_line.start(p_min_on_cl);
566 constraint_line.appendNew<Geom::LineSegment>(p_max_on_cl);
567 constraint_path.push_back(constraint_line);
568 }
569
570 bool const node_tool_active = _snapmanager->snapprefs.isTargetSnappable(SNAPTARGET_PATH, SNAPTARGET_PATH_INTERSECTION) && selected_path != nullptr;
571
572 //TODO: code duplication
573 if (p.getSourceNum() <= 0) {
574 /* findCandidates() is used for snapping to both paths and nodes. It ignores the path that is
575 * currently being edited, because that path requires special care: when snapping to nodes
576 * only the unselected nodes of that path should be considered, and these will be passed on separately.
577 * This path must not be ignored however when snapping to the paths, so we add it here
578 * manually when applicable.
579 * */
580 if (node_tool_active) {
581 // TODO fix the function to be const correct:
582 if (auto curve = curve_for_item(const_cast<SPPath *>(selected_path))) {
583 _paths_to_snap_to->emplace_back(curve->get_pathvector() * selected_path->i2doc_affine(), SNAPTARGET_PATH, Geom::OptRect(), true);
584 }
585 }
586 }
587
588 bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping();
589
590 // Find all intersections of the constrained path with the snap target candidates
591 for (const auto & k : *_paths_to_snap_to) {
592 if (_allowSourceToSnapToTarget(p.getSourceType(), k.target_type, strict_snapping)) {
593 // Do the intersection math
594 std::vector<Geom::PVIntersection> inters = constraint_path.intersect(k.path_vector);
595
596 bool const being_edited = node_tool_active && k.currently_being_edited;
597
598 // Convert the collected intersections to snapped points
599 for (const auto & inter : inters) {
600 int index = inter.second.path_index; // index on the second path, which is the target path that we snapped to
601 Geom::Curve const *curve = &k.path_vector.at(index).at(inter.second.curve_index);
602
603 bool c1 = true;
604 bool c2 = true;
605 //TODO: Remove code duplication, see _snapPaths; it's documented in detail there
606 if (being_edited) {
607 g_assert(unselected_nodes != nullptr);
608 Geom::Point start_pt = dt->doc2dt(curve->pointAt(0));
609 Geom::Point end_pt = dt->doc2dt(curve->pointAt(1));
610 c1 = isUnselectedNode(start_pt, unselected_nodes);
611 c2 = isUnselectedNode(end_pt, unselected_nodes);
612 }
613
614 if (!being_edited || (c1 && c2)) {
615 // Convert to desktop coordinates
616 Geom::Point p_inters = dt->doc2dt(inter.point());
617 // Construct a snapped point
618 Geom::Coord dist = Geom::L2(p.getPoint() - p_inters);
619 bool always = getSnapperAlwaysSnap(p.getSourceType());
620 SnappedPoint s = SnappedPoint(p_inters, p.getSourceType(), p.getSourceNum(), k.target_type, dist, getSnapperTolerance(), always, true, false, k.target_bbox);
621 // Store the snapped point
622 if (dist <= tolerance) { // If the intersection is within snapping range, then we might snap to it
623 isr.points.push_back(s);
624 }
625 }
626 }
627 }
628 }
629}
630
631
633 SnapCandidatePoint const &p,
634 Geom::OptRect const &bbox_to_snap,
635 std::vector<SPObject const *> const *it,
636 std::vector<SnapCandidatePoint> *unselected_nodes) const
637{
638 if (_snap_enabled == false || _snapmanager->snapprefs.isSourceSnappable(p.getSourceType()) == false || ThisSnapperMightSnap() == false) {
639 return;
640 }
641
642 /* Get a list of all the SPItems that we will try to snap to; this only needs to be done for some snappers, and
643 not for the grid snappers, so we'll do this here and not in the Snapmanager::freeSnap(). This saves us from wasting
644 precious CPU cycles */
645 if (p.getSourceNum() <= 0) {
646 Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(p.getPoint(), p.getPoint());
647 _snapmanager->_findCandidates(_snapmanager->getDocument()->getRoot(), it, local_bbox_to_snap, false, Geom::identity());
648 }
649
650 _snapNodes(isr, p, unselected_nodes);
651
653 unsigned n = (unselected_nodes == nullptr) ? 0 : unselected_nodes->size();
654 if (n > 0) {
655 /* While editing a path in the node tool, findCandidates must ignore that path because
656 * of the node snapping requirements (i.e. only unselected nodes must be snapable).
657 * That path must not be ignored however when snapping to the paths, so we add it here
658 * manually when applicable
659 */
660 SPPath const *path = nullptr;
661 if (it != nullptr) {
662 SPPath const *tmpPath = cast<SPPath>(*it->begin());
663 if ((it->size() == 1) && tmpPath) {
664 path = tmpPath;
665 } // else: *it->begin() might be a SPGroup, e.g. when editing a LPE of text that has been converted to a group of paths
666 // as reported in bug #356743. In that case we can just ignore it, i.e. not snap to this item
667 }
668 _snapPaths(isr, p, unselected_nodes, path);
669 } else {
670 _snapPaths(isr, p, nullptr, nullptr);
671 }
672 }
673}
674
676 SnapCandidatePoint const &p,
677 Geom::OptRect const &bbox_to_snap,
678 SnapConstraint const &c,
679 std::vector<SPObject const *> const *it,
680 std::vector<SnapCandidatePoint> *unselected_nodes) const
681{
682 if (_snap_enabled == false || _snapmanager->snapprefs.isSourceSnappable(p.getSourceType()) == false || ThisSnapperMightSnap() == false) {
683 return;
684 }
685
686 // project the mouse pointer onto the constraint. Only the projected point will be considered for snapping
687 Geom::Point pp = c.projection(p.getPoint());
688
689 /* Get a list of all the SPItems that we will try to snap to; this only needs to be done for some snappers, and
690 not for the grid snappers, so we'll do this here and not in the Snapmanager::freeSnap(). This saves us from wasting
691 precious CPU cycles */
692 if (p.getSourceNum() <= 0) {
693 Geom::Rect const local_bbox_to_snap = bbox_to_snap ? *bbox_to_snap : Geom::Rect(pp, pp); // Using the projected point here! Not so in freeSnap()!
694 _snapmanager->_findCandidates(_snapmanager->getDocument()->getRoot(), it, local_bbox_to_snap, false, Geom::identity());
695 }
696
697 // A constrained snap, is a snap in only one degree of freedom (specified by the constraint line).
698 // This is useful for example when scaling an object while maintaining a fixed aspect ratio. Its
699 // nodes are only allowed to move in one direction (i.e. in one degree of freedom).
700
701 _snapNodes(isr, p, unselected_nodes, c, pp);
702
704 //TODO: Remove code duplication; see freeSnap()
705 unsigned n = (unselected_nodes == nullptr) ? 0 : unselected_nodes->size();
706 if (n > 0) {
707 /* While editing a path in the node tool, findCandidates must ignore that path because
708 * of the node snapping requirements (i.e. only unselected nodes must be snapable).
709 * That path must not be ignored however when snapping to the paths, so we add it here
710 * manually when applicable
711 */
712 SPPath const *path = nullptr;
713 if (it != nullptr) {
714 SPPath const *tmpPath = cast<SPPath>(*it->begin());
715 if ((it->size() == 1) && tmpPath) {
716 path = tmpPath;
717 } // else: *it->begin() might be a SPGroup, e.g. when editing a LPE of text that has been converted to a group of paths
718 // as reported in bug #356743. In that case we can just ignore it, i.e. not snap to this item
719 }
720 _snapPathsConstrained(isr, p, c, pp, unselected_nodes, path);
721 } else {
722 _snapPathsConstrained(isr, p, c, pp, nullptr, nullptr);
723 }
724 }
725}
726
728{
729 return true;
730}
731
733{
734 _paths_to_snap_to->clear();
735}
736
741
746 std::vector<SnapCandidatePoint> *points,
747 bool const isTarget,
748 bool const corners,
749 bool const edges,
750 bool const midpoint)
751{
752 getBBoxPoints(bbox, points, isTarget,
759}
760
762 std::vector<SnapCandidatePoint> *points,
763 bool const /*isTarget*/,
764 Inkscape::SnapSourceType corner_src,
765 Inkscape::SnapTargetType corner_tgt,
770{
771 if (bbox) {
772 // collect the corners of the bounding box
773 for (unsigned k = 0; k < 4; k++) {
774 if (corner_src || corner_tgt) {
775 points->emplace_back(bbox->corner(k), corner_src, -1, corner_tgt, *bbox);
776 }
777 // optionally, collect the midpoints of the bounding box's edges too
778 if (edge_src || edge_tgt) {
779 points->emplace_back((bbox->corner(k) + bbox->corner((k + 1) % 4)) / 2, edge_src, -1, edge_tgt, *bbox);
780 }
781 }
782 if (mid_src || mid_tgt) {
783 points->emplace_back(bbox->midpoint(), mid_src, -1, mid_tgt, *bbox);
784 }
785 }
786}
787
789{
790 bool allow_this_pair_to_snap = true;
791
792 if (strict_snapping) { // bounding boxes will not snap to nodes/paths and vice versa
793 if (((source & SNAPSOURCE_BBOX_CATEGORY) && (target & SNAPTARGET_NODE_CATEGORY)) ||
794 ((source & SNAPSOURCE_NODE_CATEGORY) && (target & SNAPTARGET_BBOX_CATEGORY))) {
795 allow_this_pair_to_snap = false;
796 }
797 }
798
799 return allow_this_pair_to_snap;
800}
801
802void Inkscape::ObjectSnapper::_snapPathsTangPerp(bool snap_tang, bool snap_perp, IntermSnapResults &isr, SnapCandidatePoint const &p, Geom::Curve const *curve, SPDesktop const *dt) const
803{
804 bool always = getSnapperAlwaysSnap(p.getSourceType());
805 // Here we will try to snap either tangentially or perpendicularly to a single path; for this we need to know where the origin is located of the line that is currently being rotated,
806 // or we need to know the vector of the guide which is currently being translated
807 std::vector<std::pair<Geom::Point, bool> > const origins_and_vectors = p.getOriginsAndVectors();
808 // Now we will iterate over all the origins and vectors and see which of these will get use a tangential or perpendicular snap
809 for (const auto & origins_and_vector : origins_and_vectors) {
810 Geom::Point origin_or_vector_doc = dt->dt2doc(origins_and_vector.first); // "first" contains a Geom::Point, denoting either a point or vector
811 if (origins_and_vector.second) { // if "second" is true then "first" is a vector, otherwise it's a point
812 // So we have a vector, which tells us what tangential or perpendicular direction we're looking for
813 if (curve->degreesOfFreedom() <= 2) { // A LineSegment has order one, and therefore 2 DOF
814 // When snapping to a point of a line segment that has a specific tangential or normal vector, then either all point
815 // along that line will be snapped to or no points at all will be snapped to. This is not very useful, so let's skip
816 // any line segments and lets only snap to higher order curves
817 continue;
818 }
819 // The vector is being treated as a point (relative to the origin), and has been translated to document coordinates accordingly
820 // We need however to make it a vector again, because also the origin has been transformed
821 origin_or_vector_doc -= dt->dt2doc(Geom::Point(0,0));
822 }
823
824 Geom::Point point_dt;
825 Geom::Coord dist;
826 std::vector<double> ts;
827
828 if (snap_tang) { // Find all points that lead to a tangential snap
829 if (origins_and_vector.second) { // if "second" is true then "first" is a vector, otherwise it's a point
830 ts = find_tangents_by_vector(origin_or_vector_doc, curve->toSBasis());
831 } else {
832 ts = find_tangents(origin_or_vector_doc, curve->toSBasis());
833 }
834 for (double t : ts) {
835 point_dt = dt->doc2dt(curve->pointAt(t));
836 dist = Geom::distance(point_dt, p.getPoint());
837 isr.points.emplace_back(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_TANGENTIAL, dist, getSnapperTolerance(), always, false, true);
838 }
839 }
840
841 if (snap_perp) { // Find all points that lead to a perpendicular snap
842 if (origins_and_vector.second) {
843 ts = find_normals_by_vector(origin_or_vector_doc, curve->toSBasis());
844 } else {
845 ts = find_normals(origin_or_vector_doc, curve->toSBasis());
846 }
847 for (double t : ts) {
848 point_dt = dt->doc2dt(curve->pointAt(t));
849 dist = Geom::distance(point_dt, p.getPoint());
850 isr.points.emplace_back(point_dt, p.getSourceType(), p.getSourceNum(), SNAPTARGET_PATH_PERPENDICULAR, dist, getSnapperTolerance(), always, false, true);
851 }
852 }
853 }
854}
855
856/*
857 Local Variables:
858 mode:c++
859 c-file-style:"stroustrup"
860 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
861 indent-tabs-mode:nil
862 fill-column:99
863 End:
864*/
865// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
uint64_t page
Definition canvas.cpp:171
int margin
Definition canvas.cpp:166
Circle shape.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Set of all points at a fixed distance from the center.
Definition circle.h:55
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
Infinite line on a plane.
Definition line.h:53
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Store paths to a PathVector.
Definition path-sink.h:226
PathVector const & peek() const
Retrieve the path.
Definition path-sink.h:236
void feed(Path const &other) override
Output a subpath.
Definition path-sink.h:209
void flush() override
Flush any internal state of the generator.
Definition path-sink.h:196
Sequence of subpaths.
Definition pathvector.h:122
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
std::vector< PVIntersection > intersect(PathVector const &other, Coord precision=EPSILON) const
Sequence of contiguous curves, aka spline.
Definition path.h:353
const_iterator begin() const
Definition path.h:464
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
void start(Point const &p)
Definition path.cpp:426
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
void constrainedSnap(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, SnapConstraint const &c, std::vector< SPObject const * > const *it, std::vector< SnapCandidatePoint > *unselected_nodes) const override
void _collectNodes(Inkscape::SnapSourceType const &t, bool const &first_point) const
std::unique_ptr< std::vector< SnapCandidatePath > > _paths_to_snap_to
Geom::PathVector _getPathvFromRect(Geom::Rect const rect) const
ObjectSnapper(SnapManager *sm, Geom::Coord const d)
bool ThisSnapperMightSnap() const override
void freeSnap(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap, std::vector< SPObject const * > const *it, std::vector< SnapCandidatePoint > *unselected_nodes) const override
bool isUnselectedNode(Geom::Point const &point, std::vector< Inkscape::SnapCandidatePoint > const *unselected_nodes) const
bool _allowSourceToSnapToTarget(SnapSourceType source, SnapTargetType target, bool strict_snapping) const
void _snapPathsConstrained(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, SnapConstraint const &c, Geom::Point const &p_proj_on_constraint, std::vector< SnapCandidatePoint > *unselected_nodes, SPPath const *selected_path) const
void _collectPaths(Geom::Point p, Inkscape::SnapSourceType const source_type, bool const &first_point) const
Returns index of first NR_END bpath in array.
void _snapTranslatingGuide(IntermSnapResults &isr, Geom::Point const &p, Geom::Point const &guide_normal) const
bool getSnapperAlwaysSnap(SnapSourceType const &source) const override
void _snapPathsTangPerp(bool snap_tang, bool snap_perp, IntermSnapResults &isr, SnapCandidatePoint const &p, Geom::Curve const *curve, SPDesktop const *dt) const
Geom::Coord getSnapperTolerance() const override
void _snapNodes(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, std::vector< SnapCandidatePoint > *unselected_nodes, SnapConstraint const &c=SnapConstraint(), Geom::Point const &p_proj_on_constraint=Geom::Point()) const
void _snapPaths(IntermSnapResults &isr, Inkscape::SnapCandidatePoint const &p, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes, SPPath const *selected_path) const
std::unique_ptr< std::vector< SnapCandidatePoint > > _points_to_snap_to
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Class to store data for points which are snap candidates, either as a source or as a target.
std::vector< std::pair< Geom::Point, bool > > const & getOriginsAndVectors() const
Inkscape::SnapSourceType getSourceType() const
Geom::Point const & getPoint() const
Class describing the result of an attempt to snap.
Geom::Coord getSnapDistance() const
Parent for classes that can snap points to something.
Definition snapper.h:39
Generates the layout for either wrapped or non-wrapped text and stores the result.
Definition Layout-TNG.h:144
std::vector< Geom::LineSegment > getBaselines() const
Returns all the baselines of a text element.
Definition Layout-TNG.h:539
bool outputExists() const
Returns true if there are some glyphs in this object, ie whether computeFlow() has been called on a n...
Definition Layout-TNG.h:362
Wrapper around a Geom::PathVector object.
Definition curve.h:28
Geom::PathVector & get_pathvector()
Definition curve.h:35
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
Geom::Affine const & doc2dt() const
Definition desktop.cpp:1337
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1837
Geom::OptRect bounds(BBoxType type, Geom::Affine const &transform=Geom::identity()) const
Definition sp-item.cpp:1011
Geom::OptRect desktopBounds(BBoxType type) const
Definition sp-item.cpp:1083
void getSnappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs=nullptr) const
Definition sp-item.cpp:1121
@ VISUAL_BBOX
Definition sp-item.h:118
@ GEOMETRIC_BBOX
Definition sp-item.h:116
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1832
SVG <path> implementation.
Definition sp-path.h:29
Class to coordinate snapping operations.
Definition snap.h:80
Geom::Point corners[8]
double c[8][4]
Editable view implementation.
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
Infinite straight line.
Point projection(Point const &p, Line const &line)
Definition line.h:513
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
Affine identity()
Create an identity matrix.
Definition affine.h:210
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Point unit_vector(Point const &a)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
SnapSourceType
enumerations of snap source types and snap target types.
Definition snap-enums.h:18
@ SNAPSOURCE_OTHERS_CATEGORY
Definition snap-enums.h:51
@ SNAPSOURCE_BBOX_MIDPOINT
Definition snap-enums.h:26
@ SNAPSOURCE_GUIDE
Definition snap-enums.h:47
@ SNAPSOURCE_UNDEFINED
Definition snap-enums.h:19
@ SNAPSOURCE_BBOX_CORNER
Definition snap-enums.h:25
@ SNAPSOURCE_DATUMS_CATEGORY
Definition snap-enums.h:46
@ SNAPSOURCE_GUIDE_ORIGIN
Definition snap-enums.h:48
@ SNAPSOURCE_PAGE_CORNER
Definition snap-enums.h:31
@ SNAPSOURCE_NODE_CATEGORY
Definition snap-enums.h:35
@ SNAPSOURCE_BBOX_EDGE_MIDPOINT
Definition snap-enums.h:27
@ SNAPSOURCE_PAGE_CENTER
Definition snap-enums.h:30
@ SNAPSOURCE_BBOX_CATEGORY
Definition snap-enums.h:23
@ SNAPTARGET_BBOX_EDGE_MIDPOINT
Definition snap-enums.h:78
@ SNAPTARGET_UNDEFINED
Definition snap-enums.h:71
@ SNAPTARGET_PAGE_MARGIN_BORDER
Definition snap-enums.h:108
@ SNAPTARGET_PATH
Definition snap-enums.h:85
@ SNAPTARGET_PAGE_MARGIN_CENTER
Definition snap-enums.h:109
@ SNAPTARGET_ROTATION_CENTER
Definition snap-enums.h:117
@ SNAPTARGET_PAGE_BLEED_CORNER
Definition snap-enums.h:112
@ SNAPTARGET_BBOX_MIDPOINT
Definition snap-enums.h:79
@ SNAPTARGET_PAGE_EDGE_CENTER
Definition snap-enums.h:106
@ SNAPTARGET_PAGE_EDGE_CORNER
Definition snap-enums.h:107
@ SNAPTARGET_NODE_CATEGORY
Definition snap-enums.h:81
@ SNAPTARGET_PAGE_EDGE_BORDER
Definition snap-enums.h:105
@ SNAPTARGET_TEXT_BASELINE
Definition snap-enums.h:119
@ SNAPTARGET_BBOX_EDGE
Definition snap-enums.h:77
@ SNAPTARGET_PAGE_MARGIN_CORNER
Definition snap-enums.h:110
@ SNAPTARGET_BBOX_CORNER
Definition snap-enums.h:76
@ SNAPTARGET_PATH_TANGENTIAL
Definition snap-enums.h:87
@ SNAPTARGET_PATH_PERPENDICULAR
Definition snap-enums.h:86
@ SNAPTARGET_PATH_INTERSECTION
Definition snap-enums.h:88
@ SNAPTARGET_PAGE_BLEED_BORDER
Definition snap-enums.h:111
@ SNAPTARGET_BBOX_CATEGORY
Definition snap-enums.h:73
void getBBoxPoints(Geom::OptRect const bbox, std::vector< SnapCandidatePoint > *points, bool const isTarget, bool const corners, bool const edges, bool const midpoint)
Default version of the getBBoxPoints with default corner source types.
Path intersection.
callback interface for SVG path data
std::optional< SPCurve > curve_for_item(SPItem *item)
Gets an SPCurve from the SPItem.
Definition path-util.cpp:73
Path utilities.
Singleton class to access the preferences file in a convenient way.
Edges edges(Path const &p, Crossings const &cr, unsigned ix)
Definition sanitize.cpp:36
TODO: insert short description here.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SPPage – a page object.
SPRoot: SVG <svg> implementation.
std::list< Inkscape::SnappedCurve > curves
Definition snapper.h:29
std::list< Inkscape::SnappedPoint > points
Definition snapper.h:26
Definition curve.h:24
int index
Inkscape::Text::Layout const * te_get_layout(SPItem const *item)