1 // SPDX-License-Identifier: GPL-2.0-or-later
2 
3 /** @file
4  *
5  * Two related object to path operations:
6  *
7  * 1. Find a path that includes fill, stroke, and markers. Useful for finding a visual bounding box.
8  * 2. Take a set of objects and find an identical visual representation using only paths.
9  *
10  * Copyright (C) 2020 Tavmjong Bah
11  * Copyright (C) 2018 Authors
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  *
15  * Code moved from splivarot.cpp
16  *
17  */
18 
19 #include "path-outline.h"
20 
21 #include <vector>
22 
23 #include "path-chemistry.h" // Should be moved to path directory
24 #include "message-stack.h"  // Should be removed.
25 #include "selection.h"
26 #include "style.h"
27 
28 #include "display/curve.h"  // Should be moved to path directory
29 
30 #include "helper/geom.h"    // pathv_to_linear_and_cubic()
31 
32 #include "livarot/LivarotDefs.h"
33 #include "livarot/Path.h"
34 #include "livarot/Shape.h"
35 
36 #include "object/sp-item.h"
37 #include "object/sp-marker.h"
38 #include "object/sp-shape.h"
39 #include "object/sp-text.h"
40 
41 #include "svg/svg.h"
42 
43 /**
44  * Given an item, find a path representing the fill and a path representing the stroke.
45  * Returns true if fill path found. Item may not have a stroke in which case stroke path is empty.
46  * bbox_only==true skips cleaning up the stroke path.
47  * Encapsulates use of livarot.
48  */
49 bool
item_find_paths(const SPItem * item,Geom::PathVector & fill,Geom::PathVector & stroke,bool bbox_only=false)50 item_find_paths(const SPItem *item, Geom::PathVector& fill, Geom::PathVector& stroke, bool bbox_only = false)
51 {
52     const SPShape *shape = dynamic_cast<const SPShape*>(item);
53     const SPText  *text  = dynamic_cast<const SPText*>(item);
54 
55     if (!shape && !text) {
56         return false;
57     }
58 
59     std::unique_ptr<SPCurve> curve;
60     if (shape) {
61         curve = SPCurve::copy(shape->curve());
62     } else if (text) {
63         curve = text->getNormalizedBpath();
64     } else {
65         std::cerr << "item_find_paths: item not shape or text!" << std::endl;
66         return false;
67     }
68 
69     if (!curve) {
70         std::cerr << "item_find_paths: no curve!" << std::endl;
71         return false;
72     }
73 
74     if (curve->get_pathvector().empty()) {
75         std::cerr << "item_find_paths: curve empty!" << std::endl;
76         return false;
77     }
78 
79     fill = curve->get_pathvector();
80 
81     if (!item->style) {
82         // Should never happen
83         std::cerr << "item_find_paths: item with no style!" << std::endl;
84         return false;
85     }
86 
87     if (item->style->stroke.isNone()) {
88         // No stroke, no chocolate!
89         return true;
90     }
91 
92     // Now that we have a valid curve with stroke, do offset. We use Livarot for this as
93     // lib2geom does not yet handle offsets correctly.
94 
95     // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for
96     // which the outline is created correctly.
97     Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( fill );
98 
99     SPStyle *style = item->style;
100 
101     double stroke_width = style->stroke_width.computed;
102     if (stroke_width < Geom::EPSILON) {
103         // https://bugs.launchpad.net/inkscape/+bug/1244861
104         stroke_width = Geom::EPSILON;
105     }
106     double miter = style->stroke_miterlimit.value * stroke_width;
107 
108     JoinType join;
109     switch (style->stroke_linejoin.computed) {
110         case SP_STROKE_LINEJOIN_MITER:
111             join = join_pointy;
112             break;
113         case SP_STROKE_LINEJOIN_ROUND:
114             join = join_round;
115             break;
116         default:
117             join = join_straight;
118             break;
119     }
120 
121     ButtType butt;
122     switch (style->stroke_linecap.computed) {
123         case SP_STROKE_LINECAP_SQUARE:
124             butt = butt_square;
125             break;
126         case SP_STROKE_LINECAP_ROUND:
127             butt = butt_round;
128             break;
129         default:
130             butt = butt_straight;
131             break;
132     }
133 
134     Path *origin = new Path; // Fill
135     Path *offset = new Path;
136 
137     Geom::Affine const transform(item->transform);
138     double const scale = transform.descrim();
139 
140     origin->LoadPathVector(pathv);
141     offset->SetBackData(false);
142 
143     if (!style->stroke_dasharray.values.empty()) {
144         // We have dashes!
145         origin->ConvertWithBackData(0.005); // Approximate by polyline
146         origin->DashPolylineFromStyle(style, scale, 0);
147         auto bounds = Geom::bounds_fast(pathv);
148         if (bounds) {
149             double size = Geom::L2(bounds->dimensions());
150             origin->Simplify(size * 0.000005); // Polylines to Beziers
151         }
152     }
153 
154     // Finally do offset!
155     origin->Outline(offset, 0.5 * stroke_width, join, butt, 0.5 * miter);
156 
157     if (bbox_only) {
158         stroke = offset->MakePathVector();
159     } else {
160         // Clean-up shape
161 
162         offset->ConvertWithBackData(1.0); // Approximate by polyline
163 
164         Shape *theShape  = new Shape;
165         offset->Fill(theShape, 0); // Convert polyline to shape, step 1.
166 
167         Shape *theOffset = new Shape;
168         theOffset->ConvertToShape(theShape, fill_positive); // Create an intersection free polygon (theOffset), step2.
169         theOffset->ConvertToForme(origin, 1, &offset); // Turn shape into contour (stored in origin).
170 
171         stroke = origin->MakePathVector(); // Note origin was replaced above by stroke!
172     }
173 
174     delete origin;
175     delete offset;
176 
177     // std::cout << "    fill:   " << sp_svg_write_path(fill)   << "  count: " << fill.curveCount() << std::endl;
178     // std::cout << "    stroke: " << sp_svg_write_path(stroke) << "  count: " << stroke.curveCount() << std::endl;
179     return true;
180 }
181 
182 
183 // ======================== Item to Outline ===================== //
184 
185 static
item_to_outline_add_marker_child(SPItem const * item,Geom::Affine marker_transform,Geom::PathVector * pathv_in)186 void item_to_outline_add_marker_child( SPItem const *item, Geom::Affine marker_transform, Geom::PathVector* pathv_in )
187 {
188     Geom::Affine tr(marker_transform);
189     tr = item->transform * tr;
190 
191     // note: a marker child item can be an item group!
192     if (SP_IS_GROUP(item)) {
193         // recurse through all childs:
194         for (auto& o: item->children) {
195             if ( SP_IS_ITEM(&o) ) {
196                 item_to_outline_add_marker_child(SP_ITEM(&o), tr, pathv_in);
197             }
198         }
199     } else {
200         Geom::PathVector* marker_pathv = item_to_outline(item);
201 
202         if (marker_pathv) {
203             for (const auto & j : *marker_pathv) {
204                 pathv_in->push_back(j * tr);
205             }
206             delete marker_pathv;
207         }
208     }
209 }
210 
211 static
item_to_outline_add_marker(SPObject const * marker_object,Geom::Affine marker_transform,Geom::Scale stroke_scale,Geom::PathVector * pathv_in)212 void item_to_outline_add_marker( SPObject const *marker_object, Geom::Affine marker_transform,
213                               Geom::Scale stroke_scale, Geom::PathVector* pathv_in )
214 {
215     SPMarker const * marker = SP_MARKER(marker_object);
216 
217     Geom::Affine tr(marker_transform);
218     if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
219         tr = stroke_scale * tr;
220     }
221     // total marker transform
222     tr = marker->c2p * tr;
223 
224     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)?
225     if (marker_item) {
226         item_to_outline_add_marker_child(marker_item, tr, pathv_in);
227     }
228 }
229 
230 
231 /**
232  *  Returns a pathvector that is the outline of the stroked item, with markers.
233  *  item must be an SPShape or an SPText.
234  *  The only current use of this function has exclude_markers true! (SPShape::either_bbox).
235  *  TODO: See if SPShape::either_bbox's union with markers is the same as one would get
236  *  with bbox_only false.
237  */
item_to_outline(SPItem const * item,bool exclude_markers)238 Geom::PathVector* item_to_outline(SPItem const *item, bool exclude_markers)
239 {
240     Geom::PathVector fill;   // Used for locating markers.
241     Geom::PathVector stroke; // Used for creating outline (and finding bbox).
242     item_find_paths(item, fill, stroke, true); // Skip cleaning up stroke shape.
243 
244     Geom::PathVector *ret_pathv = nullptr;
245 
246     if (fill.curveCount() == 0) {
247         std::cerr << "item_to_outline: fill path has no segments!" << std::endl;
248         return ret_pathv;
249     }
250 
251     if (stroke.size() > 0) {
252         ret_pathv = new Geom::PathVector(stroke);
253     } else {
254         // No stroke, use fill path.
255         ret_pathv = new Geom::PathVector(fill);
256     }
257 
258     if (exclude_markers) {
259         return ret_pathv;
260     }
261 
262     const SPShape *shape = dynamic_cast<const SPShape *>(item);
263     if (shape && shape->hasMarkers()) {
264 
265         SPStyle *style = shape->style;
266         Geom::Scale scale(style->stroke_width.computed);
267 
268         // START marker
269         for (int i = 0; i < 2; i++) {  // SP_MARKER_LOC and SP_MARKER_LOC_START
270             if ( SPObject *marker_obj = shape->_marker[i] ) {
271                 Geom::Affine const m (sp_shape_marker_get_transform_at_start(fill.front().front()));
272                 item_to_outline_add_marker( marker_obj, m, scale, ret_pathv );
273             }
274         }
275 
276         // MID marker
277         for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
278             SPObject *midmarker_obj = shape->_marker[i];
279             if (!midmarker_obj) continue;
280             for(Geom::PathVector::const_iterator path_it = fill.begin(); path_it != fill.end(); ++path_it) {
281 
282                 // START position
283                 if ( path_it != fill.begin() &&
284                      ! ((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
285                 {
286                     Geom::Affine const m (sp_shape_marker_get_transform_at_start(path_it->front()));
287                     item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv);
288                 }
289 
290                 // MID position
291                 if (path_it->size_default() > 1) {
292                     Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
293                     Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
294                     while (curve_it2 != path_it->end_default())
295                     {
296                         /* Put marker between curve_it1 and curve_it2.
297                          * Loop to end_default (so including closing segment), because when a path is closed,
298                          * there should be a midpoint marker between last segment and closing straight line segment
299                          */
300                         Geom::Affine const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2));
301                         item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv);
302 
303                         ++curve_it1;
304                         ++curve_it2;
305                     }
306                 }
307 
308                 // END position
309                 if ( path_it != (fill.end()-1) && !path_it->empty()) {
310                     Geom::Curve const &lastcurve = path_it->back_default();
311                     Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
312                     item_to_outline_add_marker( midmarker_obj, m, scale, ret_pathv );
313                 }
314             }
315         }
316 
317         // END marker
318         for (int i = 0; i < 4; i += 3) {  // SP_MARKER_LOC and SP_MARKER_LOC_END
319             if ( SPObject *marker_obj = shape->_marker[i] ) {
320                 /* Get reference to last curve in the path.
321                  * For moveto-only path, this returns the "closing line segment". */
322                 Geom::Path const &path_last = fill.back();
323                 unsigned int index = path_last.size_default();
324                 if (index > 0) {
325                     index--;
326                 }
327                 Geom::Curve const &lastcurve = path_last[index];
328 
329                 Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
330                 item_to_outline_add_marker( marker_obj, m, scale, ret_pathv );
331             }
332         }
333     }
334 
335     return ret_pathv;
336 }
337 
338 
339 
340 // ========================= Stroke to Path ====================== //
341 
342 static
item_to_paths_add_marker(SPItem * context,SPObject * marker_object,Geom::Affine marker_transform,Geom::Scale stroke_scale,Inkscape::XML::Node * g_repr,Inkscape::XML::Document * xml_doc,SPDocument * doc,bool legacy)343 void item_to_paths_add_marker( SPItem *context,
344                                SPObject *marker_object, Geom::Affine marker_transform,
345                                Geom::Scale stroke_scale,
346                                Inkscape::XML::Node *g_repr, Inkscape::XML::Document *xml_doc,
347                                SPDocument * doc, bool legacy)
348 {
349     SPMarker* marker = SP_MARKER (marker_object);
350     SPItem* marker_item = sp_item_first_item_child(marker_object);
351     if (!marker_item) {
352         return;
353     }
354 
355     Geom::Affine tr(marker_transform);
356 
357     if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
358         tr = stroke_scale * tr;
359     }
360     // total marker transform
361     tr = marker_item->transform * marker->c2p * tr;
362 
363     if (marker_item->getRepr()) {
364         Inkscape::XML::Node *m_repr = marker_item->getRepr()->duplicate(xml_doc);
365         g_repr->addChildAtPos(m_repr, 0);
366         SPItem *marker_item = (SPItem *) doc->getObjectByRepr(m_repr);
367         marker_item->doWriteTransform(tr);
368         if (!legacy) {
369             item_to_paths(marker_item, legacy, context);
370         }
371     }
372 }
373 
374 
375 /*
376  * Find an outline that represents an item.
377  * If not legacy, items are already converted to paths (see verbs.cpp).
378  * If legacy, text will not be handled as it is not a shape.
379  * If a new item is created it is returned.
380  * If the input item is a group and that group contains a changed item, the group node is returned
381  * (marking a change).
382  *
383  * The return value is only used externally to update a selection.
384  */
385 Inkscape::XML::Node*
item_to_paths(SPItem * item,bool legacy,SPItem * context)386 item_to_paths(SPItem *item, bool legacy, SPItem *context)
387 {
388     char const *id = item->getAttribute("id");
389     // flatten all paths effects
390     SPLPEItem *lpeitem = SP_LPE_ITEM(item);
391     if (lpeitem) {
392         SPDocument * document = item->document;
393         lpeitem->removeAllPathEffects(true);
394         SPObject *elemref = document->getObjectById(id);
395         if (elemref && elemref != item) {
396             // If the LPE item is a shape, it is converted to a path
397             // so we need to reupdate the item
398             item = dynamic_cast<SPItem *>(elemref);
399         }
400     }
401     // if group, recurse
402     SPGroup *group = dynamic_cast<SPGroup *>(item);
403     if (group) {
404         if (legacy) {
405             return nullptr;
406         }
407         std::vector<SPItem*> const item_list = sp_item_group_item_list(group);
408         bool did = false;
409         for (auto subitem : item_list) {
410             if (item_to_paths(subitem, legacy)) {
411                 did = true;
412             }
413         }
414         if (did) {
415             // This indicates that at least one thing was changed inside the group.
416             return group->getRepr();
417         } else {
418             return nullptr;
419         }
420     }
421 
422     // As written, only shapes are handled. We bail on text early.
423     SPShape* shape = dynamic_cast<SPShape *>(item);
424     if (!shape) {
425         return nullptr;
426     }
427 
428     Geom::PathVector fill_path;
429     Geom::PathVector stroke_path;
430     bool status = item_find_paths(item, fill_path, stroke_path);
431 
432     if (!status) {
433         // Was not a well structured shape (or text).
434         return nullptr;
435     }
436 
437     // The styles ------------------------
438 
439     // Copying stroke style to fill will fail for properties not defined by style attribute
440     // (i.e., properties defined in style sheet or by attributes).
441     SPStyle *style = item->style;
442     SPCSSAttr *ncss = sp_css_attr_from_style(style, SP_STYLE_FLAG_ALWAYS);
443     SPCSSAttr *ncsf = sp_css_attr_from_style(style, SP_STYLE_FLAG_ALWAYS);
444     if (context) {
445         SPStyle *context_style = context->style;
446         SPCSSAttr *ctxt_style = sp_css_attr_from_style(context_style, SP_STYLE_FLAG_ALWAYS);
447         // TODO: browsers has diferent behabiours with context on markers
448         // we need to revisit in the future for best matching
449         // also dont know if opacity is or want to be included in context
450         gchar const *s_val   = sp_repr_css_property(ctxt_style, "stroke", nullptr);
451         gchar const *f_val   = sp_repr_css_property(ctxt_style, "fill", nullptr);
452         if (style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ||
453             style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL)
454         {
455             gchar const *fill_value = (style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE) ? s_val : f_val;
456             sp_repr_css_set_property(ncss, "fill", fill_value);
457             sp_repr_css_set_property(ncsf, "fill", fill_value);
458         }
459         if (style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE ||
460             style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL)
461         {
462             gchar const *stroke_value = (style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL) ? f_val : s_val;
463             sp_repr_css_set_property(ncss, "stroke", stroke_value);
464             sp_repr_css_set_property(ncsf, "stroke", stroke_value);
465         }
466     }
467     // Stroke
468 
469     gchar const *s_val   = sp_repr_css_property(ncss, "stroke", nullptr);
470     gchar const *s_opac  = sp_repr_css_property(ncss, "stroke-opacity", nullptr);
471     gchar const *f_val   = sp_repr_css_property(ncss, "fill", nullptr);
472     gchar const *opacity = sp_repr_css_property(ncss, "opacity", nullptr);  // Also for markers
473     gchar const *filter  = sp_repr_css_property(ncss, "filter", nullptr);   // Also for markers
474 
475     sp_repr_css_set_property(ncss, "stroke", "none");
476     sp_repr_css_set_property(ncss, "stroke-width", nullptr);
477     sp_repr_css_set_property(ncss, "stroke-opacity", "1.0");
478     sp_repr_css_set_property(ncss, "filter", nullptr);
479     sp_repr_css_set_property(ncss, "opacity", nullptr);
480     sp_repr_css_unset_property(ncss, "marker-start");
481     sp_repr_css_unset_property(ncss, "marker-mid");
482     sp_repr_css_unset_property(ncss, "marker-end");
483 
484     // we change the stroke to fill on ncss for create the filled stroke
485     sp_repr_css_set_property(ncss, "fill", s_val);
486     if ( s_opac ) {
487         sp_repr_css_set_property(ncss, "fill-opacity", s_opac);
488     } else {
489         sp_repr_css_set_property(ncss, "fill-opacity", "1.0");
490     }
491 
492 
493     sp_repr_css_set_property(ncsf, "stroke", "none");
494     sp_repr_css_set_property(ncsf, "stroke-opacity", "1.0");
495     sp_repr_css_set_property(ncss, "stroke-width", nullptr);
496     sp_repr_css_set_property(ncsf, "filter", nullptr);
497     sp_repr_css_set_property(ncsf, "opacity", nullptr);
498     sp_repr_css_unset_property(ncsf, "marker-start");
499     sp_repr_css_unset_property(ncsf, "marker-mid");
500     sp_repr_css_unset_property(ncsf, "marker-end");
501 
502     // The object tree -------------------
503 
504     // Remember the position of the item
505     gint pos = item->getRepr()->position();
506 
507     // Remember parent
508     Inkscape::XML::Node *parent = item->getRepr()->parent();
509 
510     SPDocument * doc = item->document;
511     Inkscape::XML::Document *xml_doc = doc->getReprDoc();
512 
513     // Create a group to put everything in.
514     Inkscape::XML::Node *g_repr = xml_doc->createElement("svg:g");
515 
516     Inkscape::copy_object_properties(g_repr, item->getRepr());
517     // drop copied style, children will be re-styled (stroke becomes fill)
518     g_repr->removeAttribute("style");
519 
520     // Add the group to the parent, move to the saved position
521     parent->addChildAtPos(g_repr, pos);
522 
523     // The stroke ------------------------
524     Inkscape::XML::Node *stroke = nullptr;
525     if (s_val && g_strcmp0(s_val,"none") != 0 && stroke_path.size() > 0) {
526         stroke = xml_doc->createElement("svg:path");
527         sp_repr_css_change(stroke, ncss, "style");
528 
529         stroke->setAttribute("d", sp_svg_write_path(stroke_path));
530     }
531     sp_repr_css_attr_unref(ncss);
532 
533     // The fill --------------------------
534     Inkscape::XML::Node *fill = nullptr;
535     if (f_val && g_strcmp0(f_val,"none") != 0 && !legacy) {
536         fill = xml_doc->createElement("svg:path");
537         sp_repr_css_change(fill, ncsf, "style");
538 
539         fill->setAttribute("d", sp_svg_write_path(fill_path));
540     }
541     sp_repr_css_attr_unref(ncsf);
542 
543     // The markers -----------------------
544     Inkscape::XML::Node *markers = nullptr;
545     Geom::Scale scale(style->stroke_width.computed);
546 
547     if (shape->hasMarkers()) {
548         if (!legacy) {
549             markers = xml_doc->createElement("svg:g");
550             g_repr->addChildAtPos(markers, pos);
551         } else {
552             markers = g_repr;
553         }
554 
555         // START marker
556         for (int i = 0; i < 2; i++) {  // SP_MARKER_LOC and SP_MARKER_LOC_START
557             if ( SPObject *marker_obj = shape->_marker[i] ) {
558                 Geom::Affine const m (sp_shape_marker_get_transform_at_start(fill_path.front().front()));
559                 item_to_paths_add_marker( item, marker_obj, m, scale,
560                                           markers, xml_doc, doc, legacy);
561             }
562         }
563 
564         // MID marker
565         for (int i = 0; i < 3; i += 2) {  // SP_MARKER_LOC and SP_MARKER_LOC_MID
566             SPObject *midmarker_obj = shape->_marker[i];
567             if (!midmarker_obj) continue; // TODO use auto below
568             for(Geom::PathVector::const_iterator path_it = fill_path.begin(); path_it != fill_path.end(); ++path_it) {
569 
570                 // START position
571                 if ( path_it != fill_path.begin() &&
572                      ! ((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
573                 {
574                     Geom::Affine const m (sp_shape_marker_get_transform_at_start(path_it->front()));
575                     item_to_paths_add_marker( item, midmarker_obj, m, scale,
576                                               markers, xml_doc, doc, legacy);
577                 }
578 
579                 // MID position
580                 if (path_it->size_default() > 1) {
581                     Geom::Path::const_iterator curve_it1 = path_it->begin();      // incoming curve
582                     Geom::Path::const_iterator curve_it2 = ++(path_it->begin());  // outgoing curve
583                     while (curve_it2 != path_it->end_default()) {
584                         /* Put marker between curve_it1 and curve_it2.
585                          * Loop to end_default (so including closing segment), because when a path is closed,
586                          * there should be a midpoint marker between last segment and closing straight line segment
587                          */
588                         Geom::Affine const m (sp_shape_marker_get_transform(*curve_it1, *curve_it2));
589                         item_to_paths_add_marker( item, midmarker_obj, m, scale,
590                                                   markers, xml_doc, doc, legacy);
591 
592                         ++curve_it1;
593                         ++curve_it2;
594                     }
595                 }
596 
597                 // END position
598                 if ( path_it != (fill_path.end()-1) && !path_it->empty()) {
599                     Geom::Curve const &lastcurve = path_it->back_default();
600                     Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
601                     item_to_paths_add_marker( item, midmarker_obj, m, scale,
602                                               markers, xml_doc, doc, legacy);
603                 }
604             }
605         }
606 
607         // END marker
608         for (int i = 0; i < 4; i += 3) {  // SP_MARKER_LOC and SP_MARKER_LOC_END
609             if ( SPObject *marker_obj = shape->_marker[i] ) {
610                 /* Get reference to last curve in the path.
611                  * For moveto-only path, this returns the "closing line segment". */
612                 Geom::Path const &path_last = fill_path.back();
613                 unsigned int index = path_last.size_default();
614                 if (index > 0) {
615                     index--;
616                 }
617                 Geom::Curve const &lastcurve = path_last[index];
618 
619                 Geom::Affine const m = sp_shape_marker_get_transform_at_end(lastcurve);
620                 item_to_paths_add_marker( item, marker_obj, m, scale,
621                                           markers, xml_doc, doc, legacy);
622             }
623         }
624     }
625 
626     gchar const *paint_order = sp_repr_css_property(ncss, "paint-order", nullptr);
627     SPIPaintOrder temp;
628     temp.read( paint_order );
629     bool unique = false;
630     if ((!fill && !markers) || (!fill && !stroke) || (!markers && !stroke)) {
631         unique = true;
632     }
633     if (temp.layer[0] != SP_CSS_PAINT_ORDER_NORMAL && !legacy && !unique) {
634 
635         if (temp.layer[0] == SP_CSS_PAINT_ORDER_FILL) {
636             if (temp.layer[1] == SP_CSS_PAINT_ORDER_STROKE) {
637                 if ( fill ) {
638                     g_repr->appendChild(fill);
639                 }
640                 if ( stroke ) {
641                     g_repr->appendChild(stroke);
642                 }
643                 if ( markers ) {
644                     markers->setPosition(2);
645                 }
646             } else {
647                 if ( fill ) {
648                     g_repr->appendChild(fill);
649                 }
650                 if ( markers ) {
651                     markers->setPosition(1);
652                 }
653                 if ( stroke ) {
654                     g_repr->appendChild(stroke);
655                 }
656             }
657         } else if (temp.layer[0] == SP_CSS_PAINT_ORDER_STROKE) {
658             if (temp.layer[1] == SP_CSS_PAINT_ORDER_FILL) {
659                 if ( stroke ) {
660                     g_repr->appendChild(stroke);
661                 }
662                 if ( fill ) {
663                     g_repr->appendChild(fill);
664                 }
665                 if ( markers ) {
666                     markers->setPosition(2);
667                 }
668             } else {
669                 if ( stroke ) {
670                     g_repr->appendChild(stroke);
671                 }
672                 if ( markers ) {
673                     markers->setPosition(1);
674                 }
675                 if ( fill ) {
676                     g_repr->appendChild(fill);
677                 }
678             }
679         } else {
680             if (temp.layer[1] == SP_CSS_PAINT_ORDER_STROKE) {
681                 if ( markers ) {
682                     markers->setPosition(0);
683                 }
684                 if ( stroke ) {
685                     g_repr->appendChild(stroke);
686                 }
687                 if ( fill ) {
688                     g_repr->appendChild(fill);
689                 }
690             } else {
691                 if ( markers ) {
692                     markers->setPosition(0);
693                 }
694                 if ( fill ) {
695                     g_repr->appendChild(fill);
696                 }
697                 if ( stroke ) {
698                     g_repr->appendChild(stroke);
699                 }
700             }
701         }
702 
703     } else if (!unique) {
704         if ( fill ) {
705             g_repr->appendChild(fill);
706         }
707         if ( stroke ) {
708             g_repr->appendChild(stroke);
709         }
710         if ( markers ) {
711             markers->setPosition(2);
712         }
713     }
714 
715     bool did = false;
716     if( fill || stroke || markers ) {
717         did = true;
718     }
719 
720     Inkscape::XML::Node *out = nullptr;
721 
722     if (!fill && !markers && did) {
723         out = stroke;
724     } else if (!fill && !stroke  && did) {
725         out = markers;
726     } else if (!markers && !stroke  && did) {
727         out = fill;
728     } else if(did) {
729         out = g_repr;
730     }
731 
732     SPCSSAttr *r_style = sp_repr_css_attr_new();
733     sp_repr_css_set_property(r_style, "opacity", opacity);
734     sp_repr_css_set_property(r_style, "filter", filter);
735     sp_repr_css_change(out, r_style, "style");
736 
737     sp_repr_css_attr_unref(r_style);
738     if (unique) {
739         g_assert(out != g_repr);
740         parent->addChild(out, g_repr);
741         parent->removeChild(g_repr);
742     }
743     out->setAttribute("transform", item->getRepr()->attribute("transform"));
744     out->setAttribute("id",id);
745     Inkscape::GC::release(out);
746 
747     // We're replacing item, delete it.
748     item->deleteObject(false);
749 
750     return out;
751 }
752 
753 /*
754   Local Variables:
755   mode:c++
756   c-file-style:"stroustrup"
757   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
758   indent-tabs-mode:nil
759   fill-column:99
760   End:
761 */
762 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
763