Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
freehand-base.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Generic drawing context
4 *
5 * Author:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Abhishek Sharma
8 * Jon A. Cruz <jon@joncruz.org>
9 *
10 * Copyright (C) 2000 Lauris Kaplinski
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 * Copyright (C) 2002 Lauris Kaplinski
13 * Copyright (C) 2012 Johan Engelen
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "freehand-base.h"
19
20#include "desktop-style.h"
21#include "id-clash.h"
22#include "message-stack.h"
23#include "selection.h"
24#include "selection-chemistry.h"
25#include "style.h"
26
27#include "display/curve.h"
29
33
35#include "object/sp-path.h"
36#include "object/sp-rect.h"
37#include "object/sp-use.h"
38
39#include "svg/svg.h"
40
41#include "ui/clipboard.h"
42#include "ui/draw-anchor.h"
43#include "ui/icon-names.h"
44#include "ui/tools/lpe-tool.h" // TODO: Remove in the future
45#include "ui/tools/pencil-tool.h" // TODO: Remove in the future
47
49
50namespace Inkscape::UI::Tools {
51
58static void spdc_flush_white(FreehandBase *dc, std::shared_ptr<SPCurve> gc);
59
60static void spdc_free_colors(FreehandBase *dc);
61
62FreehandBase::FreehandBase(SPDesktop *desktop, std::string &&prefs_path, std::string &&cursor_filename)
63 : ToolBase(desktop, std::move(prefs_path), std::move(cursor_filename))
64{
66
67 // Connect signals to track selection changes
70
71 // Create red bpath
72 red_bpath = make_canvasitem<CanvasItemBpath>(desktop->getCanvasSketch());
73 red_bpath->set_stroke(red_color);
74 red_bpath->set_fill(0x0, SP_WIND_RULE_NONZERO);
75
76 // Create blue bpath
77 blue_bpath = make_canvasitem<CanvasItemBpath>(desktop->getCanvasSketch());
78 blue_bpath->set_stroke(blue_color);
79 blue_bpath->set_fill(0x0, SP_WIND_RULE_NONZERO);
80
81 // Create green curve
82 green_curve = std::make_shared<SPCurve>();
83
84 // Create start anchor alternative curve
85 sa_overwrited = std::make_shared<SPCurve>();
86
88}
89
91{
92 sel_changed_connection.disconnect();
93 sel_modified_connection.disconnect();
94
96
97 if (selection) {
98 selection = nullptr;
99 }
100
101 spdc_free_colors(this);
102}
103
105{
106 bool ret = false;
107
108 inspect_event(event,
109 [&] (KeyPressEvent const &event) {
110 switch (get_latin_keyval(event)) {
111 case GDK_KEY_Up:
112 case GDK_KEY_Down:
113 case GDK_KEY_KP_Up:
114 case GDK_KEY_KP_Down:
115 // prevent the zoom field from activation
116 if (!mod_ctrl_only(event)) {
117 ret = true;
118 }
119 break;
120 default:
121 break;
122 }
123 },
124 [&] (CanvasEvent const &event) {}
125 );
126
127 return ret || ToolBase::root_handler(event);
128}
129
130std::optional<Geom::Point> FreehandBase::red_curve_get_last_point() const
131{
132 if (!red_curve.is_empty()) {
133 return red_curve.last_point();
134 }
135 return {};
136}
137
139{
140 using namespace Inkscape::LivePathEffect;
141
142 // TODO: Don't paste path if nothing is on the clipboard
143 SPDocument *document = dc->getDesktop()->doc();
144 Effect::createAndApply(PATTERN_ALONG_PATH, document, item);
145 Effect* lpe = cast<SPLPEItem>(item)->getCurrentLPE();
146 static_cast<LPEPatternAlongPath*>(lpe)->pattern.set_new_value(newpath,true);
148 double scale = prefs->getDouble("/live_effects/skeletal/width", 1);
149 if (!scale) {
150 scale = 1;
151 }
153 os << scale;
154 lpe->getRepr()->setAttribute("prop_scale", os.str());
155}
156
158{
160 if (obj->style) {
161 if (obj->style->stroke.isPaintserver()) {
162 SPPaintServer *server = obj->style->getStrokePaintServer();
163 if (server) {
164 Glib::ustring str;
165 str += "url(#";
166 str += server->getId();
167 str += ")";
168 sp_repr_css_set_property(css, "fill", str.c_str());
169 }
170 } else if (obj->style->stroke.isColor()) {
171 auto color = obj->style->stroke.getColor();
172 color.addOpacity(obj->style->stroke_opacity);
173 sp_repr_css_set_property_string(css, "fill", color.toString());
174 } else {
175 sp_repr_css_set_property(css, "fill", "none");
176 }
177 } else {
179 }
180
181 sp_repr_css_set_property(css, "fill-rule", "nonzero");
182 sp_repr_css_set_property(css, "stroke", "none");
183
186}
187
188static void spdc_apply_powerstroke_shape(std::vector<Geom::Point> const &points, FreehandBase *dc, SPItem *item)
189{
190 using namespace Inkscape::LivePathEffect;
192 SPDocument *document = desktop->getDocument();
193 if (!document || !desktop) {
194 return;
195 }
196 if (SP_IS_PENCIL_CONTEXT(dc)) {
197 if (dc->tablet_enabled) {
198 SPObject *elemref = nullptr;
199 if ((elemref = document->getObjectById("power_stroke_preview"))) {
200 elemref->getRepr()->removeAttribute("style");
201 auto successor = cast<SPItem>(elemref);
202 sp_desktop_apply_style_tool(desktop, successor->getRepr(),
203 Glib::ustring("/tools/freehand/pencil").data(), false);
204 spdc_apply_style(successor);
206 item->deleteObject(false);
207 item->setSuccessor(successor);
209 item = successor;
210 dc->selection->set(item);
211 item->setLocked(false);
212 dc->white_item = item;
213 rename_id(item, "path-1");
214 }
215 return;
216 }
217 }
218 Effect::createAndApply(POWERSTROKE, document, item);
219 Effect* lpe = cast<SPLPEItem>(item)->getCurrentLPE();
220
221 static_cast<LPEPowerStroke*>(lpe)->offset_points.param_set_and_write_new_value(points);
222
223 // write powerstroke parameters:
224 lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth");
225 lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth");
226 lpe->getRepr()->setAttribute("sort_points", "true");
227 lpe->getRepr()->setAttribute("not_jump", "false");
228 lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan");
229 lpe->getRepr()->setAttribute("interpolator_beta", "0.2");
230 lpe->getRepr()->setAttribute("miter_limit", "4");
231 lpe->getRepr()->setAttribute("scale_width", "1");
232 lpe->getRepr()->setAttribute("linejoin_type", "extrp_arc");
233}
234
235static void spdc_apply_bend_shape(gchar const *svgd, FreehandBase *dc, SPItem *item)
236{
237 using namespace Inkscape::LivePathEffect;
238 if (is<SPUse>(item)) {
239 return;
240 }
242 SPDocument *document = desktop->getDocument();
243 if (!document || !desktop) {
244 return;
245 }
246 auto lpeitem = cast<SPLPEItem>(item);
247 if (!lpeitem) {
248 return;
249 }
250 if (!lpeitem->hasPathEffectOfType(BEND_PATH)){
251 Effect::createAndApply(BEND_PATH, document, item);
252 }
253 auto lpe = lpeitem->getCurrentLPE();
254
255 // write bend parameters:
256 auto prefs = Preferences::get();
257 double scale = prefs->getDouble("/live_effects/bend_path/width", 1);
258 if (!scale) {
259 scale = 1;
260 }
262 os << scale;
263 lpe->getRepr()->setAttribute("prop_scale", os.str());
264 lpe->getRepr()->setAttribute("scale_y_rel", "false");
265 lpe->getRepr()->setAttribute("vertical", "false");
266 static_cast<LPEBendPath*>(lpe)->bend_path.paste_param_path(svgd);
267}
268
269static void spdc_apply_simplify(double threshold, FreehandBase *dc, SPItem *item)
270{
271 const SPDesktop *desktop = dc->getDesktop();
272 SPDocument *document = desktop->getDocument();
273 if (!document || !desktop) {
274 return;
275 }
276 using namespace Inkscape::LivePathEffect;
277
278 Effect::createAndApply(SIMPLIFY, document, item);
279 Effect* lpe = cast<SPLPEItem>(item)->getCurrentLPE();
280 // write simplify parameters:
281 lpe->getRepr()->setAttribute("steps", "1");
282 lpe->getRepr()->setAttributeOrRemoveIfEmpty("threshold", std::to_string(threshold).c_str());
283 lpe->getRepr()->setAttribute("smooth_angles", "360");
284 lpe->getRepr()->setAttribute("helper_size", "0");
285 lpe->getRepr()->setAttribute("simplify_individual_paths", "false");
286 lpe->getRepr()->setAttribute("simplify_just_coalesce", "false");
287}
288
290
292{
293 using namespace Inkscape::LivePathEffect;
294 auto prefs = Preferences::get();
295
296 auto desktop = dc->getDesktop();
297
298 if (is<SPLPEItem>(item)) {
299 double const defsize = 10 / (0.265 * dc->getDesktop()->getDocument()->getDocumentScale()[0]);
300 auto const SHAPE_LENGTH = defsize;
301 auto const SHAPE_HEIGHT = defsize;
302 //Store the clipboard path to apply in the future without the use of clipboard
303 static Geom::PathVector previous_shape_pathv;
304 static SPItem *bend_item = nullptr;
305 auto shape = static_cast<ShapeType>(prefs->getInt(dc->getPrefsPath() + "/shape", 0));
306 if (previous_shape_type == NONE) {
307 previous_shape_type = shape;
308 }
309 if(shape == LAST_APPLIED){
310 shape = previous_shape_type;
311 if(shape == CLIPBOARD || shape == BEND_CLIPBOARD){
312 shape = LAST_APPLIED;
313 }
314 }
316 if (is_bend &&
317 (shape == BEND_CLIPBOARD || (shape == LAST_APPLIED && previous_shape_type != CLIPBOARD)) &&
318 cm->paste(desktop, true))
319 {
320 bend_item = dc->selection->singleItem();
321 if(!bend_item || (!is<SPShape>(bend_item) && !is<SPGroup>(bend_item))){
323 return;
324 }
325 } else if(is_bend) {
326 return;
327 }
328 if (!is_bend && previous_shape_type == BEND_CLIPBOARD && shape == BEND_CLIPBOARD) {
329 return;
330 }
331 bool shape_applied = false;
332 bool simplify = prefs->getInt(dc->getPrefsPath() + "/simplify", 0);
334 guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
335 if(simplify && mode != 2){
336 double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0);
337 tol = tol/(100.0*(102.0-tol));
338 tol *= 10000;
339 spdc_apply_simplify(tol, dc, item);
340 sp_lpe_item_update_patheffect(cast<SPLPEItem>(item), true, false);
341 }
342 if (prefs->getInt(dc->getPrefsPath() + "/freehand-mode", 0) == 1) {
343 Effect::createAndApply(SPIRO, dc->getDesktop()->getDocument(), item);
344 }
345
346 if (prefs->getInt(dc->getPrefsPath() + "/freehand-mode", 0) == 2) {
347 Effect::createAndApply(BSPLINE, dc->getDesktop()->getDocument(), item);
348 }
349 if (auto sp_shape = cast<SPShape>(item)) {
350 curve = sp_shape->curve();
351 }
353 const char *cstroke = sp_repr_css_property(css_item, "stroke", "none");
354 const char *cfill = sp_repr_css_property(css_item, "fill", "none");
355 const char *stroke_width = sp_repr_css_property(css_item, "stroke-width", "0");
356 double swidth;
357 sp_svg_number_read_d(stroke_width, &swidth);
358 swidth = prefs->getDouble("/live_effects/powerstroke/width", SHAPE_HEIGHT / 2);
359 if (!swidth) {
360 swidth = swidth/2;
361 }
362 swidth = std::abs(swidth);
363 guint curve_length = curve->get_segment_count();
364 if (SP_IS_PENCIL_CONTEXT(dc)) {
365 if (dc->tablet_enabled) {
367 shape_applied = true;
368 shape = NONE;
370 }
371 }
372
373 switch (shape) {
374 case NONE:
375 // don't apply any shape
376 break;
377 case TRIANGLE_IN:
378 // "triangle in"
379 spdc_apply_powerstroke_shape({{0, swidth}}, dc, item);
380 shape_applied = false;
381 break;
382 case TRIANGLE_OUT:
383 // "triangle out"
384 spdc_apply_powerstroke_shape({{(double)curve_length, swidth}}, dc, item);
385 shape_applied = false;
386 break;
387 case ELLIPSE:
388 {
389 // "ellipse"
390 SPCurve c;
391 constexpr double C1 = 0.552;
392 c.moveto(0, SHAPE_HEIGHT/2);
393 c.curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0);
394 c.curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2);
395 c.curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT);
396 c.curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2);
397 c.closepath();
398 spdc_paste_curve_as_freehand_shape(c.get_pathvector(), dc, item);
399
400 shape_applied = true;
401 break;
402 }
403 case CLIPBOARD:
404 {
405 // take shape from clipboard;
407 if(cm->paste(desktop,true)){
408 dc->selection->toCurves(true);
409 if (auto pasted_clipboard = dc->selection->singleItem()){
410 Inkscape::XML::Node *pasted_clipboard_root = pasted_clipboard->getRepr();
411 Inkscape::XML::Node *path = sp_repr_lookup_name(pasted_clipboard_root, "svg:path", -1); // unlimited search depth
412 if ( path != nullptr ) {
413 gchar const *svgd = path->attribute("d");
414 dc->selection->remove(pasted_clipboard);
415 previous_shape_pathv = sp_svg_read_pathv(svgd);
416 previous_shape_pathv *= pasted_clipboard->transform;
417 spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item);
418
419 shape = CLIPBOARD;
420 shape_applied = true;
421 pasted_clipboard->deleteObject();
422 } else {
423 shape = NONE;
424 }
425 } else {
426 shape = NONE;
427 }
428 } else {
429 shape = NONE;
430 }
431 break;
432 }
433 case BEND_CLIPBOARD:
434 {
435 gchar const *svgd = item->getRepr()->attribute("d");
436 if(bend_item && (is<SPShape>(bend_item) || is<SPGroup>(bend_item))){
437 // If item is a SPRect, convert it to path first:
438 if (is<SPRect>(bend_item) ) {
439 if (desktop) {
441 if ( sel && !sel->isEmpty() ) {
442 sel->clear();
443 sel->add(bend_item);
444 sel->toCurves();
445 bend_item = sel->singleItem();
446 }
447 }
448 }
449 bend_item->moveTo(item,false);
451 spdc_apply_bend_shape(svgd, dc, bend_item);
452 dc->selection->add(bend_item);
453
454 shape = BEND_CLIPBOARD;
455 } else {
456 bend_item = nullptr;
457 shape = NONE;
458 }
459 break;
460 }
461 case LAST_APPLIED:
462 {
464 if(previous_shape_pathv.size() != 0){
465 spdc_paste_curve_as_freehand_shape(previous_shape_pathv, dc, item);
466 shape_applied = true;
467 shape = CLIPBOARD;
468 } else{
469 shape = NONE;
470 }
471 } else {
472 if(bend_item != nullptr && bend_item->getRepr() != nullptr){
473 gchar const *svgd = item->getRepr()->attribute("d");
474 dc->selection->add(bend_item);
475 dc->selection->duplicate();
476 dc->selection->remove(bend_item);
477 bend_item = dc->selection->singleItem();
478 if(bend_item){
479 bend_item->moveTo(item,false);
480 Geom::Coord expansion_X = bend_item->transform.expansionX();
481 Geom::Coord expansion_Y = bend_item->transform.expansionY();
482 bend_item->transform = Geom::Affine(1,0,0,1,0,0);
483 bend_item->transform.setExpansionX(expansion_X);
484 bend_item->transform.setExpansionY(expansion_Y);
485 spdc_apply_bend_shape(svgd, dc, bend_item);
486 dc->selection->add(bend_item);
487
488 shape = BEND_CLIPBOARD;
489 } else {
490 shape = NONE;
491 }
492 } else {
493 shape = NONE;
494 }
495 }
496 break;
497 }
498 default:
499 break;
500 }
501 previous_shape_type = shape;
502
503 if (shape_applied) {
504 // apply original stroke color as fill and unset stroke; then return
506 if (!strcmp(cfill, "none")) {
507 sp_repr_css_set_property (css, "fill", cstroke);
508 } else {
509 sp_repr_css_set_property (css, "fill", cfill);
510 }
511 sp_repr_css_set_property (css, "stroke", "none");
514 return;
515 }
516 if (dc->waiting_LPE_type != INVALID_LPE) {
517 Effect::createAndApply(dc->waiting_LPE_type, dc->getDesktop()->getDocument(), item);
518 dc->waiting_LPE_type = INVALID_LPE;
519
520 if (auto lc = SP_LPETOOL_CONTEXT(dc)) {
521 // since a geometric LPE was applied, we switch back to "inactive" mode
522 lc->switch_mode(INVALID_LPE);
523 }
524 }
525 if (SP_IS_PEN_CONTEXT(dc)) {
526 SP_PEN_CONTEXT(dc)->setPolylineMode();
527 }
528 }
529}
530
531/*
532 * Selection handlers
533 */
534
535/* fixme: We have to ensure this is not delayed (Lauris) */
540
542{
543 // We reset white and forget white/start/end anchors
544 white_curves.clear();
545 white_anchors.clear();
546 white_item = nullptr;
547 sa = nullptr;
548 ea = nullptr;
549
550 SPItem *item = selection ? selection->singleItem() : nullptr;
551
552 if (is<SPPath>(item)) {
553 // Create new white data
554 // Item
556
557 // Curve list
558 // We keep it in desktop coordinates to eliminate calculation errors
559 auto path = static_cast<SPPath *>(item);
560 if (!path->curveForEdit()) {
561 return;
562 }
563
564 auto tmp = path->curveForEdit()->transformed(white_item->i2dt_affine()).split();
565 white_curves.clear();
566 white_curves.reserve(tmp.size());
567 for (auto &t : tmp) {
568 white_curves.emplace_back(std::make_shared<SPCurve>(std::move(t)));
569 }
570
571 // Anchor list
572 for (auto const &c : white_curves) {
573 g_return_if_fail( c->get_segment_count() > 0 );
574 if ( !c->is_closed() ) {
575 white_anchors.emplace_back(std::make_unique<SPDrawAnchor>(this, c, true , *c->first_point()));
576 white_anchors.emplace_back(std::make_unique<SPDrawAnchor>(this, c, false, *c->last_point()));
577 }
578 }
579 // fixme: recalculate active anchor?
580 }
581}
582
583void spdc_endpoint_snap_rotation(ToolBase *tool, Geom::Point &p, Geom::Point const &o, unsigned state)
584{
585 auto prefs = Preferences::get();
586 unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12));
587
589 m.setup(tool->getDesktop());
590
591 bool snap_enabled = m.snapprefs.getSnapEnabledGlobally();
592 if (state & GDK_SHIFT_MASK) {
593 // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular
594 // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable
595 // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round
596 // to the nearest angle increment)
598 }
599
601 p = dummy.getPoint();
602
603 if (state & GDK_SHIFT_MASK) {
604 m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting
605 }
606
607 m.unSetup();
608}
609
610void spdc_endpoint_snap_free(ToolBase *tool, Geom::Point &p, std::optional<Geom::Point> &start_of_line)
611{
612 const SPDesktop *dt = tool->getDesktop();
614 Inkscape::Selection *selection = dt->getSelection();
615
616 // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping)
617 // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment
618
619 m.setup(dt, true, selection->singleItem());
621 if (start_of_line) {
622 scp.addOrigin(*start_of_line);
623 }
624
626 p = sp.getPoint();
627
628 m.unSetup();
629}
630
632{
633 // Concat RBG
635
636 // Green
637 auto c = std::make_shared<SPCurve>();
638 std::swap(c, dc->green_curve);
639 dc->green_bpaths.clear();
640
641 // Blue
642 c->append_continuous(std::move(dc->blue_curve));
643 dc->blue_curve.reset();
644 dc->blue_bpath->set_bpath(nullptr);
645
646 // Red
647 if (dc->red_curve_is_valid) {
648 c->append_continuous(dc->red_curve);
649 }
650 dc->red_curve.reset();
651 dc->red_bpath->set_bpath(nullptr);
652
653 if (c->is_empty()) {
654 return;
655 }
656
657 // Step A - test, whether we ended on green anchor
658 if ( (forceclosed &&
659 (!dc->sa || (dc->sa && dc->sa->curve->is_empty()))) ||
660 ( dc->green_anchor && dc->green_anchor->active))
661 {
662 // We hit green anchor, closing Green-Blue-Red
663 dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
664 c->closepath_current();
665 // Closed path, just flush
666 spdc_flush_white(dc, std::move(c));
667 return;
668 }
669
670 // Step B - both start and end anchored to same curve
671 if ( dc->sa && dc->ea
672 && ( dc->sa->curve == dc->ea->curve )
673 && ( ( dc->sa != dc->ea )
674 || dc->sa->curve->is_closed() ) )
675 {
676 // We hit bot start and end of single curve, closing paths
677 dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path."));
678 dc->sa_overwrited->append_continuous(*c);
679 dc->sa_overwrited->closepath_current();
680 if (!dc->white_curves.empty()) {
681 dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), dc->sa->curve));
682 }
683 dc->white_curves.push_back(std::move(dc->sa_overwrited));
684 spdc_flush_white(dc, nullptr);
685 return;
686 }
687 // Step C - test start
688 if (dc->sa) {
689 if (!dc->white_curves.empty()) {
690 dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), dc->sa->curve));
691 }
692 dc->sa_overwrited->append_continuous(*c);
693 c = std::move(dc->sa_overwrited);
694 } else /* Step D - test end */ if (dc->ea) {
695 auto e = std::move(dc->ea->curve);
696 if (!dc->white_curves.empty()) {
697 dc->white_curves.erase(std::find(dc->white_curves.begin(),dc->white_curves.end(), e));
698 }
699 if (!dc->ea->start) {
700 e = std::make_shared<SPCurve>(e->reversed());
701 }
702 if(prefs->getInt(dc->getPrefsPath() + "/freehand-mode", 0) == 1 ||
703 prefs->getInt(dc->getPrefsPath() + "/freehand-mode", 0) == 2)
704 {
705 e = std::make_shared<SPCurve>(e->reversed());
706 Geom::CubicBezier const * cubic = dynamic_cast<Geom::CubicBezier const*>(&*e->last_segment());
707 if(cubic){
708 auto lastSeg = std::make_shared<SPCurve>();
709 lastSeg->moveto((*cubic)[0]);
710 lastSeg->curveto((*cubic)[1],(*cubic)[3],(*cubic)[3]);
711 if ( e->get_segment_count() == 1) {
712 e = std::move(lastSeg);
713 } else {
714 //we eliminate the last segment
715 e->backspace();
716 //and we add it again with the recreation
717 e->append_continuous(*lastSeg);
718 }
719 }
720 e = std::make_shared<SPCurve>(e->reversed());
721 }
722 c->append_continuous(*e);
723 }
724 if (forceclosed)
725 {
726 dc->getDesktop()->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed."));
727 c->closepath_current();
728 }
729 spdc_flush_white(dc, std::move(c));
730}
731
732static void spdc_flush_white(FreehandBase *dc, std::shared_ptr<SPCurve> gc)
733{
734 std::shared_ptr<SPCurve> c;
735
736 if (!dc->white_curves.empty()) {
737 g_assert(dc->white_item);
738
739 c = std::make_shared<SPCurve>();
740 for (auto const &wc : dc->white_curves) {
741 c->append(*wc);
742 }
743
744 dc->white_curves.clear();
745 if (gc) {
746 c->append(*gc);
747 }
748 } else if (gc) {
749 c = std::move(gc);
750 } else {
751 return;
752 }
753
756 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
757
758 // Now we have to go back to item coordinates at last
759 c->transform( dc->white_item
760 ? (dc->white_item)->dt2i_affine()
761 : desktop->dt2doc() );
762
763 if (!c->is_empty()) {
764 // We actually have something to write
765
766 bool has_lpe = false;
768
769 if (dc->white_item) {
770 repr = dc->white_item->getRepr();
771 has_lpe = cast<SPLPEItem>(dc->white_item)->hasPathEffectRecursive();
772 } else {
773 repr = xml_doc->createElement("svg:path");
774 // Set style
776 }
777
778 auto str = sp_svg_write_path(c->get_pathvector());
779 if (has_lpe)
780 repr->setAttribute("inkscape:original-d", str);
781 else
782 repr->setAttribute("d", str);
783
784 auto layer = dc->currentLayer();
785 if (SP_IS_PENCIL_CONTEXT(dc) && dc->tablet_enabled) {
786 if (!dc->white_item) {
787 dc->white_item = cast<SPItem>(layer->appendChildRepr(repr));
788 }
790 }
791 if (!dc->white_item) {
792 // Attach repr
793 auto item = cast<SPItem>(layer->appendChildRepr(repr));
794 dc->white_item = item;
795 //Bend needs the transforms applied after, Other effects best before
798 item->transform = layer->i2doc_affine().inverse();
799 item->updateRepr();
800 item->doWriteTransform(item->transform, nullptr, true);
803 repr->parent()->removeChild(repr);
804 dc->white_item = nullptr;
805 } else {
806 dc->selection->set(repr);
807 }
808 }
809 auto lpeitem = cast<SPLPEItem>(dc->white_item);
810 if (lpeitem && lpeitem->hasPathEffectRecursive()) {
811 sp_lpe_item_update_patheffect(lpeitem, true, false);
812 }
813 DocumentUndo::done(doc, _("Draw path"), SP_IS_PEN_CONTEXT(dc)? INKSCAPE_ICON("draw-path") : INKSCAPE_ICON("draw-freehand"));
814
815 // When quickly drawing several subpaths with Shift, the next subpath may be finished and
816 // flushed before the selection_modified signal is fired by the previous change, which
817 // results in the tool losing all of the selected path's curve except that last subpath. To
818 // fix this, we force the selection_modified callback now, to make sure the tool's curve is
819 // in sync immediately.
821 }
822
823 // Flush pending updates
824 doc->ensureUpToDate();
825}
826
828{
829 SPDrawAnchor *active = nullptr;
830
831 // Test green anchor
832 if (dc->green_anchor) {
833 active = dc->green_anchor->anchorTest(p, true);
834 }
835
836 for (auto &i : dc->white_anchors) {
837 auto na = i->anchorTest(p, !active);
838 if (!active && na) {
839 active = na;
840 }
841 }
842
843 return active;
844}
845
847{
848 // Red
849 dc->red_bpath.reset();
850
851 // Blue
852 dc->blue_bpath.reset();
853 dc->blue_curve.reset();
854
855 // Overwrite start anchor curve
856 dc->sa_overwrited.reset();
857 // Green
858 dc->green_bpaths.clear();
859 dc->green_curve.reset();
860 dc->green_anchor.reset();
861
862 // White
863 if (dc->white_item) {
864 // We do not hold refcount
865 dc->white_item = nullptr;
866 }
867 dc->white_curves.clear();
868 dc->white_anchors.clear();
869}
870
871void spdc_create_single_dot(ToolBase *tool, Geom::Point const &pt, char const *path, unsigned event_state)
872{
873 g_return_if_fail(!strcmp(path, "/tools/freehand/pen") || !strcmp(path, "/tools/freehand/pencil")
874 || !strcmp(path, "/tools/calligraphic") );
875 Glib::ustring tool_path = path;
876
877 SPDesktop *desktop = tool->getDesktop();
879 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
880 repr->setAttribute("sodipodi:type", "arc");
881 auto layer = tool->currentLayer();
882 auto item = cast<SPItem>(layer->appendChildRepr(repr));
883 item->transform = layer->i2doc_affine().inverse();
885
886 // apply the tool's current style
887 sp_desktop_apply_style_tool(desktop, repr, path, false);
888
889 // find out stroke width (TODO: is there an easier way??)
890 double stroke_width = 3.0;
891 gchar const *style_str = repr->attribute("style");
892 if (style_str) {
893 SPStyle style(desktop->doc());
894 style.mergeString(style_str);
895 stroke_width = style.stroke_width.computed;
896 }
897
898 // unset stroke and set fill color to former stroke color
899 bool cali = strcmp(path, "/tools/calligraphic");
900 auto fill = sp_desktop_get_color_tool(desktop, path, cali);
901 auto stroke = sp_desktop_get_color_tool(desktop, path, false);
902
904 sp_repr_css_set_property_string(css, "fill", fill ? fill->toString() : "none");
905 sp_repr_css_set_property_string(css, "stroke", !cali && stroke ? stroke->toString() : "none");
906 sp_repr_css_set(repr, css, "style");
908
909 // put the circle where the mouse click occurred and set the diameter to the
910 // current stroke width, multiplied by the amount specified in the preferences
911 auto prefs = Preferences::get();
912
913 Geom::Affine const i2d (item->i2dt_affine ());
914 Geom::Point pp = pt * i2d.inverse();
915
916 double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0);
917 if (!strcmp(path, "/tools/calligraphic"))
918 rad = 0.0333 * prefs->getDouble(tool_path + "/width", 3.0) / desktop->current_zoom() / desktop->getDocument()->getDocumentScale()[Geom::X];
919 if (event_state & GDK_ALT_MASK) {
920 // TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size
921 // as specified in prefs. Very simple, but it might be sufficient in practice. If not,
922 // we need to devise something more sophisticated.
923 double s = g_random_double_range(-0.5, 0.5);
924 rad *= (1 + s);
925 }
926 if (event_state & GDK_SHIFT_MASK) {
927 // double the point size
928 rad *= 2;
929 }
930
931 repr->setAttributeSvgDouble("sodipodi:cx", pp[Geom::X]);
932 repr->setAttributeSvgDouble("sodipodi:cy", pp[Geom::Y]);
933 repr->setAttributeSvgDouble("sodipodi:rx", rad * stroke_width);
934 repr->setAttributeSvgDouble("sodipodi:ry", rad * stroke_width);
935 item->updateRepr();
936 item->doWriteTransform(item->transform, nullptr, true);
937
939
940 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot"));
941 DocumentUndo::done(desktop->getDocument(), _("Create single dot"), "");
942}
943
944} // namespace Inkscape::UI::Tools
945
946/*
947 Local Variables:
948 mode:c++
949 c-file-style:"stroustrup"
950 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
951 indent-tabs-mode:nil
952 fill-column:99
953 End:
954*/
955// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
3x3 matrix representing an affine transformation.
Definition affine.h:70
void setTranslation(Point const &loc)
Sets the translation imparted by the Affine.
Definition affine.cpp:56
Coord expansionX() const
Calculates the amount of x-scaling imparted by the Affine.
Definition affine.cpp:64
void setExpansionX(Coord val)
Definition affine.cpp:75
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
void setExpansionY(Coord val)
Definition affine.cpp:85
Coord expansionY() const
Calculates the amount of y-scaling imparted by the Affine.
Definition affine.cpp:71
Bezier curve with compile-time specified order.
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
Two-dimensional point that doubles as a vector.
Definition point.h:66
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Inkscape::XML::Node * getRepr()
Definition effect.cpp:1934
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void duplicate(bool suppressDone=false, bool duplicateLayer=false)
void clear()
Unselects all selected objects.
bool isEmpty()
Returns true if no items are selected.
void toCurves(bool skip_undo=false, bool clonesjustunlink=false)
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
Definition preferences.h:66
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
std::string str() const
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
void remove(XML::Node *repr)
Removes an item from the set of selected objects.
Definition selection.h:131
sigc::connection connectChanged(sigc::slot< void(Selection *)> slot)
Connects a slot to be notified of selection changes.
Definition selection.h:179
sigc::connection connectModified(sigc::slot< void(Selection *, unsigned)> slot)
Connects a slot to be notified of selected object modifications.
Definition selection.h:232
Class to store data for points which are snap candidates, either as a source or as a target.
void addOrigin(Geom::Point pt)
void setSnapEnabledGlobally(bool enabled)
Class describing the result of an attempt to snap.
Geom::Point getPoint() const
System-wide clipboard manager.
Definition clipboard.h:44
static ClipboardManager * get()
virtual bool paste(SPDesktop *desktop, bool in_place=false, bool on_page=false)=0
std::shared_ptr< SPCurve > sa_overwrited
LivePathEffect::EffectType waiting_LPE_type
std::vector< std::shared_ptr< SPCurve > > white_curves
sigc::connection sel_changed_connection
CanvasItemPtr< CanvasItemBpath > red_bpath
bool root_handler(CanvasEvent const &event) override
std::unique_ptr< SPDrawAnchor > green_anchor
std::vector< std::unique_ptr< SPDrawAnchor > > white_anchors
FreehandBase(SPDesktop *desktop, std::string &&prefs_path, std::string &&cursor_filename)
std::optional< Geom::Point > red_curve_get_last_point() const
CanvasItemPtr< CanvasItemBpath > blue_bpath
std::shared_ptr< SPCurve > green_curve
std::vector< CanvasItemPtr< CanvasItemBpath > > green_bpaths
sigc::connection sel_modified_connection
Base class for Event processors.
Definition tool-base.h:107
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
SPGroup * currentLayer() const
std::string const & getPrefsPath() const
Definition tool-base.h:120
SPDesktop * getDesktop() const
Definition tool-base.h:125
virtual bool root_handler(CanvasEvent const &event)
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual void removeChild(Node *child)=0
Remove a child of this node.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Wrapper around a Geom::PathVector object.
Definition curve.h:26
void moveto(Geom::Point const &p)
Perform a moveto to a point, thus starting a new subpath.
Definition curve.cpp:138
void reset()
Set curve to empty curve.
Definition curve.cpp:118
bool is_empty() const
True if no paths are in curve.
Definition curve.cpp:237
SPCurve transformed(Geom::Affine const &m) const
Return a copy of the curve with all paths transformed by matrix.
Definition curve.cpp:109
std::optional< Geom::Point > last_point() const
Return last point of last subpath or nothing when the curve is empty.
Definition curve.cpp:399
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::CanvasItemGroup * getCanvasSketch() const
Definition desktop.h:201
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Geom::Affine const & dt2doc() const
Definition desktop.cpp:1343
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SPDocument * doc() const
Definition desktop.h:159
Typed SVG document implementation.
Definition document.h:103
SPObject * getObjectById(std::string const &id) const
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:773
The drawing anchor.
Definition draw-anchor.h:42
std::shared_ptr< SPCurve > curve
Definition draw-anchor.h:46
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1821
void moveTo(SPItem *target, bool intoafter)
Move this SPItem into or after another SPItem in the doc.
Definition sp-item.cpp:478
Geom::Affine transform
Definition sp-item.h:138
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
void setLocked(bool lock)
Definition sp-item.cpp:228
Geom::Affine dt2i_affine() const
Returns the transformation from desktop to item coords.
Definition sp-item.cpp:1841
SnapManager snap_manager
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * getId() const
Returns the objects current ID string.
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
void setSuccessor(SPObject *successor)
Indicates that another object supercedes this one.
Definition sp-object.h:564
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
SVG <path> implementation.
Definition sp-path.h:29
SPCurve const * curveForEdit() const
Return a borrowed pointer of the curve for edit.
Definition sp-shape.cpp:986
An SVG style object.
Definition style.h:45
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPPaintServer * getStrokePaintServer()
Definition style.h:343
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
T< SPAttr::STROKE_OPACITY, SPIScale24 > stroke_opacity
stroke-opacity
Definition style.h:261
void mergeString(char const *p)
Parses a style="..." string and merges it with an existing SPStyle.
Definition style.cpp:854
Class to coordinate snapping operations.
Definition snap.h:80
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
Inkscape::SnappedPoint constrainedAngularSnap(Inkscape::SnapCandidatePoint const &p, std::optional< Geom::Point > const &p_ref, Geom::Point const &o, unsigned const snaps) const
Try to snap a point to something at a specific angle.
Definition snap.cpp:372
void unSetup()
Definition snap.h:147
Inkscape::SnapPreferences & snapprefs
Definition snap.h:342
Inkscape::SnappedPoint freeSnap(Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap=Geom::OptRect(), bool to_path_only=false) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:143
System-wide clipboard management - class declaration.
std::shared_ptr< Css const > css
double c[8][4]
void sp_desktop_apply_style_tool(SPDesktop *desktop, Inkscape::XML::Node *repr, Glib::ustring const &tool_path, bool with_text)
Apply the desktop's current style or the tool style to repr.
std::optional< Color > sp_desktop_get_color_tool(SPDesktop *desktop, Glib::ustring const &tool, bool is_fill)
void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines)
Apply style on object and children, recursively.
The nodes at the ends of the path in the pen/pencil tools.
static Glib::ustring const prefs_path
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
void rename_id(SPObject *elem, Glib::ustring const &new_name)
Definition id-clash.cpp:571
TODO: insert short description here.
SPItem * item
PowerStroke LPE effect, see lpe-powerstroke.cpp.
LPETool: a generic tool composed of subtools that are given by LPEs.
auto SP_LPETOOL_CONTEXT(Inkscape::UI::Tools::ToolBase *tool)
Definition lpe-tool.h:83
Raw stack of active status messages.
static R & release(R &r)
Decrements the reference count of a anchored object.
Live Path Effects code.
static void spdc_apply_simplify(double threshold, FreehandBase *dc, SPItem *item)
static ShapeType previous_shape_type
void spdc_apply_style(SPObject *obj)
static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve const *curve, bool is_bend)
static void spdc_paste_curve_as_freehand_shape(Geom::PathVector const &newpath, FreehandBase *dc, SPItem *item)
void spdc_endpoint_snap_free(ToolBase *tool, Geom::Point &p, std::optional< Geom::Point > &start_of_line)
void spdc_endpoint_snap_rotation(ToolBase *tool, Geom::Point &p, Geom::Point const &o, unsigned state)
Snaps node or handle to PI/rotationsnapsperpi degree increments.
static void spdc_flush_white(FreehandBase *dc, std::shared_ptr< SPCurve > gc)
Flushes white curve(s) and additional curve into object.
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
SPDrawAnchor * spdc_test_inside(FreehandBase *dc, Geom::Point const &p)
Returns FIRST active anchor (the activated one).
static void spdc_apply_powerstroke_shape(std::vector< Geom::Point > const &points, FreehandBase *dc, SPItem *item)
static void spdc_apply_bend_shape(gchar const *svgd, FreehandBase *dc, SPItem *item)
void spdc_concat_colors_and_flush(FreehandBase *dc, bool forceclosed)
Concats red, blue and green.
void spdc_create_single_dot(ToolBase *tool, Geom::Point const &pt, char const *path, unsigned event_state)
Create a single dot represented by a circle.
static void spdc_free_colors(FreehandBase *dc)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool mod_ctrl_only(unsigned modifiers)
@ NORMAL_MESSAGE
Definition message.h:26
STL namespace.
int mode
PencilTool: a context for pencil tool events.
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_set(Node *repr, SPCSSAttr *css, gchar const *attr)
Sets an attribute (e.g.
Definition repr-css.cpp:265
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value)
Set a style property to a standard string.
Definition repr-css.cpp:235
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
void sp_repr_css_unset_property(SPCSSAttr *css, gchar const *name)
Set a style property to "inkscape:unset".
Definition repr-css.cpp:202
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
Inkscape::XML::Node const * sp_repr_lookup_name(Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth)
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
Abstract base class for events.
A key has been pressed.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Definition curve.h:24
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
static const unsigned SP_STYLE_FLAG_ALWAYS(1<< 2)
SPCSSAttr * sp_css_attr_from_object(SPObject *object, guint const flags)
Definition style.cpp:1427
SPStyle - a style object for SPItem objects.
unsigned int sp_svg_number_read_d(gchar const *str, double *val)
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop