1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * SVG <path> implementation
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   David Turner <novalis@gnu.org>
8  *   Abhishek Sharma
9  *   Johan Engelen
10  *
11  * Copyright (C) 2004 David Turner
12  * Copyright (C) 1999-2002 Lauris Kaplinski
13  * Copyright (C) 2000-2001 Ximian, Inc.
14  * Copyright (C) 1999-2012 Authors
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include <glibmm/i18n.h>
20 #include <glibmm/regex.h>
21 
22 #include "live_effects/effect.h"
23 #include "live_effects/lpeobject.h"
24 #include "live_effects/lpeobject-reference.h"
25 #include "sp-lpe-item.h"
26 
27 #include "display/curve.h"
28 #include <2geom/curves.h>
29 #include "helper/geom-curves.h"
30 
31 #include "svg/svg.h"
32 #include "xml/repr.h"
33 #include "attributes.h"
34 
35 #include "sp-path.h"
36 #include "sp-guide.h"
37 
38 #include "document.h"
39 #include "desktop.h"
40 
41 #include "desktop-style.h"
42 #include "ui/tools/tool-base.h"
43 #include "inkscape.h"
44 #include "style.h"
45 
46 #define noPATH_VERBOSE
47 
nodesInPath() const48 gint SPPath::nodesInPath() const
49 {
50     return _curve ? _curve->nodes_in_path() : 0;
51 }
52 
displayName() const53 const char* SPPath::displayName() const {
54     return _("Path");
55 }
56 
description() const57 gchar* SPPath::description() const {
58     int count = this->nodesInPath();
59     char *lpe_desc = g_strdup("");
60 
61     if (hasPathEffect()) {
62         Glib::ustring s;
63         PathEffectList effect_list =  this->getEffectList();
64 
65         for (auto & it : effect_list)
66         {
67             LivePathEffectObject *lpeobj = it->lpeobject;
68 
69             if (!lpeobj || !lpeobj->get_lpe()) {
70                 break;
71             }
72 
73             if (s.empty()) {
74                 s = lpeobj->get_lpe()->getName();
75             } else {
76                 s = s + ", " + lpeobj->get_lpe()->getName();
77             }
78         }
79         lpe_desc = g_strdup_printf(_(", path effect: %s"), s.c_str());
80     }
81     char *ret = g_strdup_printf(ngettext(
82                 _("%i node%s"), _("%i nodes%s"), count), count, lpe_desc);
83     g_free(lpe_desc);
84     return ret;
85 }
86 
convert_to_guides() const87 void SPPath::convert_to_guides() const {
88     if (!this->_curve) {
89         return;
90     }
91 
92     std::list<std::pair<Geom::Point, Geom::Point> > pts;
93 
94     Geom::Affine const i2dt(this->i2dt_affine());
95     Geom::PathVector const & pv = this->_curve->get_pathvector();
96 
97     for(const auto & pit : pv) {
98         for(Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_default(); ++cit) {
99             // only add curves for straight line segments
100             if( is_straight_curve(*cit) )
101             {
102                 pts.emplace_back(cit->initialPoint() * i2dt, cit->finalPoint() * i2dt);
103             }
104         }
105     }
106 
107     sp_guide_pt_pairs_to_guides(this->document, pts);
108 }
109 
SPPath()110 SPPath::SPPath() : SPShape(), connEndPair(this) {
111 }
112 
113 SPPath::~SPPath() = default;
114 
build(SPDocument * document,Inkscape::XML::Node * repr)115 void SPPath::build(SPDocument *document, Inkscape::XML::Node *repr) {
116     /* Are these calls actually necessary? */
117     this->readAttr(SPAttr::MARKER);
118     this->readAttr(SPAttr::MARKER_START);
119     this->readAttr(SPAttr::MARKER_MID);
120     this->readAttr(SPAttr::MARKER_END);
121 
122     sp_conn_end_pair_build(this);
123 
124     SPShape::build(document, repr);
125     // Our code depends on 'd' being an attribute (LPE's, etc.). To support 'd' as a property, we
126     // check it here (after the style property has been evaluated, this allows us to properly
127     // handled precedence of property vs attribute). If we read in a 'd' set by styling, convert it
128     // to an attribute. We'll convert it back on output.
129 
130     d_source = style->d.style_src;
131 
132     if (style->d.set &&
133 
134         (d_source == SPStyleSrc::STYLE_PROP || d_source == SPStyleSrc::STYLE_SHEET) ) {
135 
136         if (char const *d_val = style->d.value()) {
137             // Chrome shipped with a different syntax for property vs attribute.
138             // The SVG Working group decided to follow the Chrome syntax (which may
139             // allow future extensions of the 'd' property). The property syntax
140             // wraps the path data with "path(...)". We must strip that!
141 
142             // Must be Glib::ustring or we get conversion errors!
143             Glib::ustring input = d_val;
144             Glib::ustring expression = R"A(path\("(.*)"\))A";
145             Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create(expression);
146             Glib::MatchInfo matchInfo;
147             regex->match(input, matchInfo);
148 
149             if (matchInfo.matches()) {
150                 Glib::ustring  value = matchInfo.fetch(1);
151                 Geom::PathVector pv = sp_svg_read_pathv(value.c_str());
152 
153                 auto curve = std::make_unique<SPCurve>(pv);
154                 if (curve) {
155 
156                     // Update curve
157                     setCurveInsync(std::move(curve));
158 
159                     // Convert from property to attribute (convert back on write)
160                     setAttributeOrRemoveIfEmpty("d", value);
161 
162                     SPCSSAttr *css = sp_repr_css_attr( getRepr(), "style");
163                     sp_repr_css_unset_property ( css, "d");
164                     sp_repr_css_set ( getRepr(), css, "style" );
165                     sp_repr_css_attr_unref ( css );
166 
167                     style->d.style_src = SPStyleSrc::ATTRIBUTE;
168                 } else {
169                     std::cerr << "SPPath::build: Failed to create curve: " << input << std::endl;
170                 }
171             }
172         }
173         // If any if statement is false, do nothing... don't overwrite 'd' from attribute
174     }
175 
176 
177     // this->readAttr(SPAttr::INKSCAPE_ORIGINAL_D); // bug #1299948
178     // Why we take the long way of doing this probably needs some explaining:
179     //
180     // Normally upon being built, reading the inkscape:original-d attribute
181     // will cause the path to actually _write to its repr_ in response to this.
182     // This is bad, bad news if the attached effect refers to a path which
183     // hasn't been constructed yet.
184     //
185     // What will happen is the effect parameter will cause the effect to
186     // recalculate with a completely different value due to the parameter being
187     // "empty" -- even worse, an undo event might be created with the bad value,
188     // and undoing the current action could cause it to revert to the "bad"
189     // state. (After that, the referred object will be constructed and the
190     // reference will trigger the path effect to update and commit the right
191     // value to "d".)
192     //
193     // This mild nastiness here (don't recalculate effects on build) prevents a
194     // plethora of issues with effects with linked parameters doing wild and
195     // stupid things on new documents upon a mere undo.
196 
197     if (gchar const* s = this->getRepr()->attribute("inkscape:original-d"))
198     {
199         // Write the value to _curve_before_lpe, do not recalculate effects
200         Geom::PathVector pv = sp_svg_read_pathv(s);
201         _curve_before_lpe.reset(new SPCurve(pv));
202     }
203     this->readAttr(SPAttr::D);
204 
205     /* d is a required attribute */
206     char const *d = this->getAttribute("d", nullptr);
207 
208     if (d == nullptr) {
209         // First see if calculating the path effect will generate "d":
210         this->update_patheffect(true);
211         d = this->getAttribute("d", nullptr);
212 
213         // I guess that didn't work, now we have nothing useful to write ("")
214         if (d == nullptr) {
215             this->setKeyValue( sp_attribute_lookup("d"), "");
216         }
217     }
218 }
219 
release()220 void SPPath::release() {
221     this->connEndPair.release();
222 
223     SPShape::release();
224 }
225 
set(SPAttr key,const gchar * value)226 void SPPath::set(SPAttr key, const gchar* value) {
227     switch (key) {
228         case SPAttr::INKSCAPE_ORIGINAL_D:
229             if (value) {
230                 Geom::PathVector pv = sp_svg_read_pathv(value);
231                 setCurveBeforeLPE(std::make_unique<SPCurve>(pv));
232             } else {
233                 bool haslpe = this->hasPathEffectOnClipOrMaskRecursive(this);
234                 if (!haslpe) {
235                     this->setCurveBeforeLPE(nullptr);
236                 } else {
237                     //This happends on undo, fix bug:#1791784
238                     this->removeAllPathEffects(false);
239                 }
240             }
241             // In 2020-8-15 next line is commented and added
242             // a todo to see regressions, after this in a commit this line is uncomented
243             // again this line in a MR that near 1.1 release is finaly rollbacked.
244             // This rollback happends near release (1.1) and think is beter leave
245             // uncomented for release as all check are done this way
246             // TODO: Comment on 1.2 branching to see issues
247             sp_lpe_item_update_patheffect(this, true, true);
248             break;
249 
250        case SPAttr::D:
251             if (value) {
252                 Geom::PathVector pv = sp_svg_read_pathv(value);
253                 setCurve(std::make_unique<SPCurve>(pv));
254             } else {
255                 this->setCurve(nullptr);
256             }
257             break;
258 
259         case SPAttr::MARKER:
260             sp_shape_set_marker(this, SP_MARKER_LOC, value);
261             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
262             break;
263         case SPAttr::MARKER_START:
264             sp_shape_set_marker(this, SP_MARKER_LOC_START, value);
265             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
266             break;
267         case SPAttr::MARKER_MID:
268             sp_shape_set_marker(this, SP_MARKER_LOC_MID, value);
269             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
270             break;
271         case SPAttr::MARKER_END:
272             sp_shape_set_marker(this, SP_MARKER_LOC_END, value);
273             this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
274             break;
275 
276         case SPAttr::CONNECTOR_TYPE:
277         case SPAttr::CONNECTOR_CURVATURE:
278         case SPAttr::CONNECTION_START:
279         case SPAttr::CONNECTION_END:
280         case SPAttr::CONNECTION_START_POINT:
281         case SPAttr::CONNECTION_END_POINT:
282             this->connEndPair.setAttr(key, value);
283             break;
284 
285         default:
286             SPShape::set(key, value);
287             break;
288     }
289 }
290 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)291 Inkscape::XML::Node* SPPath::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
292     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
293         repr = xml_doc->createElement("svg:path");
294     }
295 
296 #ifdef PATH_VERBOSE
297 g_message("sp_path_write writes 'd' attribute");
298 #endif
299 
300     if (this->_curve) {
301         repr->setAttribute("d", sp_svg_write_path(this->_curve->get_pathvector()));
302     } else {
303         repr->removeAttribute("d");
304     }
305 
306     if (flags & SP_OBJECT_WRITE_EXT) {
307         if ( this->_curve_before_lpe != nullptr ) {
308             repr->setAttribute("inkscape:original-d", sp_svg_write_path(this->_curve_before_lpe->get_pathvector()));
309         } else {
310             repr->removeAttribute("inkscape:original-d");
311         }
312     }
313 
314     this->connEndPair.writeRepr(repr);
315 
316     SPShape::write(xml_doc, repr, flags);
317 
318     return repr;
319 }
320 
update_patheffect(bool write)321 void SPPath::update_patheffect(bool write) {
322     SPShape::update_patheffect(write);
323 }
324 
update(SPCtx * ctx,guint flags)325 void SPPath::update(SPCtx *ctx, guint flags) {
326     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
327         flags &= ~SP_OBJECT_USER_MODIFIED_FLAG_B; // since we change the description, it's not a "just translation" anymore
328     }
329 
330     SPShape::update(ctx, flags);
331     this->connEndPair.update();
332 }
333 
set_transform(Geom::Affine const & transform)334 Geom::Affine SPPath::set_transform(Geom::Affine const &transform) {
335     if (!_curve) { // 0 nodes, nothing to transform
336         return Geom::identity();
337     }
338     if (pathEffectsEnabled() && !optimizeTransforms()) {
339         return transform;
340     }
341     if (hasPathEffectRecursive() && pathEffectsEnabled()) {
342         if (!_curve_before_lpe) {
343             // we are inside a LPE group creating a new element
344             // and the original-d curve is not defined,
345             // This fix a issue with calligrapic tool that make a transform just when draw
346             setCurveBeforeLPE(std::move(_curve));
347         }
348         _curve_before_lpe->transform(transform);
349     } else {
350         _curve->transform(transform);
351     }
352     // Adjust stroke
353     this->adjust_stroke(transform.descrim());
354 
355     // Adjust pattern fill
356     this->adjust_pattern(transform);
357 
358     // Adjust gradient fill
359     this->adjust_gradient(transform);
360 
361     // nothing remains - we've written all of the transform, so return identity
362     return Geom::identity();
363 }
364 
365 /*
366   Local Variables:
367   mode:c++
368   c-file-style:"stroustrup"
369   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
370   indent-tabs-mode:nil
371   fill-column:99
372   End:
373 */
374 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
375