From c853ffb5b2f75f5a889ed2e3ef89b818a736e87a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:50:49 +0200 Subject: Adding upstream version 1.3+ds. Signed-off-by: Daniel Baumann --- src/path/path-outline.cpp | 792 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 792 insertions(+) create mode 100644 src/path/path-outline.cpp (limited to 'src/path/path-outline.cpp') diff --git a/src/path/path-outline.cpp b/src/path/path-outline.cpp new file mode 100644 index 0000000..47c3dee --- /dev/null +++ b/src/path/path-outline.cpp @@ -0,0 +1,792 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/** @file + * + * Two related object to path operations: + * + * 1. Find a path that includes fill, stroke, and markers. Useful for finding a visual bounding box. + * 2. Take a set of objects and find an identical visual representation using only paths. + * + * Copyright (C) 2020 Tavmjong Bah + * Copyright (C) 2018 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * Code moved from splivarot.cpp + * + */ + +#include "path-outline.h" + +#include + +#include "path-chemistry.h" // Should be moved to path directory +#include "message-stack.h" // Should be removed. +#include "selection.h" +#include "style.h" + +#include "display/curve.h" // Should be moved to path directory + +#include "helper/geom.h" // pathv_to_linear_and_cubic() + +#include "livarot/LivarotDefs.h" +#include "livarot/Path.h" +#include "livarot/Shape.h" + +#include "object/object-set.h" +#include "object/box3d.h" +#include "object/sp-item.h" +#include "object/sp-marker.h" +#include "object/sp-shape.h" +#include "object/sp-text.h" +#include "object/sp-flowtext.h" + +#include "svg/svg.h" + +/** + * Given an item, find a path representing the fill and a path representing the stroke. + * Returns true if fill path found. Item may not have a stroke in which case stroke path is empty. + * bbox_only==true skips cleaning up the stroke path. + * Encapsulates use of livarot. + */ +bool +item_find_paths(const SPItem *item, Geom::PathVector& fill, Geom::PathVector& stroke, bool bbox_only = false) +{ + auto shape = cast(item); + auto text = cast(item); + + if (!shape && !text) { + return false; + } + + std::optional curve; + if (shape) { + curve = SPCurve::ptr_to_opt(shape->curve()); + } else if (text) { + curve = text->getNormalizedBpath(); + } else { + std::cerr << "item_find_paths: item not shape or text!" << std::endl; + return false; + } + + if (!curve) { + std::cerr << "item_find_paths: no curve!" << std::endl; + return false; + } + + if (curve->get_pathvector().empty()) { + std::cerr << "item_find_paths: curve empty!" << std::endl; + return false; + } + + fill = curve->get_pathvector(); + + if (!item->style) { + // Should never happen + std::cerr << "item_find_paths: item with no style!" << std::endl; + return false; + } + + if (item->style->stroke.isNone()) { + // No stroke, no chocolate! + return true; + } + + // Now that we have a valid curve with stroke, do offset. We use Livarot for this as + // lib2geom does not yet handle offsets correctly. + + // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for + // which the outline is created correctly. + Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( fill ); + + SPStyle *style = item->style; + + double stroke_width = style->stroke_width.computed; + if (stroke_width < Geom::EPSILON) { + // https://bugs.launchpad.net/inkscape/+bug/1244861 + stroke_width = Geom::EPSILON; + } + double miter = style->stroke_miterlimit.value * stroke_width; + + JoinType join; + switch (style->stroke_linejoin.computed) { + case SP_STROKE_LINEJOIN_MITER: + join = join_pointy; + break; + case SP_STROKE_LINEJOIN_ROUND: + join = join_round; + break; + default: + join = join_straight; + break; + } + + ButtType butt; + switch (style->stroke_linecap.computed) { + case SP_STROKE_LINECAP_SQUARE: + butt = butt_square; + break; + case SP_STROKE_LINECAP_ROUND: + butt = butt_round; + break; + default: + butt = butt_straight; + break; + } + + Path *origin = new Path; // Fill + Path *offset = new Path; + + Geom::Affine const transform(item->transform); + double const scale = transform.descrim(); + + origin->LoadPathVector(pathv); + offset->SetBackData(false); + + if (!style->stroke_dasharray.values.empty() && style->stroke_dasharray.is_valid()) { + // We have dashes! + origin->ConvertWithBackData(0.005); // Approximate by polyline + origin->DashPolylineFromStyle(style, scale, 0); + auto bounds = Geom::bounds_fast(pathv); + if (bounds) { + double size = Geom::L2(bounds->dimensions()); + origin->Simplify(size * 0.000005); // Polylines to Beziers + } + } + + // Finally do offset! + origin->Outline(offset, 0.5 * stroke_width, join, butt, 0.5 * miter); + + if (bbox_only) { + stroke = offset->MakePathVector(); + } else { + // Clean-up shape + + offset->ConvertWithBackData(1.0); // Approximate by polyline + + Shape *theShape = new Shape; + offset->Fill(theShape, 0); // Convert polyline to shape, step 1. + + Shape *theOffset = new Shape; + theOffset->ConvertToShape(theShape, fill_positive); // Create an intersection free polygon (theOffset), step2. + theOffset->ConvertToForme(origin, 1, &offset); // Turn shape into contour (stored in origin). + + stroke = origin->MakePathVector(); // Note origin was replaced above by stroke! + } + + delete origin; + delete offset; + + // std::cout << " fill: " << sp_svg_write_path(fill) << " count: " << fill.curveCount() << std::endl; + // std::cout << " stroke: " << sp_svg_write_path(stroke) << " count: " << stroke.curveCount() << std::endl; + return true; +} + + +// ======================== Item to Outline ===================== // + +static +void item_to_outline_add_marker_child( SPItem const *item, Geom::Affine marker_transform, Geom::PathVector* pathv_in ) +{ + Geom::Affine tr(marker_transform); + tr = item->transform * tr; + + // note: a marker child item can be an item group! + if (is(item)) { + // recurse through all childs: + for (auto& o: item->children) { + if (auto childitem = cast(&o)) { + item_to_outline_add_marker_child(childitem, tr, pathv_in); + } + } + } else { + Geom::PathVector* marker_pathv = item_to_outline(item); + + if (marker_pathv) { + for (const auto & j : *marker_pathv) { + pathv_in->push_back(j * tr); + } + delete marker_pathv; + } + } +} + +static +void item_to_outline_add_marker( SPObject const *marker_object, Geom::Affine marker_transform, + Geom::Scale stroke_scale, Geom::PathVector* pathv_in ) +{ + SPMarker const * marker = cast(marker_object); + + Geom::Affine tr(marker_transform); + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + tr = stroke_scale * tr; + } + // total marker transform + tr = marker->c2p * tr; + + SPItem const * marker_item = sp_item_first_item_child(marker_object); // why only consider the first item? can a marker only consist of a single item (that may be a group)? + if (marker_item) { + item_to_outline_add_marker_child(marker_item, tr, pathv_in); + } +} + + +/** + * Returns a pathvector that is the outline of the stroked item, with markers. + * item must be an SPShape or an SPText. + * The only current use of this function has exclude_markers true! (SPShape::either_bbox). + * TODO: See if SPShape::either_bbox's union with markers is the same as one would get + * with bbox_only false. + */ +Geom::PathVector* item_to_outline(SPItem const *item, bool exclude_markers) +{ + Geom::PathVector fill; // Used for locating markers. + Geom::PathVector stroke; // Used for creating outline (and finding bbox). + item_find_paths(item, fill, stroke, true); // Skip cleaning up stroke shape. + + Geom::PathVector *ret_pathv = nullptr; + + if (fill.curveCount() == 0) { + std::cerr << "item_to_outline: fill path has no segments!" << std::endl; + return ret_pathv; + } + + if (stroke.size() > 0) { + ret_pathv = new Geom::PathVector(stroke); + } else { + // No stroke, use fill path. + ret_pathv = new Geom::PathVector(fill); + } + + if (exclude_markers) { + return ret_pathv; + } + + auto shape = cast(item); + if (shape && shape->hasMarkers()) { + + SPStyle *style = shape->style; + Geom::Scale scale(style->stroke_width.computed); + + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( SPObject *marker_obj = shape->_marker[i] ) { + Geom::Affine const m (sp_shape_marker_get_transform_at_start(fill.front().front())); + item_to_outline_add_marker( marker_obj, m, scale, ret_pathv ); + } + } + + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + SPObject *midmarker_obj = shape->_marker[i]; + if (!midmarker_obj) continue; + for(Geom::PathVector::const_iterator path_it = fill.begin(); path_it != fill.end(); ++path_it) { + + // START position + if ( path_it != fill.begin() && + ! ((path_it == (fill.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 const m (sp_shape_marker_get_transform_at_start(path_it->front())); + item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv); + } + + // 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)); + item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv); + + ++curve_it1; + ++curve_it2; + } + } + + // END position + if ( path_it != (fill.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); + item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv ); + } + } + } + + // END marker + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( SPObject *marker_obj = shape->_marker[i] ) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = fill.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); + item_to_outline_add_marker( marker_obj, m, scale, ret_pathv ); + } + } + } + + return ret_pathv; +} + + + +// ========================= Stroke to Path ====================== // + +static +void item_to_paths_add_marker( SPItem *context, + SPObject *marker_object, + Geom::Affine marker_transform, + double linewidth, + bool start_marker, + Inkscape::XML::Node *g_repr, + bool legacy) +{ + auto doc = context->document; + auto marker = cast(marker_object); + SPItem* marker_item = sp_item_first_item_child(marker_object); + if (!marker_item) { + return; + } + + + // total marker transform + auto tr = marker->get_marker_transform(marker_transform, linewidth, start_marker); + tr = marker_item->transform * marker->c2p * tr; + + if (marker_item->getRepr()) { + Inkscape::XML::Node *m_repr = marker_item->getRepr()->duplicate(doc->getReprDoc()); + g_repr->addChildAtPos(m_repr, 0); + SPItem *marker_item = (SPItem *) doc->getObjectByRepr(m_repr); + marker_item->doWriteTransform(tr); + if (!legacy) { + item_to_paths(marker_item, legacy, context); + } + } +} + + +/* + * Find an outline that represents an item. + * If legacy, text will not be handled as it is not a shape. + * If a new item is created it is returned. + * If the input item is a group and that group contains a changed item, the group node is returned + * (marking a change). + * + * The return value is used externally to update a selection. It is nullptr if no change is made. + */ +Inkscape::XML::Node* +item_to_paths(SPItem *item, bool legacy, SPItem *context) +{ + char const *id = item->getAttribute("id"); + SPDocument *doc = item->document; + bool flatten = false; + // flatten all paths effects + auto lpeitem = cast(item); + if (lpeitem && lpeitem->hasPathEffect()) { + lpeitem->removeAllPathEffects(true); + SPObject *elemref = doc->getObjectById(id); + if (elemref && elemref != item) { + // If the LPE item is a shape, it is converted to a path + // so we need to reupdate the item + item = cast(elemref); + } + auto flat_item = cast(elemref); + if (!flat_item || !flat_item->hasPathEffect()) { + flatten = true; + } + } + // convert text/3dbox to path + if (is(item) || is(item) || is(item)) { + if (legacy) { + return nullptr; + } + + Inkscape::ObjectSet original_objects {doc}; // doc or desktop shouldn't be necessary + original_objects.add(item); + original_objects.toCurves(true); + SPItem * new_item = original_objects.singleItem(); + if (new_item && new_item != item) { + flatten = true; + item = new_item; + } else { + g_warning("item_to_paths: flattening text or 3D box failed."); + return nullptr; + } + } + // if group, recurse + auto group = cast(item); + if (group) { + if (legacy) { + return nullptr; + } + std::vector const item_list = group->item_list(); + bool did = false; + for (auto subitem : item_list) { + if (item_to_paths(subitem, legacy)) { + did = true; + } + } + if (did || flatten) { + // This indicates that at least one thing was changed inside the group. + return group->getRepr(); + } else { + return nullptr; + } + } + + auto shape = cast(item); + if (!shape) { + return nullptr; + } + + Geom::PathVector fill_path; + Geom::PathVector stroke_path; + bool status = item_find_paths(item, fill_path, stroke_path); + + if (!status) { + // Was not a well structured shape (or text). + return nullptr; + } + + // The styles ------------------------ + + // Copying stroke style to fill will fail for properties not defined by style attribute + // (i.e., properties defined in style sheet or by attributes). + SPStyle *style = item->style; + SPCSSAttr *ncss = sp_css_attr_from_style(style, SP_STYLE_FLAG_ALWAYS); + SPCSSAttr *ncsf = sp_css_attr_from_style(style, SP_STYLE_FLAG_ALWAYS); + + if (context) { + SPCSSAttr *ctxt_style = sp_css_attr_from_style(context->style, SP_STYLE_FLAG_ALWAYS); + + // TODO: browsers have different behaviours with context on markers + // we need to revisit in the future for best matching + // also dont know if opacity is or should be included in context + gchar const *s_val = sp_repr_css_property(ctxt_style, "stroke", nullptr); + gchar const *f_val = sp_repr_css_property(ctxt_style, "fill", nullptr); + if (style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE || + style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) + { + gchar const *fill_value = (style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) ? s_val : f_val; + sp_repr_css_set_property(ncss, "fill", fill_value); + sp_repr_css_set_property(ncsf, "fill", fill_value); + } + if (style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE || + style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) + { + gchar const *stroke_value = (style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) ? f_val : s_val; + sp_repr_css_set_property(ncss, "stroke", stroke_value); + sp_repr_css_set_property(ncsf, "stroke", stroke_value); + } + } + // Stroke + + gchar const *s_val = sp_repr_css_property(ncss, "stroke", nullptr); + gchar const *s_opac = sp_repr_css_property(ncss, "stroke-opacity", nullptr); + gchar const *f_val = sp_repr_css_property(ncss, "fill", nullptr); + gchar const *opacity = sp_repr_css_property(ncss, "opacity", nullptr); // Also for markers + gchar const *filter = sp_repr_css_property(ncss, "filter", nullptr); // Also for markers + + sp_repr_css_set_property(ncss, "stroke", "none"); + sp_repr_css_set_property(ncss, "stroke-width", nullptr); + sp_repr_css_set_property(ncss, "stroke-opacity", "1.0"); + sp_repr_css_set_property(ncss, "filter", nullptr); + sp_repr_css_set_property(ncss, "opacity", nullptr); + sp_repr_css_unset_property(ncss, "marker-start"); + sp_repr_css_unset_property(ncss, "marker-mid"); + sp_repr_css_unset_property(ncss, "marker-end"); + + // we change the stroke to fill on ncss to create the filled stroke + sp_repr_css_set_property(ncss, "fill", s_val); + if ( s_opac ) { + sp_repr_css_set_property(ncss, "fill-opacity", s_opac); + } else { + sp_repr_css_set_property(ncss, "fill-opacity", "1.0"); + } + + sp_repr_css_set_property(ncsf, "stroke", "none"); + sp_repr_css_set_property(ncsf, "stroke-width", nullptr); + sp_repr_css_set_property(ncsf, "stroke-opacity", "1.0"); + sp_repr_css_set_property(ncsf, "filter", nullptr); + sp_repr_css_set_property(ncsf, "opacity", nullptr); + sp_repr_css_unset_property(ncsf, "marker-start"); + sp_repr_css_unset_property(ncsf, "marker-mid"); + sp_repr_css_unset_property(ncsf, "marker-end"); + + // The object tree ------------------- + + // Remember the position of the item + gint pos = item->getRepr()->position(); + + // Remember parent + Inkscape::XML::Node *parent = item->getRepr()->parent(); + + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + // Create a group to put everything in. + Inkscape::XML::Node *g_repr = xml_doc->createElement("svg:g"); + + Inkscape::copy_object_properties(g_repr, item->getRepr()); + // drop copied style, children will be re-styled (stroke becomes fill) + g_repr->removeAttribute("style"); + + // Add the group to the parent, move to the saved position + parent->addChildAtPos(g_repr, pos); + + // The stroke ------------------------ + Inkscape::XML::Node *stroke = nullptr; + if (s_val && g_strcmp0(s_val,"none") != 0 && stroke_path.size() > 0) { + stroke = xml_doc->createElement("svg:path"); + sp_repr_css_change(stroke, ncss, "style"); + + stroke->setAttribute("d", sp_svg_write_path(stroke_path)); + } + sp_repr_css_attr_unref(ncss); + + // The fill -------------------------- + Inkscape::XML::Node *fill = nullptr; + if (f_val && g_strcmp0(f_val,"none") != 0 && !legacy) { + fill = xml_doc->createElement("svg:path"); + sp_repr_css_change(fill, ncsf, "style"); + + fill->setAttribute("d", sp_svg_write_path(fill_path)); + } + sp_repr_css_attr_unref(ncsf); + + // The markers ----------------------- + Inkscape::XML::Node *markers = nullptr; + + if (shape->hasMarkers()) { + auto linewidth = style->stroke_width.computed; + if (!legacy) { + markers = xml_doc->createElement("svg:g"); + g_repr->addChildAtPos(markers, pos); + } else { + markers = g_repr; + } + + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( SPObject *marker_obj = shape->_marker[i] ) { + Geom::Affine const m (sp_shape_marker_get_transform_at_start(fill_path.front().front())); + item_to_paths_add_marker(item, marker_obj, m, linewidth, true, markers, legacy); + } + } + + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + SPObject *midmarker_obj = shape->_marker[i]; + if (!midmarker_obj) continue; // TODO use auto below + for(Geom::PathVector::const_iterator path_it = fill_path.begin(); path_it != fill_path.end(); ++path_it) { + + // START position + if ( path_it != fill_path.begin() && + ! ((path_it == (fill_path.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 const m (sp_shape_marker_get_transform_at_start(path_it->front())); + item_to_paths_add_marker(item, midmarker_obj, m, linewidth, false, markers, legacy); + } + + // 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)); + item_to_paths_add_marker(item, midmarker_obj, m, linewidth, false, markers, legacy); + + ++curve_it1; + ++curve_it2; + } + } + + // END position + if ( path_it != (fill_path.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); + item_to_paths_add_marker(item, midmarker_obj, m, linewidth, false, markers, legacy); + } + } + } + + // END marker + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( SPObject *marker_obj = shape->_marker[i] ) { + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = fill_path.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); + item_to_paths_add_marker(item, marker_obj, m, linewidth, false, markers, legacy); + } + } + } + + gchar const *paint_order = sp_repr_css_property(ncss, "paint-order", nullptr); + SPIPaintOrder temp; + temp.read( paint_order ); + bool unique = false; + if ((!fill && !markers) || (!fill && !stroke) || (!markers && !stroke)) { + unique = true; + } + if (temp.layer[0] != SP_CSS_PAINT_ORDER_NORMAL && !legacy && !unique) { + + if (temp.layer[0] == SP_CSS_PAINT_ORDER_FILL) { + if (temp.layer[1] == SP_CSS_PAINT_ORDER_STROKE) { + if ( fill ) { + g_repr->appendChild(fill); + } + if ( stroke ) { + g_repr->appendChild(stroke); + } + if ( markers ) { + markers->setPosition(2); + } + } else { + if ( fill ) { + g_repr->appendChild(fill); + } + if ( markers ) { + markers->setPosition(1); + } + if ( stroke ) { + g_repr->appendChild(stroke); + } + } + } else if (temp.layer[0] == SP_CSS_PAINT_ORDER_STROKE) { + if (temp.layer[1] == SP_CSS_PAINT_ORDER_FILL) { + if ( stroke ) { + g_repr->appendChild(stroke); + } + if ( fill ) { + g_repr->appendChild(fill); + } + if ( markers ) { + markers->setPosition(2); + } + } else { + if ( stroke ) { + g_repr->appendChild(stroke); + } + if ( markers ) { + markers->setPosition(1); + } + if ( fill ) { + g_repr->appendChild(fill); + } + } + } else { + if (temp.layer[1] == SP_CSS_PAINT_ORDER_STROKE) { + if ( markers ) { + markers->setPosition(0); + } + if ( stroke ) { + g_repr->appendChild(stroke); + } + if ( fill ) { + g_repr->appendChild(fill); + } + } else { + if ( markers ) { + markers->setPosition(0); + } + if ( fill ) { + g_repr->appendChild(fill); + } + if ( stroke ) { + g_repr->appendChild(stroke); + } + } + } + + } else if (!unique) { + if ( fill ) { + g_repr->appendChild(fill); + } + if ( stroke ) { + g_repr->appendChild(stroke); + } + if ( markers ) { + markers->setPosition(2); + } + } + + bool did = false; + // only consider it a change if more than a fill is created. + if (stroke || markers) { + did = true; + } + + Inkscape::XML::Node *out = nullptr; + + if (!fill && !markers && did) { + out = stroke; + } else if (!fill && !stroke && did) { + out = markers; + } else if(did) { + out = g_repr; + } else { + parent->removeChild(g_repr); + Inkscape::GC::release(g_repr); + if (fill) { + // Copy the style, to preserve context-fill cascade + if (context) { + item->setAttribute("style", fill->attribute("style")); + } + Inkscape::GC::release(fill); + } + return (flatten ? item->getRepr() : nullptr); + } + + SPCSSAttr *r_style = sp_repr_css_attr_new(); + sp_repr_css_set_property(r_style, "opacity", opacity); + sp_repr_css_set_property(r_style, "filter", filter); + sp_repr_css_change(out, r_style, "style"); + + sp_repr_css_attr_unref(r_style); + if (unique && out != markers) { // markers are already a child of g_repr + g_assert(out != g_repr); + parent->addChild(out, g_repr); + parent->removeChild(g_repr); + Inkscape::GC::release(g_repr); + } + out->setAttribute("transform", item->getRepr()->attribute("transform")); + + // We're replacing item, delete it. + item->deleteObject(false); + + out->setAttribute("id",id); + Inkscape::GC::release(out); + + return out; +} + +/* + 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 : -- cgit v1.2.3