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