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