From: mjwybrow Date: Wed, 15 Jul 2009 05:12:33 +0000 (+0000) Subject: - Fix bug #171150: Connectors should always touch the shape boundary X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=2bb33702d3002a9cc9d6751d6918c86fa046586e;p=inkscape.git - Fix bug #171150: Connectors should always touch the shape boundary This fix is backported from some of Arcadie Cracan's Summer of Code work on the gsoc2009_connectors branch. --- diff --git a/src/sp-conn-end-pair.cpp b/src/sp-conn-end-pair.cpp index 49d6fbcdb..4dc0230ff 100644 --- a/src/sp-conn-end-pair.cpp +++ b/src/sp-conn-end-pair.cpp @@ -309,6 +309,9 @@ SPConnEndPair::reroutePath(void) Geom::Point p(route.ps[i].x, route.ps[i].y); curve->lineto(p); } + + Geom::Matrix doc2item = sp_item_i2doc_affine(SP_ITEM(_path)).inverse(); + curve->transform(doc2item); } /* diff --git a/src/sp-conn-end.cpp b/src/sp-conn-end.cpp index 91ff4bc2b..0b420a98e 100644 --- a/src/sp-conn-end.cpp +++ b/src/sp-conn-end.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "display/curve.h" #include "libnr/nr-matrix-fns.h" @@ -9,11 +10,13 @@ #include "sp-path.h" #include "uri.h" #include "document.h" +#include "sp-item-group.h" +#include "2geom/path.h" +#include "2geom/pathvector.h" +#include "2geom/path-intersection.h" static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2]); -static Geom::Point calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p); -static double signed_one(double const x); SPConnEnd::SPConnEnd(SPObject *const owner) : ref(owner), @@ -35,6 +38,95 @@ get_nearest_common_ancestor(SPObject const *const obj, SPItem const *const objs[ return anc_sofar; } + +static bool try_get_intersect_point_with_item_recursive(SPCurve *conn_curve, SPItem& item, + const Geom::Matrix& item_transform, const bool at_start, double* intersect_pos, + unsigned *intersect_index) { + + double initial_pos = (at_start) ? 0.0 : std::numeric_limits::max(); + + // if this is a group... + if (SP_IS_GROUP(&item)) { + SPGroup* group = SP_GROUP(&item); + + // consider all first-order children + double child_pos = initial_pos; + unsigned child_index; + for (GSList const* i = sp_item_group_item_list(group); i != NULL; i = i->next) { + SPItem* child_item = SP_ITEM(i->data); + try_get_intersect_point_with_item_recursive(conn_curve, *child_item, + item_transform * child_item->transform, at_start, &child_pos, &child_index); + if (fabs(initial_pos - child_pos) > fabs(initial_pos - *intersect_pos)) { + // It is further away from the initial point than the current intersection + // point (i.e. the "outermost" intersection), so use this one. + *intersect_pos = child_pos; + *intersect_index = child_index; + } + } + return *intersect_pos != initial_pos; + } + + // if this is a shape... + if (!SP_IS_SHAPE(&item)) return false; + + // make sure it has an associated curve + SPCurve* item_curve = sp_shape_get_curve(SP_SHAPE(&item)); + if (!item_curve) return false; + + // apply transformations (up to common ancestor) + item_curve->transform(item_transform); + + const Geom::PathVector& curve_pv = item_curve->get_pathvector(); + const Geom::PathVector& conn_pv = conn_curve->get_pathvector(); + Geom::CrossingSet cross = crossings(conn_pv, curve_pv); + // iterate over all Crossings + for (Geom::CrossingSet::const_iterator i = cross.begin(); i != cross.end(); i++) { + const Geom::Crossings& cr = *i; + + for (Geom::Crossings::const_iterator i = cr.begin(); i != cr.end(); i++) { + const Geom::Crossing& cr_pt = *i; + if (fabs(initial_pos - cr_pt.ta) > fabs(initial_pos - *intersect_pos)) { + // It is further away from the initial point than the current intersection + // point (i.e. the "outermost" intersection), so use this one. + *intersect_pos = cr_pt.ta; + *intersect_index = cr_pt.a; + } + } + } + + item_curve->unref(); + + return *intersect_pos != initial_pos; +} + + +// This function returns the outermost intersection point between the path (a connector) +// and the item given. If the item is a group, then the component items are considered. +// The transforms given should be to a common ancestor of both the path and item. +// +static bool try_get_intersect_point_with_item(SPPath& conn, SPItem& item, + const Geom::Matrix& item_transform, const Geom::Matrix& conn_transform, + const bool at_start, double* intersect_pos, unsigned *intersect_index) { + + // We start with the intersection point either at the beginning or end of the + // path, depending on whether we are considering the source or target endpoint. + *intersect_pos = (at_start) ? 0.0 : std::numeric_limits::max(); + + // Copy the curve and apply transformations up to common ancestor. + SPCurve* conn_curve = conn.curve->copy(); + conn_curve->transform(conn_transform); + + // Find the intersection. + bool result = try_get_intersect_point_with_item_recursive(conn_curve, item, item_transform, + at_start, intersect_pos, intersect_index); + + // Free the curve copy. + conn_curve->unref(); + + return result; +} + + static void sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPPath *const path, @@ -50,98 +142,28 @@ sp_conn_end_move_compensate(Geom::Matrix const */*mp*/, SPItem */*moved_item*/, SPItem *h2attItem[2]; path->connEndPair.getAttachedItems(h2attItem); - if ( !h2attItem[0] && !h2attItem[1] ) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; - } SPItem const *const path_item = SP_ITEM(path); SPObject const *const ancestor = get_nearest_common_ancestor(path_item, h2attItem); Geom::Matrix const path2anc(i2anc_affine(path_item, ancestor)); - if (h2attItem[0] != NULL && h2attItem[1] != NULL) { - /* Initial end-points: centre of attached object. */ - Geom::Point h2endPt_icoordsys[2]; - Geom::Matrix h2i2anc[2]; - Geom::Rect h2bbox_icoordsys[2]; - Geom::Point last_seg_endPt[2] = { - *(path->curve->second_point()), - *(path->curve->penultimate_point()) - }; - for (unsigned h = 0; h < 2; ++h) { - Geom::OptRect bbox = h2attItem[h]->getBounds(Geom::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); - } - return; - } - h2bbox_icoordsys[h] = *bbox; - h2i2anc[h] = i2anc_affine(h2attItem[h], ancestor); - h2endPt_icoordsys[h] = h2bbox_icoordsys[h].midpoint(); - } - - // For each attached object, change the corresponding point to be - // on the edge of the bbox. - Geom::Point h2endPt_pcoordsys[2]; - for (unsigned h = 0; h < 2; ++h) { - h2endPt_icoordsys[h] = calc_bbox_conn_pt(h2bbox_icoordsys[h], - ( last_seg_endPt[h] * h2i2anc[h].inverse() )); - h2endPt_pcoordsys[h] = h2endPt_icoordsys[h] * h2i2anc[h] * path2anc.inverse(); - } - change_endpts(path->curve, h2endPt_pcoordsys); - } else { - // We leave the unattached endpoint where it is, and adjust the - // position of the attached endpoint to be on the edge of the bbox. - unsigned ind; - Geom::Point other_endpt; - Geom::Point last_seg_pt; - if (h2attItem[0] != NULL) { - other_endpt = *(path->curve->last_point()); - last_seg_pt = *(path->curve->second_point()); - ind = 0; - } - else { - other_endpt = *(path->curve->first_point()); - last_seg_pt = *(path->curve->penultimate_point()); - ind = 1; - } - Geom::Point h2endPt_icoordsys[2]; - Geom::Matrix h2i2anc; - - Geom::Rect otherpt_rect = Geom::Rect(other_endpt, other_endpt); - Geom::Rect h2bbox_icoordsys[2] = { otherpt_rect, otherpt_rect }; - Geom::OptRect bbox = h2attItem[ind]->getBounds(Geom::identity()); - if (!bbox) { - if (updatePathRepr) { - path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); - path->updateRepr(); + Geom::Point endPts[2] = { *(path->curve->first_point()), *(path->curve->last_point()) }; + + for (unsigned h = 0; h < 2; ++h) { + if (h2attItem[h]) { + // For each attached object, change the corresponding point to be + // at the outermost intersection with the object's path. + double intersect_pos; + unsigned intersect_index; + Geom::Matrix h2i2anc = i2anc_affine(h2attItem[h], ancestor); + if ( try_get_intersect_point_with_item(*path, *h2attItem[h], h2i2anc, path2anc, + (h == 0), &intersect_pos, &intersect_index) ) { + const Geom::PathVector& curve = path->curve->get_pathvector(); + endPts[h] = curve[intersect_index].pointAt(intersect_pos); } - return; } - - h2bbox_icoordsys[ind] = *bbox; - h2i2anc = i2anc_affine(h2attItem[ind], ancestor); - h2endPt_icoordsys[ind] = h2bbox_icoordsys[ind].midpoint(); - - h2endPt_icoordsys[!ind] = other_endpt; - - // For the attached object, change the corresponding point to be - // on the edge of the bbox. - Geom::Point h2endPt_pcoordsys[2]; - h2endPt_icoordsys[ind] = calc_bbox_conn_pt(h2bbox_icoordsys[ind], - ( last_seg_pt * h2i2anc.inverse() )); - h2endPt_pcoordsys[ind] = h2endPt_icoordsys[ind] * h2i2anc * path2anc.inverse(); - - // Leave the other where it is. - h2endPt_pcoordsys[!ind] = other_endpt; - - change_endpts(path->curve, h2endPt_pcoordsys); } + change_endpts(path->curve, endPts); if (updatePathRepr) { path->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); path->updateRepr(); @@ -180,45 +202,6 @@ sp_conn_adjust_path(SPPath *const path) sp_conn_end_move_compensate(NULL, NULL, path, updatePathRepr); } -static Geom::Point -calc_bbox_conn_pt(Geom::Rect const &bbox, Geom::Point const &p) -{ - using Geom::X; - using Geom::Y; - Geom::Point const ctr(bbox.midpoint()); - Geom::Point const lengths(bbox.dimensions()); - if ( ctr == p ) { - /* Arbitrarily choose centre of right edge. */ - return Geom::Point(ctr[X] + .5 * lengths[X], - ctr[Y]); - } - Geom::Point const cp( p - ctr ); - Geom::Dim2 const edgeDim = ( ( fabs(lengths[Y] * cp[X]) < - fabs(lengths[X] * cp[Y]) ) - ? Y - : X ); - Geom::Dim2 const otherDim = (Geom::Dim2) !edgeDim; - Geom::Point offset; - offset[edgeDim] = (signed_one(cp[edgeDim]) - * lengths[edgeDim]); - offset[otherDim] = (lengths[edgeDim] - * cp[otherDim] - / fabs(cp[edgeDim])); - g_assert((offset[otherDim] >= 0) == (cp[otherDim] >= 0)); -#ifndef NDEBUG - for (unsigned d = 0; d < 2; ++d) { - g_assert(fabs(offset[d]) <= lengths[d] + .125); - } -#endif - return ctr + .5 * offset; -} - -static double signed_one(double const x) -{ - return (x < 0 - ? -1. - : 1.); -} static void change_endpts(SPCurve *const curve, Geom::Point const h2endPt[2])