diff options
| author | Diederik van Lierop <mail@diedenrezi.nl> | 2011-06-30 20:46:15 +0000 |
|---|---|---|
| committer | Diederik van Lierop <mail@diedenrezi.nl> | 2011-06-30 20:46:15 +0000 |
| commit | 8911d9a8ca0c7f4ef1476b2f056adf2afa4e99cd (patch) | |
| tree | bc14be1ac41f82196fc5a07ac99ff3613eeb4cd7 /src/object-snapper.cpp | |
| parent | Minimise GTK version changes in text-edit dialog (diff) | |
| download | inkscape-8911d9a8ca0c7f4ef1476b2f056adf2afa4e99cd.tar.gz inkscape-8911d9a8ca0c7f4ef1476b2f056adf2afa4e99cd.zip | |
Implement decent snapping to text (baseline & anchor), and provide a toggle button for this (as requested in LP bug #727281 )
(bzr r10392)
Diffstat (limited to 'src/object-snapper.cpp')
| -rw-r--r-- | src/object-snapper.cpp | 230 |
1 files changed, 131 insertions, 99 deletions
diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index 3088accd2..1944f7ffa 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -183,7 +183,7 @@ void Inkscape::ObjectSnapper::_collectNodes(Inkscape::SnapSourceType const &t, bool p_is_a_bbox = t & Inkscape::SNAPSOURCE_BBOX_CATEGORY; bool p_is_other = t & Inkscape::SNAPSOURCE_OTHERS_CATEGORY; - // A point considered for snapping should be either a node, a bbox corner or a guide. Pick only ONE! + // A point considered for snapping should be either a node, a bbox corner or a guide/other. Pick only ONE! g_assert(!((p_is_a_node && p_is_a_bbox) || (p_is_a_bbox && p_is_other) || (p_is_a_node && p_is_other))); if (_snapmanager->snapprefs.getSnapToBBoxNode() || _snapmanager->snapprefs.getSnapBBoxEdgeMidpoints() || _snapmanager->snapprefs.getSnapBBoxMidpoints()) { @@ -282,27 +282,30 @@ void Inkscape::ObjectSnapper::_snapNodes(SnappedConstraints &sc, SnappedPoint s; bool success = false; + bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); for (std::vector<SnapCandidatePoint>::const_iterator k = _points_to_snap_to->begin(); k != _points_to_snap_to->end(); k++) { - Geom::Point target_pt = (*k).getPoint(); - Geom::Coord dist = NR_HUGE; - if (!c.isUndefined()) { - // We're snapping to nodes along a constraint only, so find out if this node - // is at the constraint, while allowing for a small margin - if (Geom::L2(target_pt - c.projection(target_pt)) > 1e-9) { - // The distance from the target point to its projection on the constraint - // is too large, so this point is not on the constraint. Skip it! - continue; + if (_allowSourceToSnapToTarget(p.getSourceType(), (*k).getTargetType(), strict_snapping)) { + Geom::Point target_pt = (*k).getPoint(); + Geom::Coord dist = NR_HUGE; + if (!c.isUndefined()) { + // We're snapping to nodes along a constraint only, so find out if this node + // is at the constraint, while allowing for a small margin + if (Geom::L2(target_pt - c.projection(target_pt)) > 1e-9) { + // The distance from the target point to its projection on the constraint + // is too large, so this point is not on the constraint. Skip it! + continue; + } + dist = Geom::L2(target_pt - p_proj_on_constraint); + } else { + // Free (unconstrained) snapping + dist = Geom::L2(target_pt - p.getPoint()); } - dist = Geom::L2(target_pt - p_proj_on_constraint); - } else { - // Free (unconstrained) snapping - dist = Geom::L2(target_pt - p.getPoint()); - } - if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) { - s = SnappedPoint(target_pt, p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox()); - success = true; + if (dist < getSnapperTolerance() && dist < s.getSnapDistance()) { + s = SnappedPoint(target_pt, p.getSourceType(), p.getSourceNum(), (*k).getTargetType(), dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, true, (*k).getTargetBBox()); + success = true; + } } } @@ -394,44 +397,46 @@ void Inkscape::ObjectSnapper::_collectPaths(Geom::Point /*p*/, //Build a list of all paths considered for snapping to //Add the item's path to snap to - if (_snapmanager->snapprefs.getSnapToItemPath() && (_snapmanager->snapprefs.getSnapModeNode() || _snapmanager->snapprefs.getSnapModeOthers())) { + if ((_snapmanager->snapprefs.getSnapToItemPath() && _snapmanager->snapprefs.getSnapModeNode()) || + (_snapmanager->snapprefs.getSnapTextBaseline() && (_snapmanager->snapprefs.getSnapModeNode() || _snapmanager->snapprefs.getSnapToItemPath())) ) { if (p_is_other || !(_snapmanager->snapprefs.getStrictSnapping() && !p_is_a_node)) { - // Snapping to the path of characters is very cool, but for a large - // chunk of text this will take ages! So limit snapping to text paths - // containing max. 240 characters. Snapping the bbox will not be affected - bool very_lenghty_prose = false; if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { - very_lenghty_prose = sp_text_get_length(SP_TEXT(root_item)) > 240; - } - // On my AMD 3000+, the snapping lag becomes annoying at approx. 240 chars - // which corresponds to a lag of 500 msec. This is for snapping a rect - // to a single line of text. - - // Snapping for example to a traced bitmap is also very stressing for - // the CPU, so we'll only snap to paths having no more than 500 nodes - // This also leads to a lag of approx. 500 msec (in my lousy test set-up). - bool very_complex_path = false; - if (SP_IS_PATH(root_item)) { - very_complex_path = sp_nodes_in_path(SP_PATH(root_item)) > 500; - } - - if (!very_lenghty_prose && !very_complex_path && root_item) { - SPCurve *curve = NULL; - if (SP_IS_SHAPE(root_item)) { - curve = SP_SHAPE(root_item)->getCurve(); - } else if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { - curve = te_get_layout(root_item)->convertToCurves(); + if (_snapmanager->snapprefs.getSnapTextBaseline()) { + // Snap to the text baseline + Inkscape::Text::Layout const *layout = te_get_layout((SPItem *) root_item); + if (layout != NULL && layout->outputExists()) { + Geom::PathVector *pv = new Geom::PathVector(); + pv->push_back(layout->baseline() * root_item->i2d_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt()); + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(pv, SNAPTARGET_TEXT_BASELINE, Geom::OptRect())); + } + } + } else { + // Snapping for example to a traced bitmap is very stressing for + // the CPU, so we'll only snap to paths having no more than 500 nodes + // This also leads to a lag of approx. 500 msec (in my lousy test set-up). + bool very_complex_path = false; + if (SP_IS_PATH(root_item)) { + very_complex_path = sp_nodes_in_path(SP_PATH(root_item)) > 500; } - if (curve) { - // We will get our own copy of the pathvector, which must be freed at some point - // Geom::PathVector *pv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine); + if (!very_complex_path && root_item && (_snapmanager->snapprefs.getSnapToItemPath() && _snapmanager->snapprefs.getSnapModeNode())) { + SPCurve *curve = NULL; + if (SP_IS_SHAPE(root_item)) { + curve = SP_SHAPE(root_item)->getCurve(); + }/* else if (SP_IS_TEXT(root_item) || SP_IS_FLOWTEXT(root_item)) { + curve = te_get_layout(root_item)->convertToCurves(); + }*/ + if (curve) { + // We will get our own copy of the pathvector, which must be freed at some point + + // Geom::PathVector *pv = pathvector_for_curve(root_item, curve, true, true, Geom::identity(), (*i).additional_affine); - Geom::PathVector *pv = new Geom::PathVector(curve->get_pathvector()); - (*pv) *= root_item->i2d_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt(); // (_edit_transform * _i2d_transform); + Geom::PathVector *pv = new Geom::PathVector(curve->get_pathvector()); + (*pv) *= root_item->i2d_affine() * (*i).additional_affine * _snapmanager->getDesktop()->doc2dt(); // (_edit_transform * _i2d_transform); - _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(pv, SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. - curve->unref(); + _paths_to_snap_to->push_back(Inkscape::SnapCandidatePath(pv, SNAPTARGET_PATH, Geom::OptRect())); // Perhaps for speed, get a reference to the Geom::pathvector, and store the transformation besides it. + curve->unref(); + } } } } @@ -490,54 +495,58 @@ void Inkscape::ObjectSnapper::_snapPaths(SnappedConstraints &sc, int num_path = 0; int num_segm = 0; - for (std::vector<Inkscape::SnapCandidatePath >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) { - bool const being_edited = node_tool_active && (*it_p).currently_being_edited; - //if true then this pathvector it_pv is currently being edited in the node tool - - for(Geom::PathVector::iterator it_pv = (it_p->path_vector)->begin(); it_pv != (it_p->path_vector)->end(); ++it_pv) { - // Find a nearest point for each curve within this path - // n curves will return n time values with 0 <= t <= 1 - std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc); + bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); - std::vector<double>::const_iterator np = anp.begin(); - unsigned int index = 0; - for (; np != anp.end(); np++, index++) { - Geom::Curve const *curve = &((*it_pv).at_index(index)); - Geom::Point const sp_doc = curve->pointAt(*np); - - bool c1 = true; - bool c2 = true; - if (being_edited) { - /* If the path is being edited, then we should only snap though to stationary pieces of the path - * and not to the pieces that are being dragged around. This way we avoid - * self-snapping. For this we check whether the nodes at both ends of the current - * piece are unselected; if they are then this piece must be stationary - */ - g_assert(unselected_nodes != NULL); - Geom::Point start_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(0)); - Geom::Point end_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(1)); - c1 = isUnselectedNode(start_pt, unselected_nodes); - c2 = isUnselectedNode(end_pt, unselected_nodes); - /* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly - * snap to path segments that are not stationary. There are at least two possible ways to overcome this: - * - Linking the individual nodes of the SPPath we have here, to the nodes of the NodePath::SubPath class as being - * used in sp_nodepath_selected_nodes_move. This class has a member variable called "selected". For this the nodes - * should be in the exact same order for both classes, so we can index them - * - Replacing the SPPath being used here by the the NodePath::SubPath class; but how? - */ - } + for (std::vector<Inkscape::SnapCandidatePath >::const_iterator it_p = _paths_to_snap_to->begin(); it_p != _paths_to_snap_to->end(); it_p++) { + if (_allowSourceToSnapToTarget(p.getSourceType(), (*it_p).target_type, strict_snapping)) { + bool const being_edited = node_tool_active && (*it_p).currently_being_edited; + //if true then this pathvector it_pv is currently being edited in the node tool + + for(Geom::PathVector::iterator it_pv = (it_p->path_vector)->begin(); it_pv != (it_p->path_vector)->end(); ++it_pv) { + // Find a nearest point for each curve within this path + // n curves will return n time values with 0 <= t <= 1 + std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc); + + std::vector<double>::const_iterator np = anp.begin(); + unsigned int index = 0; + for (; np != anp.end(); np++, index++) { + Geom::Curve const *curve = &((*it_pv).at_index(index)); + Geom::Point const sp_doc = curve->pointAt(*np); + + bool c1 = true; + bool c2 = true; + if (being_edited) { + /* If the path is being edited, then we should only snap though to stationary pieces of the path + * and not to the pieces that are being dragged around. This way we avoid + * self-snapping. For this we check whether the nodes at both ends of the current + * piece are unselected; if they are then this piece must be stationary + */ + g_assert(unselected_nodes != NULL); + Geom::Point start_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(0)); + Geom::Point end_pt = _snapmanager->getDesktop()->doc2dt(curve->pointAt(1)); + c1 = isUnselectedNode(start_pt, unselected_nodes); + c2 = isUnselectedNode(end_pt, unselected_nodes); + /* Unfortunately, this might yield false positives for coincident nodes. Inkscape might therefore mistakenly + * snap to path segments that are not stationary. There are at least two possible ways to overcome this: + * - Linking the individual nodes of the SPPath we have here, to the nodes of the NodePath::SubPath class as being + * used in sp_nodepath_selected_nodes_move. This class has a member variable called "selected". For this the nodes + * should be in the exact same order for both classes, so we can index them + * - Replacing the SPPath being used here by the the NodePath::SubPath class; but how? + */ + } - Geom::Point const sp_dt = _snapmanager->getDesktop()->doc2dt(sp_doc); - if (!being_edited || (c1 && c2)) { - Geom::Coord const dist = Geom::distance(sp_doc, p_doc); - if (dist < getSnapperTolerance()) { - sc.curves.push_back(Inkscape::SnappedCurve(sp_dt, num_path, num_segm, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, p.getSourceType(), p.getSourceNum(), it_p->target_type, it_p->target_bbox)); + Geom::Point const sp_dt = _snapmanager->getDesktop()->doc2dt(sp_doc); + if (!being_edited || (c1 && c2)) { + Geom::Coord const dist = Geom::distance(sp_doc, p_doc); + if (dist < getSnapperTolerance()) { + sc.curves.push_back(Inkscape::SnappedCurve(sp_dt, num_path, num_segm, dist, getSnapperTolerance(), getSnapperAlwaysSnap(), false, curve, p.getSourceType(), p.getSourceNum(), it_p->target_type, it_p->target_bbox)); + } } } - } - num_segm++; - } // End of: for (Geom::PathVector::iterator ....) - num_path++; + num_segm++; + } // End of: for (Geom::PathVector::iterator ....) + num_path++; + } } } @@ -599,10 +608,12 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(SnappedConstraints &sc, } // Length of constraint_path will always be one + bool strict_snapping = _snapmanager->snapprefs.getStrictSnapping(); + // Find all intersections of the constrained path with the snap target candidates std::vector<Geom::Point> intersections; for (std::vector<Inkscape::SnapCandidatePath >::const_iterator k = _paths_to_snap_to->begin(); k != _paths_to_snap_to->end(); k++) { - if (k->path_vector) { + if (k->path_vector && _allowSourceToSnapToTarget(p.getSourceType(), (*k).target_type, strict_snapping)) { // Do the intersection math Geom::CrossingSet cs = Geom::crossings(constraint_path, *(k->path_vector)); // Store the results as intersection points @@ -681,7 +692,8 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, )) || (_snapmanager->snapprefs.getSnapModeAny() && ( _snapmanager->snapprefs.getIncludeItemCenter() || _snapmanager->snapprefs.getSnapToPageBorder() || - _snapmanager->snapprefs.getSnapObjectMidpoints() + _snapmanager->snapprefs.getSnapObjectMidpoints() || + _snapmanager->snapprefs.getSnapTextBaseline() )) ; if (snap_nodes) { @@ -690,7 +702,7 @@ void Inkscape::ObjectSnapper::freeSnap(SnappedConstraints &sc, if ((_snapmanager->snapprefs.getSnapModeNode() && _snapmanager->snapprefs.getSnapToItemPath()) || (_snapmanager->snapprefs.getSnapModeBBox() && _snapmanager->snapprefs.getSnapToBBoxPath()) || - (_snapmanager->snapprefs.getSnapModeAny() && _snapmanager->snapprefs.getSnapToPageBorder())) { + _snapmanager->snapprefs.getSnapModeAny()) { unsigned n = (unselected_nodes == NULL) ? 0 : unselected_nodes->size(); if (n > 0) { /* While editing a path in the node tool, findCandidates must ignore that path because @@ -748,7 +760,8 @@ void Inkscape::ObjectSnapper::constrainedSnap( SnappedConstraints &sc, )) || (_snapmanager->snapprefs.getSnapModeAny() && ( _snapmanager->snapprefs.getIncludeItemCenter() || _snapmanager->snapprefs.getSnapObjectMidpoints() || - _snapmanager->snapprefs.getSnapToPageBorder() + _snapmanager->snapprefs.getSnapToPageBorder() || + _snapmanager->snapprefs.getSnapTextBaseline() )); if (snap_nodes) { @@ -808,7 +821,8 @@ bool Inkscape::ObjectSnapper::ThisSnapperMightSnap() const )) || (_snapmanager->snapprefs.getSnapModeAny() && ( _snapmanager->snapprefs.getSnapToPageBorder() || _snapmanager->snapprefs.getIncludeItemCenter() || - _snapmanager->snapprefs.getSnapObjectMidpoints() + _snapmanager->snapprefs.getSnapObjectMidpoints() || + _snapmanager->snapprefs.getSnapTextBaseline() )); return (_snap_enabled && snap_to_something); @@ -873,6 +887,24 @@ void Inkscape::getBBoxPoints(Geom::OptRect const bbox, } } +bool Inkscape::ObjectSnapper::_allowSourceToSnapToTarget(SnapSourceType source, SnapTargetType target, bool strict_snapping) const +{ + bool allow_this_pair_to_snap = false; + + if (strict_snapping) { // bounding boxes will not snap to nodes/paths and vice versa + int source_cat = source & (SNAPSOURCE_BBOX_CATEGORY | SNAPSOURCE_NODE_CATEGORY | SNAPSOURCE_OTHERS_CATEGORY); + int target_cat = target & (SNAPTARGET_BBOX_CATEGORY | SNAPTARGET_NODE_CATEGORY | SNAPTARGET_OTHERS_CATEGORY); + if (source_cat == target_cat || source_cat == SNAPSOURCE_OTHERS_CATEGORY || target_cat == SNAPTARGET_OTHERS_CATEGORY) { + allow_this_pair_to_snap = true; + } + } else { // anything will snap to anything + allow_this_pair_to_snap = true; + } + + return allow_this_pair_to_snap; +} + + /* Local Variables: mode:c++ |
