1 #define INKSCAPE_LIVEPATHEFFECT_CPP
3 /*
4 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
5 *
6 * Released under GNU GPL, read the file 'COPYING' for more information
7 */
9 #include "live_effects/effect.h"
11 #include "display/display-forward.h"
12 #include "xml/node-event-vector.h"
13 #include "sp-object.h"
14 #include "attributes.h"
15 #include "message-stack.h"
16 #include "desktop.h"
17 #include "inkscape.h"
18 #include "document.h"
19 #include "document-private.h"
20 #include "xml/document.h"
21 #include <glibmm/i18n.h>
22 #include "pen-context.h"
23 #include "tools-switch.h"
24 #include "message-stack.h"
25 #include "desktop.h"
26 #include "nodepath.h"
28 #include "live_effects/lpeobject.h"
29 #include "live_effects/parameter/parameter.h"
30 #include <glibmm/ustring.h>
31 #include "libnr/n-art-bpath-2geom.h"
32 #include "display/curve.h"
33 #include <gtkmm.h>
35 #include <exception>
37 #include <2geom/sbasis-to-bezier.h>
38 #include <2geom/matrix.h>
41 // include effects:
42 #include "live_effects/lpe-patternalongpath.h"
43 #include "live_effects/lpe-bendpath.h"
44 #include "live_effects/lpe-sketch.h"
45 #include "live_effects/lpe-vonkoch.h"
46 #include "live_effects/lpe-knot.h"
47 #include "live_effects/lpe-test-doEffect-stack.h"
48 #include "live_effects/lpe-gears.h"
49 #include "live_effects/lpe-curvestitch.h"
50 #include "live_effects/lpe-circle_with_radius.h"
51 #include "live_effects/lpe-perspective_path.h"
52 #include "live_effects/lpe-spiro.h"
53 #include "live_effects/lpe-lattice.h"
54 #include "live_effects/lpe-envelope.h"
55 #include "live_effects/lpe-constructgrid.h"
56 #include "live_effects/lpe-perp_bisector.h"
57 #include "live_effects/lpe-tangent_to_curve.h"
58 #include "live_effects/lpe-mirror_symmetry.h"
59 #include "live_effects/lpe-circle_3pts.h"
60 #include "live_effects/lpe-angle_bisector.h"
61 #include "live_effects/lpe-parallel.h"
62 #include "live_effects/lpe-copy_rotate.h"
63 #include "live_effects/lpe-offset.h"
64 #include "live_effects/lpe-ruler.h"
65 #include "live_effects/lpe-boolops.h"
66 #include "live_effects/lpe-interpolate.h"
67 // end of includes
69 namespace Inkscape {
71 namespace LivePathEffect {
73 const Util::EnumData<EffectType> LPETypeData[] = {
74 // {constant defined in effect.h, N_("name of your effect"), "name of your effect in SVG"}
75 {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"},
76 {BEND_PATH, N_("Bend"), "bend_path"},
77 {BOOLOPS, N_("Boolops"), "boolops"},
78 {CIRCLE_WITH_RADIUS, N_("Circle (center+radius)"), "circle_with_radius"},
79 {CIRCLE_3PTS, N_("Circle through 3 points"), "circle_3pts"},
80 {CONSTRUCT_GRID, N_("Construct grid"), "construct_grid"},
81 #ifdef LPE_ENABLE_TEST_EFFECTS
82 {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"},
83 #endif
84 {ENVELOPE, N_("Envelope Deformation"), "envelope"},
85 {FREEHAND_SHAPE, N_("Freehand Shape"), "freehand_shape"}, // this is actually a special type of PatternAlongPath, used to paste shapes in pen/pencil tool
86 {GEARS, N_("Gears"), "gears"},
87 {INTERPOLATE, N_("Interpolate Sub-Paths"), "interpolate"},
88 {KNOT, N_("Knot"), "knot"},
89 {LATTICE, N_("Lattice Deformation"), "lattice"},
90 {MIRROR_SYMMETRY, N_("Mirror symmetry"), "mirror_symmetry"},
91 {OFFSET, N_("Offset"), "offset"},
92 {PARALLEL, N_("Parallel"), "parallel"},
93 {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG
94 {PERP_BISECTOR, N_("Perpendicular bisector"), "perp_bisector"},
95 {PERSPECTIVE_PATH, N_("Perspective path"), "perspective_path"},
96 {COPY_ROTATE, N_("Rotate copies"), "copy_rotate"},
97 {RULER, N_("Ruler"), "ruler"},
98 {SKETCH, N_("Sketch"), "sketch"},
99 {SPIRO, N_("Spiro spline"), "spiro"},
100 {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"},
101 {TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
102 {VONKOCH, N_("VonKoch"), "vonkoch"},
103 };
104 const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, sizeof(LPETypeData)/sizeof(*LPETypeData));
106 Effect*
107 Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
108 {
109 Effect* neweffect = NULL;
110 switch (lpenr) {
111 case PATTERN_ALONG_PATH:
112 neweffect = static_cast<Effect*> ( new LPEPatternAlongPath(lpeobj) );
113 break;
114 case FREEHAND_SHAPE:
115 neweffect = static_cast<Effect*> ( new LPEFreehandShape(lpeobj) );
116 break;
117 case BEND_PATH:
118 neweffect = static_cast<Effect*> ( new LPEBendPath(lpeobj) );
119 break;
120 case SKETCH:
121 neweffect = static_cast<Effect*> ( new LPESketch(lpeobj) );
122 break;
123 case VONKOCH:
124 neweffect = static_cast<Effect*> ( new LPEVonKoch(lpeobj) );
125 break;
126 case KNOT:
127 neweffect = static_cast<Effect*> ( new LPEKnot(lpeobj) );
128 break;
129 #ifdef LPE_ENABLE_TEST_EFFECTS
130 case DOEFFECTSTACK_TEST:
131 neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) );
132 break;
133 #endif
134 case GEARS:
135 neweffect = static_cast<Effect*> ( new LPEGears(lpeobj) );
136 break;
137 case CURVE_STITCH:
138 neweffect = static_cast<Effect*> ( new LPECurveStitch(lpeobj) );
139 break;
140 case LATTICE:
141 neweffect = static_cast<Effect*> ( new LPELattice(lpeobj) );
142 break;
143 case ENVELOPE:
144 neweffect = static_cast<Effect*> ( new LPEEnvelope(lpeobj) );
145 break;
146 case CIRCLE_WITH_RADIUS:
147 neweffect = static_cast<Effect*> ( new LPECircleWithRadius(lpeobj) );
148 break;
149 case PERSPECTIVE_PATH:
150 neweffect = static_cast<Effect*> ( new LPEPerspectivePath(lpeobj) );
151 break;
152 case SPIRO:
153 neweffect = static_cast<Effect*> ( new LPESpiro(lpeobj) );
154 break;
155 case CONSTRUCT_GRID:
156 neweffect = static_cast<Effect*> ( new LPEConstructGrid(lpeobj) );
157 break;
158 case PERP_BISECTOR:
159 neweffect = static_cast<Effect*> ( new LPEPerpBisector(lpeobj) );
160 break;
161 case TANGENT_TO_CURVE:
162 neweffect = static_cast<Effect*> ( new LPETangentToCurve(lpeobj) );
163 break;
164 case MIRROR_SYMMETRY:
165 neweffect = static_cast<Effect*> ( new LPEMirrorSymmetry(lpeobj) );
166 break;
167 case CIRCLE_3PTS:
168 neweffect = static_cast<Effect*> ( new LPECircle3Pts(lpeobj) );
169 break;
170 case ANGLE_BISECTOR:
171 neweffect = static_cast<Effect*> ( new LPEAngleBisector(lpeobj) );
172 break;
173 case PARALLEL:
174 neweffect = static_cast<Effect*> ( new LPEParallel(lpeobj) );
175 break;
176 case COPY_ROTATE:
177 neweffect = static_cast<Effect*> ( new LPECopyRotate(lpeobj) );
178 break;
179 case OFFSET:
180 neweffect = static_cast<Effect*> ( new LPEOffset(lpeobj) );
181 break;
182 case RULER:
183 neweffect = static_cast<Effect*> ( new LPERuler(lpeobj) );
184 break;
185 case BOOLOPS:
186 neweffect = static_cast<Effect*> ( new LPEBoolops(lpeobj) );
187 break;
188 case INTERPOLATE:
189 neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) );
190 break;
191 default:
192 g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
193 neweffect = NULL;
194 break;
195 }
197 if (neweffect) {
198 neweffect->readallParameters(SP_OBJECT_REPR(lpeobj));
199 }
201 return neweffect;
202 }
204 void
205 Effect::createAndApply(const char* name, SPDocument *doc, SPItem *item)
206 {
207 // Path effect definition
208 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
209 Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
210 repr->setAttribute("effect", name);
212 SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
213 const gchar * repr_id = repr->attribute("id");
214 Inkscape::GC::release(repr);
216 gchar *href = g_strdup_printf("#%s", repr_id);
217 sp_lpe_item_add_path_effect(SP_LPE_ITEM(item), href, true);
218 g_free(href);
219 }
221 void
222 Effect::createAndApply(EffectType type, SPDocument *doc, SPItem *item)
223 {
224 createAndApply(LPETypeConverter.get_key(type).c_str(), doc, item);
225 }
227 Effect::Effect(LivePathEffectObject *lpeobject)
228 : oncanvasedit_it(0),
229 is_visible(_("Is visible?"), _("If unchecked, the effect remains applied to the object but is temporarily disabled on canvas"), "is_visible", &wr, this, true),
230 done_pathparam_set(false),
231 show_orig_path(false),
232 lpeobj(lpeobject),
233 concatenate_before_pwd2(false),
234 provides_own_flash_paths(true) // is automatically set to false if providesOwnFlashPaths() is not overridden
235 {
236 registerParameter( dynamic_cast<Parameter *>(&is_visible) );
237 }
239 Effect::~Effect()
240 {
241 }
243 Glib::ustring
244 Effect::getName()
245 {
246 if (lpeobj->effecttype_set && LPETypeConverter.is_valid_id(lpeobj->effecttype) )
247 return Glib::ustring( _(LPETypeConverter.get_label(lpeobj->effecttype).c_str()) );
248 else
249 return Glib::ustring( _("No effect") );
250 }
252 EffectType
253 Effect::effectType() {
254 return lpeobj->effecttype;
255 }
257 /**
258 * Is performed a single time when the effect is freshly applied to a path
259 */
260 void
261 Effect::doOnApply (SPLPEItem */*lpeitem*/)
262 {
263 }
265 /**
266 * Is performed each time before the effect is updated.
267 */
268 void
269 Effect::doBeforeEffect (SPLPEItem */*lpeitem*/)
270 {
271 //Do nothing for simple effects
272 }
274 /**
275 * Effects can have a parameter path set before they are applied by accepting a nonzero number of
276 * mouse clicks. This method activates the pen context, which waits for the specified number of
277 * clicks. Override Effect::acceptsNumParams() to return the number of expected mouse clicks.
278 */
279 void
280 Effect::doAcceptPathPreparations(SPLPEItem *lpeitem)
281 {
282 // switch to pen context
283 SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop?
284 if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) {
285 tools_switch(desktop, TOOLS_FREEHAND_PEN);
286 }
288 SPEventContext *ec = desktop->event_context;
289 SPPenContext *pc = SP_PEN_CONTEXT(ec);
290 pc->expecting_clicks_for_LPE = this->acceptsNumParams();
291 pc->waiting_LPE = this;
292 pc->waiting_item = lpeitem;
293 pc->polylines_only = true;
295 ec->desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE,
296 g_strdup_printf(_("Please specify a parameter path for the LPE '%s' with %d mouse clicks"),
297 getName().c_str(), acceptsNumParams()));
298 }
300 void
301 Effect::writeParamsToSVG() {
302 std::vector<Inkscape::LivePathEffect::Parameter *>::iterator p;
303 for (p = param_vector.begin(); p != param_vector.end(); ++p) {
304 (*p)->write_to_SVG();
305 }
306 }
308 /**
309 * If the effect expects a path parameter (specified by a number of mouse clicks) before it is
310 * applied, this is the method that processes the resulting path. Override it to customize it for
311 * your LPE. But don't forget to call the parent method so that done_pathparam_set is set to true!
312 */
313 void
314 Effect::acceptParamPath (SPPath */*param_path*/) {
315 done_pathparam_set = true;
316 }
318 /*
319 * Here be the doEffect function chain:
320 */
321 void
322 Effect::doEffect (SPCurve * curve)
323 {
324 std::vector<Geom::Path> orig_pathv = curve->get_pathvector();
326 std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv);
328 curve->set_pathvector(result_pathv);
329 }
331 std::vector<Geom::Path>
332 Effect::doEffect_path (std::vector<Geom::Path> const & path_in)
333 {
334 std::vector<Geom::Path> path_out;
336 if ( !concatenate_before_pwd2 ) {
337 // default behavior
338 for (unsigned int i=0; i < path_in.size(); i++) {
339 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
340 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
341 std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
342 // add the output path vector to the already accumulated vector:
343 for (unsigned int j=0; j < path.size(); j++) {
344 path_out.push_back(path[j]);
345 }
346 }
347 } else {
348 // concatenate the path into possibly discontinuous pwd2
349 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
350 for (unsigned int i=0; i < path_in.size(); i++) {
351 pwd2_in.concat( path_in[i].toPwSb() );
352 }
353 Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in);
354 path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
355 }
357 return path_out;
358 }
360 Geom::Piecewise<Geom::D2<Geom::SBasis> >
361 Effect::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
362 {
363 g_warning("Effect has no doEffect implementation");
364 return pwd2_in;
365 }
367 void
368 Effect::readallParameters(Inkscape::XML::Node * repr)
369 {
370 std::vector<Parameter *>::iterator it = param_vector.begin();
371 while (it != param_vector.end()) {
372 Parameter * param = *it;
373 const gchar * key = param->param_key.c_str();
374 const gchar * value = repr->attribute(key);
375 if (value) {
376 bool accepted = param->param_readSVGValue(value);
377 if (!accepted) {
378 g_warning("Effect::readallParameters - '%s' not accepted for %s", value, key);
379 }
380 } else {
381 // set default value
382 param->param_set_default();
383 }
385 it++;
386 }
387 }
389 /* This function does not and SHOULD NOT write to XML */
390 void
391 Effect::setParameter(const gchar * key, const gchar * new_value)
392 {
393 Parameter * param = getParameter(key);
394 if (param) {
395 if (new_value) {
396 bool accepted = param->param_readSVGValue(new_value);
397 if (!accepted) {
398 g_warning("Effect::setParameter - '%s' not accepted for %s", new_value, key);
399 }
400 } else {
401 // set default value
402 param->param_set_default();
403 }
404 }
405 }
407 void
408 Effect::registerParameter(Parameter * param)
409 {
410 param_vector.push_back(param);
411 }
413 // TODO: should we provide a way to alter the handle's appearance?
414 void
415 Effect::registerKnotHolderHandle(KnotHolderEntity* entity, const char* descr)
416 {
417 kh_entity_vector.push_back(std::make_pair(entity, descr));
418 }
420 /**
421 * Add all registered LPE knotholder handles to the knotholder
422 */
423 void
424 Effect::addHandles(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) {
425 using namespace Inkscape::LivePathEffect;
427 // add handles provided by the effect itself
428 std::vector<std::pair<KnotHolderEntity*, const char*> >::iterator i;
429 for (i = kh_entity_vector.begin(); i != kh_entity_vector.end(); ++i) {
430 KnotHolderEntity *entity = i->first;
431 const char *descr = i->second;
433 entity->create(desktop, item, knotholder, descr);
434 knotholder->add(entity);
435 }
437 // add handles provided by the effect's parameters (if any)
438 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
439 (*p)->addKnotHolderEntities(knotholder, desktop, item);
440 }
441 }
443 void
444 Effect::addHelperPaths(SPLPEItem *lpeitem, SPDesktop *desktop)
445 {
446 g_return_if_fail(desktop);
447 g_return_if_fail(SP_IS_PATH(lpeitem));
449 if (providesKnotholder() && showOrigPath()) {
450 // TODO: we assume that if the LPE provides its own knotholder, there is no nodepath so we
451 // must create the helper curve for the original path manually; once we allow nodepaths and
452 // knotholders alongside each other, this needs to be rethought!
453 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, SP_PATH(lpeitem));
454 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
455 lpeitem->lpe_helperpaths.push_back(tmpitem);
456 }
458 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
459 if ( Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam*>(*p) ) {
460 SPCurve *c = new SPCurve(pathparam->get_pathvector());
462 // TODO: factor this out (also the copied code above); see also lpe-lattice.cpp
463 SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, c, SP_ITEM(lpeitem), 0x009000ff);
464 Inkscape::Display::TemporaryItem* tmpitem = desktop->add_temporary_canvasitem (canvasitem, 0);
465 lpeitem->lpe_helperpaths.push_back(tmpitem);
466 }
467 }
469 addHelperPathsImpl(lpeitem, desktop);
470 }
472 void
473 Effect::addHelperPathsImpl(SPLPEItem */*lpeitem*/, SPDesktop */*desktop*/)
474 {
475 // if this method is overloaded in derived classes, provides_own_flash_paths will be true
476 provides_own_flash_paths = false;
477 }
479 /**
480 * This *creates* a new widget, management of deletion should be done by the caller
481 */
482 Gtk::Widget *
483 Effect::newWidget(Gtk::Tooltips * tooltips)
484 {
485 // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
486 Gtk::VBox * vbox = Gtk::manage( new Gtk::VBox() );
488 vbox->set_border_width(5);
490 std::vector<Parameter *>::iterator it = param_vector.begin();
491 while (it != param_vector.end()) {
492 Parameter * param = *it;
493 Gtk::Widget * widg = param->param_newWidget(tooltips);
494 Glib::ustring * tip = param->param_getTooltip();
495 if (widg) {
496 vbox->pack_start(*widg, true, true, 2);
497 if (tip != NULL) {
498 tooltips->set_tip(*widg, *tip);
499 }
500 }
502 it++;
503 }
505 return dynamic_cast<Gtk::Widget *>(vbox);
506 }
509 Inkscape::XML::Node *
510 Effect::getRepr()
511 {
512 return SP_OBJECT_REPR(lpeobj);
513 }
515 SPDocument *
516 Effect::getSPDoc()
517 {
518 if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("Effect::getSPDoc() returns NULL");
519 return SP_OBJECT_DOCUMENT(lpeobj);
520 }
522 Parameter *
523 Effect::getParameter(const char * key)
524 {
525 Glib::ustring stringkey(key);
527 std::vector<Parameter *>::iterator it = param_vector.begin();
528 while (it != param_vector.end()) {
529 Parameter * param = *it;
530 if ( param->param_key == key) {
531 return param;
532 }
534 it++;
535 }
537 return NULL;
538 }
540 Parameter *
541 Effect::getNextOncanvasEditableParam()
542 {
543 if (param_vector.size() == 0) // no parameters
544 return NULL;
546 oncanvasedit_it++;
547 if (oncanvasedit_it >= static_cast<int>(param_vector.size())) {
548 oncanvasedit_it = 0;
549 }
550 int old_it = oncanvasedit_it;
552 do {
553 Parameter * param = param_vector[oncanvasedit_it];
554 if(param && param->oncanvas_editable) {
555 return param;
556 } else {
557 oncanvasedit_it++;
558 if (oncanvasedit_it == static_cast<int>(param_vector.size())) { // loop round the map
559 oncanvasedit_it = 0;
560 }
561 }
562 } while (oncanvasedit_it != old_it); // iterate until complete loop through map has been made
564 return NULL;
565 }
567 void
568 Effect::editNextParamOncanvas(SPItem * item, SPDesktop * desktop)
569 {
570 if (!desktop) return;
572 Parameter * param = getNextOncanvasEditableParam();
573 if (param) {
574 param->param_editOncanvas(item, desktop);
575 gchar *message = g_strdup_printf(_("Editing parameter <b>%s</b>."), param->param_label.c_str());
576 desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
577 g_free(message);
578 } else {
579 desktop->messageStack()->flash( Inkscape::WARNING_MESSAGE,
580 _("None of the applied path effect's parameters can be edited on-canvas.") );
581 }
582 }
584 /* This function should reset the defaults and is used for example to initialize an effect right after it has been applied to a path
585 * The nice thing about this is that this function can use knowledge of the original path and set things accordingly for example to the size or origin of the original path!
586 */
587 void
588 Effect::resetDefaults(SPItem * /*item*/)
589 {
590 // do nothing for simple effects
591 }
593 void
594 Effect::setup_nodepath(Inkscape::NodePath::Path *np)
595 {
596 np->helperpath_rgba = 0xff0000ff;
597 np->helperpath_width = 1.0;
598 }
600 void
601 Effect::transform_multiply(Geom::Matrix const& postmul, bool set)
602 {
603 // cycle through all parameters. Most parameters will not need transformation, but path and point params do.
604 for (std::vector<Parameter *>::iterator it = param_vector.begin(); it != param_vector.end(); it++) {
605 Parameter * param = *it;
606 param->param_transform_multiply(postmul, set);
607 }
608 }
610 // TODO: take _all_ parameters into account, not only PointParams
611 bool
612 Effect::providesKnotholder()
613 {
614 // does the effect actively provide any knotholder entities of its own?
615 if (kh_entity_vector.size() > 0)
616 return true;
618 // otherwise: are there any PointParams?
619 for (std::vector<Parameter *>::iterator p = param_vector.begin(); p != param_vector.end(); ++p) {
620 if ( Inkscape::LivePathEffect::PointParam *pointparam = dynamic_cast<Inkscape::LivePathEffect::PointParam*>(*p) ) {
621 return true;
622 }
623 }
625 return false;
626 }
628 } /* namespace LivePathEffect */
630 } /* namespace Inkscape */
632 /*
633 Local Variables:
634 mode:c++
635 c-file-style:"stroustrup"
636 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
637 indent-tabs-mode:nil
638 fill-column:99
639 End:
640 */
641 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :