// SPDX-License-Identifier: GPL-2.0-or-later /* * Base class for shapes, including element * * Author: * Lauris Kaplinski * Abhishek Sharma * * Copyright (C) 1999-2002 Lauris Kaplinski * Copyright (C) 2000-2001 Ximian, Inc. * Copyright (C) 2004 John Cliff * Copyright (C) 2007-2008 Johan Engelen * Copyright (C) 2010 Jon A. Cruz * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include <2geom/rect.h> #include <2geom/transforms.h> #include <2geom/pathvector.h> #include <2geom/path-intersection.h> #include "helper/geom.h" #include "helper/geom-nodetype.h" #include #include #include "display/drawing-shape.h" #include "display/curve.h" #include "print.h" #include "document.h" #include "style.h" #include "sp-marker.h" #include "sp-root.h" #include "sp-path.h" #include "preferences.h" #include "attributes.h" #include "path/path-outline.h" // For bound box calculation #include "svg/svg.h" #include "svg/path-string.h" #include "snap-candidate.h" #include "snap-preferences.h" #include "live_effects/lpeobject.h" #include "helper/mathfns.h" // for triangle_area() #define noSHAPE_VERBOSE static void sp_shape_update_marker_view (SPShape *shape, Inkscape::DrawingItem *ai); SPShape::SPShape() : SPLPEItem() { for (auto & i : this->_marker) { i = nullptr; } this->_curve = nullptr; this->_curve_before_lpe = nullptr; } SPShape::~SPShape() { for ( int i = 0 ; i < SP_MARKER_LOC_QTY ; i++ ) { this->_release_connect[i].disconnect(); this->_modified_connect[i].disconnect(); } } void SPShape::build(SPDocument *document, Inkscape::XML::Node *repr) { SPLPEItem::build(document, repr); for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value()); } } /** * Removes, releases and unrefs all children of object * * This is the inverse of sp_shape_build(). It must be invoked as soon * as the shape is removed from the tree, even if it is still referenced * by other objects. This routine also disconnects/unrefs markers and * curves attached to it. * * \see SPObject::release() */ void SPShape::release() { for (int i = 0; i < SP_MARKER_LOC_QTY; i++) { if (this->_marker[i]) { for (SPItemView *v = this->display; v != nullptr; v = v->next) { sp_marker_hide(_marker[i], v->arenaitem->key() + i); } this->_release_connect[i].disconnect(); this->_modified_connect[i].disconnect(); _marker[i]->unhrefObject(this); _marker[i] = nullptr; } } _curve.reset(); _curve_before_lpe.reset(); SPLPEItem::release(); } void SPShape::set(SPAttr key, const gchar* value) { SPLPEItem::set(key, value); } Inkscape::XML::Node* SPShape::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { SPLPEItem::write(xml_doc, repr, flags); return repr; } void SPShape::update(SPCtx* ctx, guint flags) { // Any update can change the bounding box, // so the cached version can no longer be used. // But the idle checker usually is just moving the objects around. bbox_vis_cache_is_valid = false; bbox_geom_cache_is_valid = false; // std::cout << "SPShape::update(): " << (getId()?getId():"null") << std::endl; SPLPEItem::update(ctx, flags); /* This stanza checks that an object's marker style agrees with * the marker objects it has allocated. sp_shape_set_marker ensures * that the appropriate marker objects are present (or absent) to * match the style. */ for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value()); } if (flags & (SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { if (this->style->stroke_width.unit == SP_CSS_UNIT_PERCENT) { SPItemCtx *ictx = (SPItemCtx *) ctx; double const aw = 1.0 / ictx->i2vp.descrim(); this->style->stroke_width.computed = this->style->stroke_width.value * aw; for (SPItemView *v = ((SPItem *) (this))->display; v != nullptr; v = v->next) { Inkscape::DrawingShape *sh = dynamic_cast(v->arenaitem); if (hasMarkers()) { this->context_style = this->style; sh->setStyle(this->style, this->context_style); // Done at end: // sh->setChildrenStyle(this->context_style); //Resolve 'context-xxx' in children. } else if (this->parent) { this->context_style = this->parent->context_style; sh->setStyle(this->style, this->context_style); } } } } if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG)) { /* This is suboptimal, because changing parent style schedules recalculation */ /* But on the other hand - how can we know that parent does not tie style and transform */ for (SPItemView *v = this->display; v != nullptr; v = v->next) { Inkscape::DrawingShape *sh = dynamic_cast(v->arenaitem); if (flags & SP_OBJECT_MODIFIED_FLAG) { sh->setPath(this->_curve.get()); } } } if (this->hasMarkers ()) { /* Dimension marker views */ for (SPItemView *v = this->display; v != nullptr; v = v->next) { if (!v->arenaitem->key()) { v->arenaitem->setKey(SPItem::display_key_new (SP_MARKER_LOC_QTY)); } for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { if (_marker[i]) { sp_marker_show_dimension(_marker[i], v->arenaitem->key() + i, numberOfMarkers(i)); } } } /* Update marker views */ for (SPItemView *v = this->display; v != nullptr; v = v->next) { sp_shape_update_marker_view (this, v->arenaitem); } // Marker selector needs this here or marker previews are not rendered. for (SPItemView *v = this->display; v != nullptr; v = v->next) { Inkscape::DrawingShape *sh = dynamic_cast(v->arenaitem); sh->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children. } } /* Update stroke/dashes for relative units. */ if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { SPItemCtx const *ictx = reinterpret_cast(ctx); double const w = ictx->viewport.width(); double const h = ictx->viewport.height(); double const d = sqrt(w*w + h*h) * M_SQRT1_2; // diagonal per SVG spec double const em = style->font_size.computed; double const ex = 0.5 * em; // fixme: get x height from pango or libnrtype. if (style->stroke_width.unit == SP_CSS_UNIT_EM) { style->stroke_width.computed = style->stroke_width.value * em; } else if (style->stroke_width.unit == SP_CSS_UNIT_EX) { style->stroke_width.computed = style->stroke_width.value * ex; } else if (style->stroke_width.unit == SP_CSS_UNIT_PERCENT) { style->stroke_width.computed = style->stroke_width.value * d; } if (style->stroke_dasharray.values.size() != 0) { for (auto&& i: style->stroke_dasharray.values) { if (i.unit == SP_CSS_UNIT_EM) i.computed = i.value * em; else if (i.unit == SP_CSS_UNIT_EX) i.computed = i.value * ex; else if (i.unit == SP_CSS_UNIT_PERCENT) i.computed = i.value * d; } } if (style->stroke_dashoffset.unit == SP_CSS_UNIT_EM) { style->stroke_dashoffset.computed = style->stroke_dashoffset.value * em; } else if (style->stroke_dashoffset.unit == SP_CSS_UNIT_EX) { style->stroke_dashoffset.computed = style->stroke_dashoffset.value * ex; } else if (style->stroke_dashoffset.unit == SP_CSS_UNIT_PERCENT) { style->stroke_dashoffset.computed = style->stroke_dashoffset.value * d; } } } /** * Calculate the transform required to get a marker's path object in the * right place for particular path segment on a shape. * * \see sp_shape_marker_update_marker_view. * * From SVG spec: * The axes of the temporary new user coordinate system are aligned according to the orient attribute on the 'marker' * element and the slope of the curve at the given vertex. (Note: if there is a discontinuity at a vertex, the slope * is the average of the slopes of the two segments of the curve that join at the given vertex. If a slope cannot be * determined, the slope is assumed to be zero.) * * Reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement, the `orient' attribute. * Reference for behaviour of zero-length segments: * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */ Geom::Affine sp_shape_marker_get_transform(Geom::Curve const & c1, Geom::Curve const & c2) { Geom::Point p = c1.pointAt(1); Geom::Curve * c1_reverse = c1.reverse(); Geom::Point tang1 = - c1_reverse->unitTangentAt(0); delete c1_reverse; Geom::Point tang2 = c2.unitTangentAt(0); double const angle1 = Geom::atan2(tang1); double const angle2 = Geom::atan2(tang2); double ret_angle = .5 * (angle1 + angle2); if ( fabs( angle2 - angle1 ) > M_PI ) { /* ret_angle is in the middle of the larger of the two sectors between angle1 and * angle2, so flip it by 180degrees to force it to the middle of the smaller sector. * * (Imagine a circle with rays drawn at angle1 and angle2 from the centre of the * circle. Those two rays divide the circle into two sectors.) */ ret_angle += M_PI; } return Geom::Rotate(ret_angle) * Geom::Translate(p); } Geom::Affine sp_shape_marker_get_transform_at_start(Geom::Curve const & c) { Geom::Point p = c.pointAt(0); Geom::Affine ret = Geom::Translate(p); if ( !c.isDegenerate() ) { Geom::Point tang = c.unitTangentAt(0); double const angle = Geom::atan2(tang); ret = Geom::Rotate(angle) * Geom::Translate(p); } else { /* FIXME: the svg spec says to search for a better alternative than zero angle directionality: * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */ } return ret; } Geom::Affine sp_shape_marker_get_transform_at_end(Geom::Curve const & c) { Geom::Point p = c.pointAt(1); Geom::Affine ret = Geom::Translate(p); if ( !c.isDegenerate() ) { Geom::Curve * c_reverse = c.reverse(); Geom::Point tang = - c_reverse->unitTangentAt(0); delete c_reverse; double const angle = Geom::atan2(tang); ret = Geom::Rotate(angle) * Geom::Translate(p); } else { /* FIXME: the svg spec says to search for a better alternative than zero angle directionality: * http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes */ } return ret; } /** * Updates the instances (views) of a given marker in a shape. * Marker views have to be scaled already. The transformation * is retrieved and then shown by calling sp_marker_show_instance. * * @todo figure out what to do when both 'marker' and for instance 'marker-end' are set. */ static void sp_shape_update_marker_view(SPShape *shape, Inkscape::DrawingItem *ai) { // position arguments to sp_marker_show_instance, basically counts the amount of markers. int counter[4] = {0}; if (!shape->curve()) return; Geom::PathVector const &pathv = shape->curve()->get_pathvector(); if (pathv.empty()) return; // the first vertex should get a start marker, the last an end marker, and all the others a mid marker // see bug 456148 // START marker { Geom::Affine const m (sp_shape_marker_get_transform_at_start(pathv.begin()->front())); for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START if ( shape->_marker[i] ) { Geom::Affine m_auto = m; // Reverse start marker if necessary. if (shape->_marker[i]->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { m_auto = Geom::Rotate::from_degrees( 180.0 ) * m; } sp_marker_show_instance(shape->_marker[i], ai, ai->key() + i, counter[i], m_auto, shape->style->stroke_width.computed); counter[i]++; } } } // MID marker if (shape->_marker[SP_MARKER_LOC_MID] || shape->_marker[SP_MARKER_LOC]) { for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { // START position if ( path_it != pathv.begin() && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, don't draw mid marker there { Geom::Affine const m (sp_shape_marker_get_transform_at_start(path_it->front())); for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if ( shape->_marker[i] ) { sp_marker_show_instance(shape->_marker[i], ai, ai->key() + i, counter[i], m, shape->style->stroke_width.computed); counter[i]++; } } } // MID position if ( path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) { /* Put marker between curve_it1 and curve_it2. * Loop to end_default (so including closing segment), because when a path is closed, * there should be a midpoint marker between last segment and closing straight line segment */ Geom::Affine const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2)); for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if (shape->_marker[i]) { sp_marker_show_instance(shape->_marker[i], ai, ai->key() + i, counter[i], m, shape->style->stroke_width.computed); counter[i]++; } } ++curve_it1; ++curve_it2; } } // END position if ( path_it != (pathv.end()-1) && !path_it->empty()) { Geom::Curve const &lastcurve = path_it->back_default(); Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve); for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if (shape->_marker[i]) { sp_marker_show_instance(shape->_marker[i], ai, ai->key() + i, counter[i], m, shape->style->stroke_width.computed); counter[i]++; } } } } } // END marker if ( shape->_marker[SP_MARKER_LOC_END] || shape->_marker[SP_MARKER_LOC] ) { /* Get reference to last curve in the path. * For moveto-only path, this returns the "closing line segment". */ Geom::Path const &path_last = pathv.back(); unsigned int index = path_last.size_default(); if (index > 0) { index--; } Geom::Curve const &lastcurve = path_last[index]; Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve); for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END if (shape->_marker[i]) { sp_marker_show_instance(shape->_marker[i], ai, ai->key() + i, counter[i], m, shape->style->stroke_width.computed); counter[i]++; } } } } void SPShape::modified(unsigned int flags) { // std::cout << "SPShape::modified(): " << (getId()?getId():"null") << std::endl; SPLPEItem::modified(flags); if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { for (SPItemView *v = this->display; v != nullptr; v = v->next) { Inkscape::DrawingShape *sh = dynamic_cast(v->arenaitem); if (hasMarkers()) { this->context_style = this->style; sh->setStyle(this->style, this->context_style); // Note: marker selector preview does not trigger SP_OBJECT_STYLE_MODIFIED_FLAG so // this is not called when marker previews are generated, however there is code in // SPShape::update() that calls this routine so we don't worry about it here. sh->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children. } else if (this->parent) { this->context_style = this->parent->context_style; sh->setStyle(this->style, this->context_style); } } } if (flags & SP_OBJECT_MODIFIED_FLAG && style->filter.set) { if (auto filter = style->getFilter()) { filter->update_filter_all_regions(); } } if (!_curve) { sp_lpe_item_update_patheffect(this, true, false); } } bool SPShape::checkBrokenPathEffect() { if (hasBrokenPathEffect()) { g_warning("The shape has unknown LPE on it. Convert to path to make it editable preserving the appearance; " "editing it will remove the bad LPE"); if (this->getRepr()->attribute("d")) { // unconditionally read the curve from d, if any, to preserve appearance Geom::PathVector pv = sp_svg_read_pathv(this->getAttribute("d")); setCurveInsync(std::make_unique(pv)); setCurveBeforeLPE(curve()); } return true; } return false; } /* Reset the shape's curve to the "original_curve" * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ bool SPShape::prepareShapeForLPE(SPCurve const *c) { auto const before = curveBeforeLPE(); if (before && before->get_pathvector() != c->get_pathvector()) { setCurveBeforeLPE(std::move(c)); sp_lpe_item_update_patheffect(this, true, false); return true; } if (hasPathEffectOnClipOrMaskRecursive(this)) { if (!before && this->getRepr()->attribute("d")) { Geom::PathVector pv = sp_svg_read_pathv(getAttribute("d")); setCurveInsync(std::make_unique(pv)); } setCurveBeforeLPE(std::move(c)); return true; } return false; } Geom::OptRect SPShape::bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const { // If the object is clipped, the update function that invalidates // the cache doesn't get called if the object is moved, so we need // to compare the transformations as well. if (bboxtype == SPItem::VISUAL_BBOX) { bbox_vis_cache = either_bbox(transform, bboxtype, bbox_vis_cache_is_valid, bbox_vis_cache, bbox_vis_cache_transform); if (bbox_vis_cache) { bbox_vis_cache_transform = transform; bbox_vis_cache_is_valid = true; } return bbox_vis_cache; } else { bbox_geom_cache = either_bbox(transform, bboxtype, bbox_geom_cache_is_valid, bbox_geom_cache, bbox_geom_cache_transform); if (bbox_geom_cache) { bbox_geom_cache_transform = transform; bbox_geom_cache_is_valid = true; } return bbox_geom_cache; } } Geom::OptRect SPShape::either_bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype, bool cache_is_valid, Geom::OptRect bbox_cache, Geom::Affine const &transform_cache) const { Geom::OptRect bbox; // Return the cache if possible. auto delta = transform_cache.inverse() * transform; if (cache_is_valid && bbox_cache && delta.isTranslation()) { // Don't re-adjust the cache if we haven't moved if (!delta.isNonzeroTranslation()) { return bbox_cache; } // delta is pure translation so it's safe to use it as is return *bbox_cache * delta; } if (!this->_curve || this->_curve->get_pathvector().empty()) { return bbox; } bbox = bounds_exact_transformed(this->_curve->get_pathvector(), transform); if (!bbox) { return bbox; } if (bboxtype == SPItem::VISUAL_BBOX) { // convert the stroke to a path and calculate that path's geometric bbox if (!this->style->stroke.isNone() && !this->style->stroke_extensions.hairline) { Geom::PathVector *pathv = item_to_outline(this, true); // calculate bbox_only if (pathv) { bbox |= bounds_exact_transformed(*pathv, transform); delete pathv; } } // Union with bboxes of the markers, if any if ( this->hasMarkers() && !this->_curve->get_pathvector().empty() ) { /** \todo make code prettier! */ Geom::PathVector const & pathv = this->_curve->get_pathvector(); // START marker for (unsigned i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START if ( this->_marker[i] ) { SPItem* marker_item = sp_item_first_item_child( _marker[i] ); if (marker_item) { Geom::Affine tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front())); if (_marker[i]->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { // Reverse start marker if necessary tr = Geom::Rotate::from_degrees( 180.0 ) * tr; } else if (_marker[i]->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(_marker[i]->orient.computed) * Geom::Translate(transl); } if (_marker[i]->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } // total marker transform tr = marker_item->transform * _marker[i]->c2p * tr * transform; // get bbox of the marker with that transform bbox |= marker_item->visualBounds(tr); } } } // MID marker for (unsigned i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if ( !this->_marker[i] ) { continue; } SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if ( !marker_item ) { continue; } for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { // START position if ( path_it != pathv.begin() && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there { Geom::Affine tr(sp_shape_marker_get_transform_at_start(path_it->front())); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } // MID position if ( path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) { /* Put marker between curve_it1 and curve_it2. * Loop to end_default (so including closing segment), because when a path is closed, * there should be a midpoint marker between last segment and closing straight line segment */ SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if (marker_item) { Geom::Affine tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } ++curve_it1; ++curve_it2; } } // END position if ( path_it != (pathv.end()-1) && !path_it->empty()) { Geom::Curve const &lastcurve = path_it->back_default(); Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } tr = marker_item->transform * marker->c2p * tr * transform; bbox |= marker_item->visualBounds(tr); } } } // END marker for (unsigned i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END if ( _marker[i] ) { SPMarker* marker = _marker[i]; SPItem* marker_item = sp_item_first_item_child( marker ); if (marker_item) { /* Get reference to last curve in the path. * For moveto-only path, this returns the "closing line segment". */ Geom::Path const &path_last = pathv.back(); unsigned int index = path_last.size_default(); if (index > 0) { index--; } Geom::Curve const &lastcurve = path_last[index]; Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); if (marker->orient_mode == MARKER_ORIENT_ANGLE) { Geom::Point transl = tr.translation(); tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(transl); } if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(this->style->stroke_width.computed) * tr; } // total marker transform tr = marker_item->transform * marker->c2p * tr * transform; // get bbox of the marker with that transform bbox |= marker_item->visualBounds(tr); } } } } } return bbox; } static void sp_shape_print_invoke_marker_printing(SPObject *obj, Geom::Affine tr, SPStyle const *style, SPPrintContext *ctx) { SPMarker *marker = SP_MARKER(obj); if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { tr = Geom::Scale(style->stroke_width.computed) * tr; } SPItem* marker_item = sp_item_first_item_child( marker ); if (marker_item) { tr = marker_item->transform * marker->c2p * tr; Geom::Affine old_tr = marker_item->transform; marker_item->transform = tr; marker_item->invoke_print (ctx); marker_item->transform = old_tr; } } void SPShape::print(SPPrintContext* ctx) { if (!this->_curve) { return; } Geom::PathVector const & pathv = this->_curve->get_pathvector(); if (pathv.empty()) { return; } /* fixme: Think (Lauris) */ Geom::OptRect pbox, dbox, bbox; pbox = this->geometricBounds(); bbox = this->desktopVisualBounds(); dbox = Geom::Rect::from_xywh(Geom::Point(0,0), this->document->getDimensions()); Geom::Affine const i2dt(this->i2dt_affine()); SPStyle* style = this->style; if (!style->fill.isNone()) { ctx->fill (pathv, i2dt, style, pbox, dbox, bbox); } if (!style->stroke.isNone()) { ctx->stroke (pathv, i2dt, style, pbox, dbox, bbox); } /** \todo make code prettier */ // START marker for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START if ( this->_marker[i] ) { Geom::Affine tr(sp_shape_marker_get_transform_at_start(pathv.begin()->front())); sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx); } } // MID marker for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID if (this->_marker[i]) { for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { // START position if ( path_it != pathv.begin() && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there { Geom::Affine tr(sp_shape_marker_get_transform_at_start(path_it->front())); sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx); } // MID position if ( path_it->size_default() > 1) { Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve while (curve_it2 != path_it->end_default()) { /* Put marker between curve_it1 and curve_it2. * Loop to end_default (so including closing segment), because when a path is closed, * there should be a midpoint marker between last segment and closing straight line segment */ Geom::Affine tr(sp_shape_marker_get_transform(*curve_it1, *curve_it2)); sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx); ++curve_it1; ++curve_it2; } } if ( path_it != (pathv.end()-1) && !path_it->empty()) { Geom::Curve const &lastcurve = path_it->back_default(); Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx); } } } } // END marker if ( this->_marker[SP_MARKER_LOC_END] || this->_marker[SP_MARKER_LOC]) { /* Get reference to last curve in the path. * For moveto-only path, this returns the "closing line segment". */ Geom::Path const &path_last = pathv.back(); unsigned int index = path_last.size_default(); if (index > 0) { index--; } Geom::Curve const &lastcurve = path_last[index]; Geom::Affine tr = sp_shape_marker_get_transform_at_end(lastcurve); for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END if (this->_marker[i]) { sp_shape_print_invoke_marker_printing(this->_marker[i], tr, style, ctx); } } } } void SPShape::update_patheffect(bool write) { auto c_lpe = SPCurve::copy(curveForEdit()); if (!c_lpe) { set_shape(); c_lpe = SPCurve::copy(curveForEdit()); } if (c_lpe) { /* if a path has an lpeitem applied, then reset the curve to the _curve_before_lpe. * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/ this->setCurveInsync(c_lpe.get()); SPRoot *root = this->document->getRoot(); if (!sp_version_inside_range(root->version.inkscape, 0, 1, 0, 92)) { this->resetClipPathAndMaskLPE(); } bool success = false; if (hasPathEffect() && pathEffectsEnabled()) { success = this->performPathEffect(c_lpe.get(), this); if (success) { this->setCurveInsync(c_lpe.get()); this->applyToClipPath(this); this->applyToMask(this); } } if (write && success) { Inkscape::XML::Node *repr = this->getRepr(); if (repr) { if (c_lpe != nullptr) { repr->setAttribute("d", sp_svg_write_path(c_lpe->get_pathvector())); } else { repr->removeAttribute("d"); } } } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } } Inkscape::DrawingItem* SPShape::show(Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/) { // std::cout << "SPShape::show(): " << (getId()?getId():"null") << std::endl; Inkscape::DrawingShape *s = new Inkscape::DrawingShape(drawing); bool has_markers = this->hasMarkers(); s->setPath(this->_curve.get()); /* This stanza checks that an object's marker style agrees with * the marker objects it has allocated. sp_shape_set_marker ensures * that the appropriate marker objects are present (or absent) to * match the style. */ for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { sp_shape_set_marker (this, i, this->style->marker_ptrs[i]->value()); } if (has_markers) { /* provide key and dimension the marker views */ if (!s->key()) { s->setKey(SPItem::display_key_new (SP_MARKER_LOC_QTY)); } for (int i = 0; i < SP_MARKER_LOC_QTY; i++) { if (_marker[i]) { sp_marker_show_dimension(_marker[i], s->key() + i, numberOfMarkers(i)); } } /* Update marker views */ sp_shape_update_marker_view (this, s); this->context_style = this->style; s->setStyle(this->style, this->context_style); s->setChildrenStyle(this->context_style); // Resolve 'context-xxx' in children. } else if (this->parent) { this->context_style = this->parent->context_style; s->setStyle(this->style, this->context_style); } return s; } /** * Sets style, path, and paintbox. Updates marker views, including dimensions. */ void SPShape::hide(unsigned int key) { for (int i = 0; i < SP_MARKER_LOC_QTY; ++i) { if (_marker[i]) { for (SPItemView* v = display; v != nullptr; v = v->next) { if (key == v->key) { sp_marker_hide(_marker[i], v->arenaitem->key() + i); } } } } //SPLPEItem::onHide(key); } /** * \param shape Shape. * \return TRUE if the shape has any markers, or FALSE if not. */ int SPShape::hasMarkers() const { /* Note, we're ignoring 'marker' settings, which technically should apply for all three settings. This should be fixed later such that if 'marker' is specified, then all three should appear. */ // Ignore markers for objects which are inside markers themselves. for (SPObject *parent = this->parent; parent != nullptr; parent = parent->parent) { if (dynamic_cast(parent)) { return 0; } } return ( this->_curve && (this->_marker[SP_MARKER_LOC] || this->_marker[SP_MARKER_LOC_START] || this->_marker[SP_MARKER_LOC_MID] || this->_marker[SP_MARKER_LOC_END]) ); } /** * \param shape Shape. * \param type Marker type (e.g. SP_MARKER_LOC_START) * \return Number of markers that the shape has of this type. */ int SPShape::numberOfMarkers(int type) const { Geom::PathVector const & pathv = this->_curve->get_pathvector(); if (pathv.size() == 0) { return 0; } switch(type) { case SP_MARKER_LOC: { if ( this->_marker[SP_MARKER_LOC] ) { guint n = 0; for(const auto & path_it : pathv) { n += path_it.size_default() + 1; } return n; } else { return 0; } } case SP_MARKER_LOC_START: // there is only a start marker on the first path of a pathvector return this->_marker[SP_MARKER_LOC_START] ? 1 : 0; case SP_MARKER_LOC_MID: { if ( this->_marker[SP_MARKER_LOC_MID] ) { guint n = 0; for(const auto & path_it : pathv) { n += path_it.size_default() + 1; } n = (n > 1) ? (n - 2) : 0; // Minus the start and end marker, but never negative. // A path or polyline may have only one point. return n; } else { return 0; } } case SP_MARKER_LOC_END: { // there is only an end marker on the last path of a pathvector return this->_marker[SP_MARKER_LOC_END] ? 1 : 0; } default: return 0; } } /** * Checks if the given marker is used in the shape, and if so, it * releases it by calling sp_marker_hide. Also detaches signals * and unrefs the marker from the shape. */ static void sp_shape_marker_release (SPObject *marker, SPShape *shape) { SPItem *item = dynamic_cast(shape); g_return_if_fail(item != nullptr); for (int i = 0; i < SP_MARKER_LOC_QTY; i++) { if (marker == shape->_marker[i]) { SPItemView *v; /* Hide marker */ for (v = item->display; v != nullptr; v = v->next) { sp_marker_hide(shape->_marker[i], v->arenaitem->key() + i); } /* Detach marker */ shape->_release_connect[i].disconnect(); shape->_modified_connect[i].disconnect(); shape->_marker[i]->unhrefObject(item); shape->_marker[i] = nullptr; } } } /** * No-op. Exists for handling 'modified' messages */ static void sp_shape_marker_modified (SPObject* marker, guint flags, SPItem* item) { if ((flags & SP_OBJECT_MODIFIED_FLAG) && item && marker) { // changing marker can impact object's visual bounding box, so request update on this object itself item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } } /** * Adds a new marker to shape object at the location indicated by key. value * must be a valid URI reference resolvable from the shape object (i.e., present * in the document ). If the shape object already has a marker * registered at the given position, it is removed first. Then the * new marker is hrefed and its signals connected. */ void sp_shape_set_marker (SPObject *object, unsigned int key, const gchar *value) { SPShape *shape = dynamic_cast(object); g_return_if_fail(shape != nullptr); if (key > SP_MARKER_LOC_END) { return; } SPObject *mrk = sp_css_uri_reference_resolve(object->document, value); SPMarker *marker = dynamic_cast(mrk); if (marker != shape->_marker[key]) { if (shape->_marker[key]) { SPItemView *v; /* Detach marker */ shape->_release_connect[key].disconnect(); shape->_modified_connect[key].disconnect(); /* Hide marker */ for (v = shape->display; v != nullptr; v = v->next) { sp_marker_hide(shape->_marker[key], v->arenaitem->key() + key); } /* Unref marker */ shape->_marker[key]->unhrefObject(object); shape->_marker[key] = nullptr; } if (marker) { shape->_marker[key] = marker; shape->_marker[key]->hrefObject(object); shape->_release_connect[key] = marker->connectRelease(sigc::bind<1>(sigc::ptr_fun(&sp_shape_marker_release), shape)); shape->_modified_connect[key] = marker->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_shape_marker_modified), shape)); } } } // CPPIFY: make pure virtual void SPShape::set_shape() { //throw; } /* Shape section */ void SPShape::_setCurve(std::unique_ptr &&new_curve, bool update_display) { _curve = std::move(new_curve); if (update_display && document) { requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } } void SPShape::_setCurve(SPCurve const *new_curve, bool update_display) { _setCurve(SPCurve::copy(new_curve), update_display); } /** * Adds a curve to the shape. * Any existing curve in the shape will be unreferenced first. * This routine also triggers a request to update the display. */ void SPShape::setCurve(std::unique_ptr &&new_curve) { _setCurve(std::move(new_curve), true); } void SPShape::setCurve(SPCurve const *new_curve) { _setCurve(new_curve, true); } /** * Sets _curve_before_lpe to a copy of `new_curve` */ void SPShape::setCurveBeforeLPE(std::unique_ptr &&new_curve) { _curve_before_lpe = std::move(new_curve); } void SPShape::setCurveBeforeLPE(SPCurve const *new_curve) { setCurveBeforeLPE(SPCurve::copy(new_curve)); } /** * Same as setCurve() but without updating the display */ void SPShape::setCurveInsync(std::unique_ptr &&new_curve) { _setCurve(std::move(new_curve), false); } void SPShape::setCurveInsync(SPCurve const *new_curve) { _setCurve(new_curve, false); } /** * Return a borrowed pointer to the curve (if any exists) or NULL if there is no curve */ SPCurve *SPShape::curve() { return _curve.get(); } SPCurve const *SPShape::curve() const { return _curve.get(); } /** * Return a borrowed pointer of the curve *before* LPE (if any exists) or NULL if there is no curve */ SPCurve const *SPShape::curveBeforeLPE() const { return _curve_before_lpe.get(); } /** * Return a borrowed pointer of the curve for edit */ SPCurve const *SPShape::curveForEdit() const { if (_curve_before_lpe) { return _curve_before_lpe.get(); } return curve(); } void SPShape::snappoints(std::vector &p, Inkscape::SnapPreferences const *snapprefs) const { if (this->_curve == nullptr) { return; } Geom::PathVector const &pathv = this->_curve->get_pathvector(); if (pathv.empty()) { return; } Geom::Affine const i2dt (this->i2dt_affine ()); if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) { Geom::OptRect bbox = this->desktopVisualBounds(); if (bbox) { p.emplace_back(bbox->midpoint(), Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT); } } for(const auto & path_it : pathv) { if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP)) { // Add the first point of the path p.emplace_back(path_it.initialPoint() * i2dt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP); } Geom::Path::const_iterator curve_it1 = path_it.begin(); // incoming curve Geom::Path::const_iterator curve_it2 = ++(path_it.begin()); // outgoing curve while (curve_it1 != path_it.end_default()) { // For each path: consider midpoints of line segments for snapping if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_LINE_MIDPOINT)) { if (Geom::LineSegment const* line_segment = dynamic_cast(&(*curve_it1))) { p.emplace_back(Geom::middle_point(*line_segment) * i2dt, Inkscape::SNAPSOURCE_LINE_MIDPOINT, Inkscape::SNAPTARGET_LINE_MIDPOINT); } } if (curve_it2 == path_it.end_default()) { // Test will only pass for the last iteration of the while loop if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && !path_it.closed()) { // Add the last point of the path, but only for open paths // (for closed paths the first and last point will coincide) p.emplace_back((*curve_it1).finalPoint() * i2dt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP); } } else { /* Test whether to add the node between curve_it1 and curve_it2. * Loop to end_default (so only iterating through the stroked part); */ Geom::NodeType nodetype = Geom::get_nodetype(*curve_it1, *curve_it2); bool c1 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && (nodetype == Geom::NODE_CUSP || nodetype == Geom::NODE_NONE); bool c2 = snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_SMOOTH) && (nodetype == Geom::NODE_SMOOTH || nodetype == Geom::NODE_SYMM); if (c1 || c2) { Inkscape::SnapSourceType sst; Inkscape::SnapTargetType stt; switch (nodetype) { case Geom::NODE_CUSP: sst = Inkscape::SNAPSOURCE_NODE_CUSP; stt = Inkscape::SNAPTARGET_NODE_CUSP; break; case Geom::NODE_SMOOTH: case Geom::NODE_SYMM: sst = Inkscape::SNAPSOURCE_NODE_SMOOTH; stt = Inkscape::SNAPTARGET_NODE_SMOOTH; break; default: sst = Inkscape::SNAPSOURCE_UNDEFINED; stt = Inkscape::SNAPTARGET_UNDEFINED; break; } p.emplace_back(curve_it1->finalPoint() * i2dt, sst, stt); } } ++curve_it1; ++curve_it2; } // Find the internal intersections of each path and consider these for snapping // (using "Method 1" as described in Inkscape::ObjectSnapper::_collectNodes()) if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_PATH_INTERSECTION) || snapprefs->isSourceSnappable(Inkscape::SNAPSOURCE_PATH_INTERSECTION)) { Geom::Crossings cs; try { cs = self_crossings(path_it); // This can be slow! if (!cs.empty()) { // There might be multiple intersections... for (const auto & c : cs) { Geom::Point p_ix = path_it.pointAt(c.ta); p.emplace_back(p_ix * i2dt, Inkscape::SNAPSOURCE_PATH_INTERSECTION, Inkscape::SNAPTARGET_PATH_INTERSECTION); } } } catch (Geom::RangeError &e) { // do nothing // The exception could be Geom::InfiniteSolutions: then no snappoints should be added } } } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :