Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-powerstroke.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
8 *
9 * Copyright (C) 2010-2012 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include "lpe-powerstroke.h"
15
16#include <glibmm/i18n.h>
17
19#include <2geom/path-sink.h>
21#include <2geom/circle.h>
22
23#include "preferences.h"
24#include "style.h"
25
26#include "helper/geom.h"
31#include "object/sp-shape.h"
32
33namespace Geom {
34// should all be moved to 2geom at some point
35
38static std::optional<Point> intersection_point( Point const & origin_a, Point const & vector_a,
39 Point const & origin_b, Point const & vector_b)
40{
41 Coord denom = cross(vector_a, vector_b);
42 if (!are_near(denom,0.)){
43 Coord t = (cross(vector_b, origin_a) + cross(origin_b, vector_b)) / denom;
44 return origin_a + t * vector_a;
45 }
46 return std::nullopt;
47}
48
50{
51 std::vector<Geom::Point> temp;
52 sbasis_to_bezier(temp, sbasis_in, 4);
53 return Geom::CubicBezier( temp );
54}
55
63{
64 Point p = P - O;
65 Point q = Q - O;
66 Coord K = 4 * dot(p,q) / (L2sq(p) + L2sq(q));
67
68 double cross = p[Y]*q[X] - p[X]*q[Y];
69 double a = -q[Y]/cross;
70 double b = q[X]/cross;
71 double c = (O[X]*q[Y] - O[Y]*q[X])/cross;
72
73 double d = p[Y]/cross;
74 double e = -p[X]/cross;
75 double f = (-O[X]*p[Y] + O[Y]*p[X])/cross;
76
77 // Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0
78 double A = (a*d*K+d*d+a*a);
79 double B = (a*e*K+b*d*K+2*d*e+2*a*b);
80 double C = (b*e*K+e*e+b*b);
81 double D = (a*f*K+c*d*K+2*d*f-2*d+2*a*c-2*a);
82 double E = (b*f*K+c*e*K+2*e*f-2*e+2*b*c-2*b);
83 double F = c*f*K+f*f-2*f+c*c-2*c+1;
84
85 return Ellipse(A, B, C, D, E, F);
86}
87
92static Circle touching_circle( D2<SBasis> const &curve, double t, double tol=0.01 )
93{
94 //Piecewise<SBasis> k = curvature(curve, tol);
96 if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) {
97 dM=derivative(dM);
98 }
99 if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) { // try second time
100 dM=derivative(dM);
101 }
102 if ( dM.isZero(tol) || (are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) )) { // admit defeat
103 return Geom::Circle(Geom::Point(0., 0.), 0.);
104 }
105 Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
106 if (unitv.empty()) { // admit defeat
107 return Geom::Circle(Geom::Point(0., 0.), 0.);
108 }
109 Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
110 Piecewise<SBasis> k = cross(derivative(unitv),unitv);
111 k = divide(k,dMlength,tol,3);
112 double curv = k(t); // note that this value is signed
113
114 Geom::Point normal = unitTangentAt(curve, t).cw();
115 double radius = 1/curv;
116 Geom::Point center = curve(t) + radius*normal;
117 return Geom::Circle(center, fabs(radius));
118}
119
120} // namespace Geom
121
122namespace Inkscape {
123namespace LivePathEffect {
124
126 {Geom::Interpolate::INTERP_CUBICBEZIER_SMOOTH, N_("CubicBezierSmooth"), "CubicBezierSmooth"},
127 {Geom::Interpolate::INTERP_LINEAR , N_("Linear"), "Linear"},
128 {Geom::Interpolate::INTERP_CUBICBEZIER , N_("CubicBezierFit"), "CubicBezierFit"},
129 {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN , N_("CubicBezierJohan"), "CubicBezierJohan"},
130 {Geom::Interpolate::INTERP_SPIRO , N_("SpiroInterpolator"), "SpiroInterpolator"},
131 {Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM, N_("Centripetal Catmull-Rom"), "CentripetalCatmullRom"}
132};
134
144 {LINEJOIN_BEVEL, N_("Beveled"), "bevel"},
145 {LINEJOIN_ROUND, N_("Rounded"), "round"},
146// {LINEJOIN_EXTRP_MITER, N_("Extrapolated"), "extrapolated"}, // disabled because doesn't work well
147 {LINEJOIN_EXTRP_MITER_ARC, N_("Extrapolated arc"), "extrp_arc"},
148 {LINEJOIN_MITER, N_("Miter"), "miter"},
149 {LINEJOIN_SPIRO, N_("Spiro"), "spiro"},
150};
152
154 Effect(lpeobject),
155 offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this),
156 not_jump(_("No jumping handles"), _("Allow to move handles along the path without them automatically attaching to the nearest path segment"), "not_jump", &wr, this, false),
157 sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve"), "sort_points", &wr, this, true),
158 interpolator_type(_("Smoothing type"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path"), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM),
159 interpolator_beta(_("Smoothness:"), _("Sets the smoothness for the CubicBezierJohan interpolator; 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2),
160 scale_width(_("Width multiplier"), _("Scale the stroke's width uniformly along the whole path"), "scale_width", &wr, this, 1.0),
161 start_linecap_type(_("Start cap:"), _("Determines the shape of the path's start"), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ZERO_WIDTH),
162 linejoin_type(_("Join"), _("Determines the shape of the path's corners"), "linejoin_type", LineJoinTypeConverter, &wr, this, LINEJOIN_ROUND),
163 miter_limit(_("Miter limit"), _("Maximum length of the miter (in units of stroke width)"), "miter_limit", &wr, this, 4.),
164 end_linecap_type(_("End cap"), _("Determines the shape of the path's end"), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ZERO_WIDTH),
165 message(_("Add new thickness control point"), _("Important messages"), "message", &wr, this, _("<b>Ctrl + click</b> on existing node and move it"))
166{
167 show_orig_path = true;
168
170
171
183
184 message.write_to_SVG(); // resert old legacy uneeded data
187
192 recusion_limit = 0;
193 has_recursion = false;
195
196}
197
199
200void
209
211{
212 lpe_shape_convert_stroke_and_fill(cast<SPShape>(lpeitem));
213}
214
215void
217{
218 if (auto shape = cast<SPShape>(lpeitem)) {
219 lpeversion.param_setValue("1.3", true);
220 SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem);
221 std::vector<Geom::Point> points;
222 Geom::PathVector const &pathv = pathv_to_linear_and_cubic_beziers(*shape->curve());
223 double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed / 2 : 1.;
225 Glib::ustring pref_path_pp = "/live_effects/powerstroke/powerpencil";
226 bool powerpencil = prefs->getBool(pref_path_pp, false);
227 bool clipboard = offset_points.data().size() > 0;
228 if (!powerpencil) {
230 }
231 if (!clipboard && !powerpencil) {
232 item->updateRepr();
233 if (pathv.empty()) {
234 points.emplace_back(0.2,width );
235 points.emplace_back(0.5, width);
236 points.emplace_back(0.8, width);
237 } else {
238 size_t current_pos = 0;
239 for (auto path : pathv) {
240 size_t psize = count_pathvector_curves(path);
241 if (!path.closed()) {
242 points.emplace_back(0.2 + current_pos, width);
243 }
244 points.emplace_back((0.5 * psize) + current_pos, width);
245 if (!path.closed()) {
246 points.emplace_back((psize - 0.2) + current_pos, width);
247 }
248 current_pos += psize;
249 }
250 }
252 }
254 } else {
255 if (!is<SPShape>(lpeitem)) {
256 g_warning("LPE Powerstroke can only be applied to shapes (not groups).");
257 }
258 }
259}
260
262{
263 auto lpeitem_mutable = const_cast<SPLPEItem *>(lpeitem);
264 auto shape = cast<SPShape>(lpeitem_mutable);
265
266 if (shape && !keep_paths) {
267 // medial width give half
269 }
270}
271
272void
277
278
279static bool compare_offsets (Geom::Point first, Geom::Point second)
280{
281 return first[Geom::X] < second[Geom::X];
282}
283
285 Geom::Piecewise<Geom::SBasis> const & y, // width path
286 LineJoinType jointype,
287 double miter_limit,
288 double tol=Geom::EPSILON)
289{
290/* per definition, each discontinuity should be fixed with a join-ending, as defined by linejoin_type
291*/
293 Geom::OptRect bbox = bounds_fast(B);
294 if (B.empty() || !bbox) {
295 return pb.peek().front();
296 }
297
298 pb.setStitching(true);
299
300 Geom::Point start = B[0].at0();
301 pb.moveTo(start);
302 build_from_sbasis(pb, B[0], tol, false);
303 unsigned prev_i = 0;
304 for (unsigned i=1; i < B.size(); i++) {
305 // Skip degenerate segments. The number below was determined, after examining
306 // very many paths with powerstrokes of all shapes and sizes, to allow filtering out most
307 // degenerate segments without losing significant quality; it is close to 1/256.
308 if (B[i].isConstant(4e-3)) {
309 continue;
310 }
311 if (!are_near(B[prev_i].at1(), B[i].at0(), tol) )
312 { // discontinuity found, so fix it :-)
313 double width = y( B.cuts[i] );
314
315 Geom::Point tang1 = -unitTangentAt(reverse(B[prev_i]),0.); // = unitTangentAt(B[prev_i],1);
316 Geom::Point tang2 = unitTangentAt(B[i],0);
317 Geom::Point discontinuity_vec = B[i].at0() - B[prev_i].at1();
318 bool on_outside = ( dot(tang1, discontinuity_vec) >= 0. );
319
320 if (on_outside) {
321 // we are on the outside: add some type of join!
322 switch (jointype) {
323 case LINEJOIN_ROUND: {
324 /* for constant width paths, the rounding is a circular arc (rx == ry),
325 for non-constant width paths, the rounding can be done with an ellipse but is hard and ambiguous.
326 The elliptical arc should go through the discontinuity's start and end points (of course!)
327 and also should match the discontinuity tangents at those start and end points.
328 To resolve the ambiguity, the elliptical arc with minimal eccentricity should be chosen.
329 A 2Geom method was created to do exactly this :)
330 */
331
332 std::optional<Geom::Point> O = intersection_point( B[prev_i].at1(), tang1,
333 B[i].at0(), tang2 );
334 if (!O) {
335 // no center found, i.e. 180 degrees round
336 pb.lineTo(B[i].at0()); // default to bevel for too shallow cusp angles
337 break;
338 }
339
340 Geom::Ellipse ellipse;
341 try {
342 ellipse = find_ellipse(B[prev_i].at1(), B[i].at0(), *O);
343 }
344 catch (Geom::LogicalError &e) {
345 // 2geom did not find a fitting ellipse, this happens for weird thick paths :)
346 // do bevel, and break
347 pb.lineTo(B[i].at0());
348 break;
349 }
350 catch (Geom::RangeError &e) {
351 // 2geom did not find a fitting ellipse, this happens for weird thick paths :)
352 // do bevel, and break
353 pb.lineTo(B[i].at0());
354 break;
355 }
356
357 // check if ellipse.ray is within 'sane' range.
358 if ( ( fabs(ellipse.ray(Geom::X)) > 1e6 ) ||
359 ( fabs(ellipse.ray(Geom::Y)) > 1e6 ) )
360 {
361 // do bevel, and break
362 pb.lineTo(B[i].at0());
363 break;
364 }
365
366 pb.arcTo( ellipse.ray(Geom::X), ellipse.ray(Geom::Y), ellipse.rotationAngle(),
367 false, width < 0, B[i].at0() );
368
369 break;
370 }
372 Geom::D2<Geom::SBasis> newcurve1 = B[prev_i] * Geom::reflection(rot90(tang1), B[prev_i].at1());
374
375 Geom::D2<Geom::SBasis> newcurve2 = B[i] * Geom::reflection(rot90(tang2), B[i].at0());
377
378 Geom::Crossings cross = crossings(bzr1, bzr2);
379 if (cross.empty()) {
380 // empty crossing: default to bevel
381 pb.lineTo(B[i].at0());
382 } else {
383 // check size of miter
384 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
385 Geom::Coord len = distance(bzr1.pointAt(cross[0].ta), point_on_path);
386 if (len > fabs(width) * miter_limit) {
387 // miter too big: default to bevel
388 pb.lineTo(B[i].at0());
389 } else {
390 std::pair<Geom::CubicBezier, Geom::CubicBezier> sub1 = bzr1.subdivide(cross[0].ta);
391 std::pair<Geom::CubicBezier, Geom::CubicBezier> sub2 = bzr2.subdivide(cross[0].tb);
392 pb.curveTo(sub1.first[1], sub1.first[2], sub1.first[3]);
393 pb.curveTo(sub2.second[1], sub2.second[2], sub2.second[3]);
394 }
395 }
396 break;
397 }
399 // Extrapolate using the curvature at the end of the path segments to join
400 Geom::Circle circle1 = Geom::touching_circle(reverse(B[prev_i]), 0.0);
401 Geom::Circle circle2 = Geom::touching_circle(B[i], 0.0);
402 std::vector<Geom::ShapeIntersection> solutions;
403 solutions = circle1.intersect(circle2);
404 if (solutions.size() == 2) {
405 Geom::Point sol(0.,0.);
406 bool solok = true;
407 bool point0bad = false;
408 bool point1bad = false;
409 if ( dot(tang2, solutions[0].point() - B[i].at0()) > 0)
410 {
411 // points[0] is bad, choose points[1]
412 point0bad = true;
413 }
414 if ( dot(tang2, solutions[1].point() - B[i].at0()) > 0)
415 {
416 // points[1] is bad, choose points[0]
417 point1bad = true;
418 }
419 if (!point0bad && !point1bad ) {
420 // both points are good, choose nearest
421 sol = ( distanceSq(B[i].at0(), solutions[0].point()) < distanceSq(B[i].at0(), solutions[1].point()) ) ?
422 solutions[0].point() : solutions[1].point();
423 } else if (!point0bad) {
424 sol = solutions[0].point();
425 } else if (!point1bad) {
426 sol = solutions[1].point();
427 } else {
428 solok = false;
429 }
430 (*bbox).expandBy (bbox->width()/4);
431
432 if (!(*bbox).contains(sol)) {
433 solok = false;
434 }
435 Geom::EllipticalArc *arc0 = nullptr;
436 Geom::EllipticalArc *arc1 = nullptr;
437 bool build = false;
438 if (solok) {
439 arc0 = circle1.arc(B[prev_i].at1(), 0.5*(B[prev_i].at1()+sol), sol);
440 arc1 = circle2.arc(sol, 0.5*(sol+B[i].at0()), B[i].at0());
441 if (arc0) {
442 // FIX: Some assertions errors here
443 build_from_sbasis(pb,arc0->toSBasis(), tol, false);
444 build = true;
445 } else if (arc1) {
446 std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
447 B[i].at0(), tang2 );
448 if (p) {
449 // check size of miter
450 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
451 Geom::Coord len = distance(*p, point_on_path);
452 if (len <= fabs(width) * miter_limit) {
453 // miter OK
454 pb.lineTo(*p);
455 build = true;
456 }
457 }
458 }
459 if (build) {
460 build_from_sbasis(pb,arc1->toSBasis(), tol, false);
461 } else if (arc0) {
462 pb.lineTo(B[i].at0());
463 }
464 }
465 if (!solok || !(arc0 && build)) {
466 // fall back to miter
467 std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
468 B[i].at0(), tang2 );
469 if (p) {
470 // check size of miter
471 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
472 Geom::Coord len = distance(*p, point_on_path);
473 if (len <= fabs(width) * miter_limit) {
474 // miter OK
475 pb.lineTo(*p);
476 }
477 }
478 pb.lineTo(B[i].at0());
479 }
480 if (arc0) {
481 delete arc0;
482 arc0 = nullptr;
483 }
484 if (arc1) {
485 delete arc1;
486 arc1 = nullptr;
487 }
488 } else {
489 // fall back to miter
490 std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
491 B[i].at0(), tang2 );
492 if (p) {
493 // check size of miter
494 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
495 Geom::Coord len = distance(*p, point_on_path);
496 if (len <= fabs(width) * miter_limit) {
497 // miter OK
498 pb.lineTo(*p);
499 }
500 }
501 pb.lineTo(B[i].at0());
502 }
503 /*else if (solutions == 1) { // one circle is inside the other
504 // don't know what to do: default to bevel
505 pb.lineTo(B[i].at0());
506 } else { // no intersections
507 // don't know what to do: default to bevel
508 pb.lineTo(B[i].at0());
509 } */
510
511 break;
512 }
513 case LINEJOIN_MITER: {
514 std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
515 B[i].at0(), tang2 );
516 if (p) {
517 // check size of miter
518 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
519 Geom::Coord len = distance(*p, point_on_path);
520 if (len <= fabs(width) * miter_limit) {
521 // miter OK
522 pb.lineTo(*p);
523 }
524 }
525 pb.lineTo(B[i].at0());
526 break;
527 }
528 case LINEJOIN_SPIRO: {
529 Geom::Point direction = B[i].at0() - B[prev_i].at1();
530 double tang1_sign = dot(direction,tang1);
531 double tang2_sign = dot(direction,tang2);
532
533 Spiro::spiro_cp *controlpoints = g_new (Spiro::spiro_cp, 4);
534 controlpoints[0].x = (B[prev_i].at1() - tang1_sign*tang1)[Geom::X];
535 controlpoints[0].y = (B[prev_i].at1() - tang1_sign*tang1)[Geom::Y];
536 controlpoints[0].ty = '{';
537 controlpoints[1].x = B[prev_i].at1()[Geom::X];
538 controlpoints[1].y = B[prev_i].at1()[Geom::Y];
539 controlpoints[1].ty = ']';
540 controlpoints[2].x = B[i].at0()[Geom::X];
541 controlpoints[2].y = B[i].at0()[Geom::Y];
542 controlpoints[2].ty = '[';
543 controlpoints[3].x = (B[i].at0() + tang2_sign*tang2)[Geom::X];
544 controlpoints[3].y = (B[i].at0() + tang2_sign*tang2)[Geom::Y];
545 controlpoints[3].ty = '}';
546
547 auto spiro = Spiro::spiro_run(controlpoints, 4);
548 pb.append(spiro.portion(1, spiro.size_open() - 1));
549 break;
550 }
551 case LINEJOIN_BEVEL:
552 default:
553 pb.lineTo(B[i].at0());
554 break;
555 }
556
557 build_from_sbasis(pb, B[i], tol, false);
558
559 } else {
560 // we are on inside of corner!
561 Geom::Path bzr1 = path_from_sbasis( B[prev_i], tol );
562 Geom::Path bzr2 = path_from_sbasis( B[i], tol );
563 Geom::Crossings cross = crossings(bzr1, bzr2);
564 if (cross.size() != 1) {
565 // empty crossing or too many crossings: default to bevel
566 pb.lineTo(B[i].at0());
567 pb.append(bzr2);
568 } else {
569 // :-) quick hack:
570 for (unsigned i=0; i < bzr1.size_open(); ++i) {
571 pb.backspace();
572 }
573
574 pb.append( bzr1.portion(0, cross[0].ta) );
575 pb.append( bzr2.portion(cross[0].tb, bzr2.size_open()) );
576 }
577 }
578 } else {
579 build_from_sbasis(pb, B[i], tol, false);
580 }
581
582 prev_i = i;
583 }
584 pb.flush();
585 return pb.peek().front();
586}
587
590{
591 using namespace Geom;
592
593 Geom::PathVector path_out;
594 if (path_in.empty()) {
595 return path_in;
596 }
598 size_t path_init = 0;
599 if (_adjust_path) {
601 _adjust_path = false; // not wait till effect finish
602 Glib::ustring version = lpeversion.param_getSVGValue();
603 if (version < "1.3") {
605
606 } else {
608 }
609 }
611 if (pwd2_in.empty()) {
612 return path_in;
613 }
615 if (der.empty()) {
616 return path_in;
617 }
619 if (n.empty()) {
620 return path_in;
621 }
622 Geom::PathVector path_out_prev_tmp = path_out_prev;
624 n = rot90(n);
625 Glib::ustring version = lpeversion.param_getSVGValue();
626 offset_points.set_pwd2(pwd2_in , n);
627 size_t pathindex = 0;
628 for (auto path : pathv) {
629 if (path.empty()) {
630 std::cerr << "LPEPowerStroke::doEffect_path: empty sub-path!" << std::endl;
631 continue;
632 }
633 size_t psize = count_pathvector_curves(path);
634 path_init += psize;
635 if (!offset_points.unplaced &&
636 knotdragging &&
637 path_out_prev_tmp.size() > pathindex &&
638 pathindex != offset_points.current_path &&
639 offset_points.current_path != Glib::ustring::npos)
640 {
641 path_out.push_back(path_out_prev_tmp[pathindex]);
642 path_out_prev.push_back(path_out_prev_tmp[pathindex]);
643 pathindex++;
644 if (path.closed()) {
645 path_out.push_back(path_out_prev_tmp[pathindex]);
646 path_out_prev.push_back(path_out_prev_tmp[pathindex]);
647 pathindex++;
648 }
649 continue;
650 }
651 pwd2_in = path.toPwSb();
652 if (pwd2_in.empty()) {
653 continue;
654 }
655 Piecewise<D2<SBasis> > der = derivative(pwd2_in);
656 if (der.empty()) {
657 continue;
658 }
659 Piecewise<D2<SBasis> > n = unitVector(der,0.00001);
660 if (n.empty()) {
661 continue;
662 }
663
664 n = rot90(n);
665
666 LineCapType end_linecap = static_cast<LineCapType>(end_linecap_type.get_value());
667 LineCapType start_linecap = static_cast<LineCapType>(start_linecap_type.get_value());
668
669 std::vector<Geom::Point> ts_no_scale = offset_points.data();
670 if (ts_no_scale.empty()) {
671 continue;
672 }
673 std::vector<Geom::Point> ts;
674 for (auto & tsp : ts_no_scale) {
675 if (path_init - psize <= tsp[Geom::X] && path_init >= tsp[Geom::X]) {
676 Geom::Point p = Geom::Point(tsp[Geom::X] - (path_init - psize), tsp[Geom::Y] * scale_width);
677 ts.push_back(p);
678 }
679 }
680 if (sort_points) {
681 sort(ts.begin(), ts.end(), compare_offsets);
682 }
683 // create stroke path where points (x,y) := (t, offset)
685 if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast<Geom::Interpolate::CubicBezierJohan*>(interpolator)) {
686 johan->setBeta(interpolator_beta);
687 }
689 smooth->setBeta(interpolator_beta);
690 }
691 if (path.closed() && ts.size()) {
692 std::vector<Geom::Point> ts_close;
693 //we have only one knot or overwrite before
694 Geom::Point start = Geom::Point( pwd2_in.domain().min(), ts.front()[Geom::Y]);
695 Geom::Point end = Geom::Point( pwd2_in.domain().max(), ts.front()[Geom::Y]);
696 if (ts.size() > 1) {
697 if (version < "1.3") {
698 end = Geom::Point(pwd2_in.domain().max(), 0);
699 Geom::Point tmpstart(0, 0);
700 tmpstart[Geom::X] = end[Geom::X] + ts.front()[Geom::X];
701 tmpstart[Geom::Y] = ts.front()[Geom::Y];
702 ts_close.push_back(ts.back());
703 ts_close.push_back(middle_point(tmpstart, ts.back()));
704 ts_close.push_back(tmpstart);
705 Geom::Path closepath = interpolator->interpolateToPath(ts_close);
706 end = closepath.pointAt(Geom::nearest_time(end, closepath));
707 end[Geom::X] = pwd2_in.domain().max();
708 start = end;
709 start[Geom::X] = pwd2_in.domain().min();
710 } else {
711 double pl = 1;
712 double pl2 = 0;
713 if (ts.front()[Geom::X] > 0) {
714 Geom::Path p = path.portion(pwd2_in.domain().min(),ts.front()[Geom::X]);
715 pl = 0;
716 for (gint i = 0; i < p.size_open(); i++) {
717 pl += p.curveAt(i).length();
718 }
719 }
720 if (pwd2_in.domain().max() != ts.back()[Geom::X]) {
721 Geom::Path p2 = path.portion(ts.back()[Geom::X], pwd2_in.domain().max());
722 pl2 = 0;
723 for (gint i = 0; i < p2.size_open(); i++) {
724 pl2 += p2.curveAt(i).length();
725 }
726 }
727 gint signfront = ts.front()[Geom::Y] > 0 ? 1 : -1;
728 gint signback = ts.back() [Geom::Y] > 0 ? 1 : -1;
729 gint sign = 1;
730 bool inverted = std::abs(ts.front()[Geom::Y]) > std::abs(ts.back()[Geom::Y]);
731 double min = std::min(std::abs(ts.front()[Geom::Y]), std::abs(ts.back()[Geom::Y]));
732 double max = std::max(std::abs(ts.front()[Geom::Y]), std::abs(ts.back()[Geom::Y]));
733 if (signfront < 0 && signback < 0) {
734 min *= -1;
735 max *= -1;
736 sign = -1;
737 } else if (signfront < 0) {
738 max *= max == std::abs(ts.front()[Geom::Y]) ? signfront : signback;
739 min *= min == std::abs(ts.front()[Geom::Y]) ? signback : signfront;
740 } else if (signback < 0) {
741 max *= max == std::abs(ts.front()[Geom::Y]) ? signfront : signback;
742 min *= min == std::abs(ts.front()[Geom::Y]) ? signback : signfront;
743 }
744 double gap = std::abs(max-min);
745 double factor1 = pl/(pl + pl2);
746 double factor2 = pl2/(pl + pl2);
747 bool toggled = false;
748 if (!inverted) {
749 toggled = true;
750 }
751 end = Geom::Point(pwd2_in.domain().max(),((std::abs(min) + (gap * (toggled ? factor1 : factor2))) * sign));
752 start = end;
753 start[Geom::X] = pwd2_in.domain().min();
754 }
755 }
756 ts.insert(ts.begin(), start );
757 ts.push_back( end );
758 if (version < "1.3") {
759 ts_close.clear();
760 }
761 } else {
762 // add width data for first and last point on the path
763 // depending on cap type, these first and last points have width zero or take the width from the closest width point.
764 auto start_y = (start_linecap == LINECAP_ZERO_WIDTH || ts.empty()) ? 0. : ts.front()[Geom::Y];
765 auto end_y = (end_linecap == LINECAP_ZERO_WIDTH || ts.empty()) ? 0. : ts.back()[Geom::Y];
766 ts.insert(ts.begin(), Geom::Point(pwd2_in.domain().min(), start_y));
767 ts.emplace_back(pwd2_in.domain().max(), end_y);
768 }
769
770 // do the interpolation in a coordinate system that is more alike to the on-canvas knots,
771 // instead of the heavily compressed coordinate system of (segment_no offset, Y) in which the knots are stored
772 double pwd2_in_arclength = length(pwd2_in);
773 double xcoord_scaling = pwd2_in_arclength / ts.back()[Geom::X];
774 for (auto & t : ts) {
775 t[Geom::X] *= xcoord_scaling;
776 }
777
778 Geom::Path strokepath = interpolator->interpolateToPath(ts);
779 delete interpolator;
780
781 // apply the inverse knot-xcoord scaling that was applied before the interpolation
782 strokepath *= Scale(1/xcoord_scaling, 1);
783
784 D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
785 Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
786 Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
787 // find time values for which x lies outside path domain
788 // and only take portion of x and y that lies within those time values
789 std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
790 std::vector< double > rtsmax = roots (x + pwd2_in.domain().max());
791 if ( !rtsmin.empty() && !rtsmax.empty() ) {
792 x = portion(x, rtsmin.at(0), rtsmax.at(0));
793 y = portion(y, rtsmin.at(0), rtsmax.at(0));
794 }
795
796 LineJoinType jointype = static_cast<LineJoinType>(linejoin_type.get_value());
797 if (x.empty() || y.empty()) {
798 continue;
799 }
800 Piecewise<D2<SBasis> > pwd2_out = compose(pwd2_in,x) + y*compose(n,x);
801 Piecewise<D2<SBasis> > mirrorpath = reverse( compose(pwd2_in,x) - y*compose(n,x));
802
803 Geom::Path fixed_path = path_from_piecewise_fix_cusps( pwd2_out, y, jointype, miter_limit, LPE_CONVERSION_TOLERANCE);
804 Geom::Path fixed_mirrorpath = path_from_piecewise_fix_cusps( mirrorpath, reverse(y), jointype, miter_limit, LPE_CONVERSION_TOLERANCE);
805 if (path.closed()) {
806 fixed_path.close(true);
807 path_out.push_back(fixed_path);
808 path_out_prev.push_back(fixed_path);
809 fixed_mirrorpath.close(true);
810 path_out.push_back(fixed_mirrorpath);
811 path_out_prev.push_back(fixed_mirrorpath);
812 pathindex++;
813 pathindex++;
814 } else {
815 // add linecaps...
816 switch (end_linecap) {
818 // do nothing
819 break;
820 case LINECAP_PEAK:
821 {
822 Geom::Point end_deriv = -unitTangentAt( reverse(pwd2_in.segs.back()), 0.);
823 double radius = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
824 Geom::Point midpoint = 0.5*(fixed_path.finalPoint() + fixed_mirrorpath.initialPoint()) + radius*end_deriv;
825 fixed_path.appendNew<LineSegment>(midpoint);
826 fixed_path.appendNew<LineSegment>(fixed_mirrorpath.initialPoint());
827 break;
828 }
829 case LINECAP_SQUARE:
830 {
831 Geom::Point end_deriv = -unitTangentAt( reverse(pwd2_in.segs.back()), 0.);
832 double radius = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
833 fixed_path.appendNew<LineSegment>( fixed_path.finalPoint() + radius*end_deriv );
834 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() + radius*end_deriv );
835 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() );
836 break;
837 }
838 case LINECAP_BUTT:
839 {
840 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() );
841 break;
842 }
843 case LINECAP_ROUND:
844 default:
845 {
846 double radius1 = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
847 fixed_path.appendNew<EllipticalArc>( radius1, radius1, M_PI/2., false, y.lastValue() < 0, fixed_mirrorpath.initialPoint() );
848 break;
849 }
850 }
851
852 fixed_path.append(fixed_mirrorpath);
853 switch (start_linecap) {
855 // do nothing
856 break;
857 case LINECAP_PEAK:
858 {
859 Geom::Point start_deriv = unitTangentAt( pwd2_in.segs.front(), 0.);
860 double radius = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
861 Geom::Point midpoint = 0.5*(fixed_mirrorpath.finalPoint() + fixed_path.initialPoint()) - radius*start_deriv;
862 fixed_path.appendNew<LineSegment>( midpoint );
863 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
864 break;
865 }
866 case LINECAP_SQUARE:
867 {
868 Geom::Point start_deriv = unitTangentAt( pwd2_in.segs.front(), 0.);
869 double radius = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
870 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.finalPoint() - radius*start_deriv );
871 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() - radius*start_deriv );
872 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
873 break;
874 }
875 case LINECAP_BUTT:
876 {
877 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
878 break;
879 }
880 case LINECAP_ROUND:
881 default:
882 {
883 double radius2 = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
884 fixed_path.appendNew<EllipticalArc>( radius2, radius2, M_PI/2., false, y.firstValue() < 0, fixed_path.initialPoint() );
885 break;
886 }
887 }
888 fixed_path.close(true);
889 path_out.push_back(fixed_path);
890 path_out_prev.push_back(fixed_path);
891 pathindex++;
892 }
893 if (version < "1.3") {
894 break;
895 }
896 }
897 path_out_prev_tmp.clear();
898 if (path_out.empty()) {
899 return path_in;
900 // doEffect_path (path_in);
901 }
902 return path_out;
903}
904
905void LPEPowerStroke::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
906{
907 if (!sp_lpe_item->unoptimized()) {
909 }
910}
911
913{
915 if (recusion_limit < 6) {
918 if (effect) {
920 dynamic_cast<LivePathEffect::LPESimplify *>(effect->getLPEObj()->get_lpe());
921 double threshold = simplify->threshold * 1.2;
922 simplify->threshold.param_set_value(threshold);
923 simplify->threshold.write_to_SVG();
924 has_recursion = true;
925 }
926 }
928 } else {
929 recusion_limit = 0;
930 }
931}
932
933/* ######################## */
934
935} //namespace LivePathEffect
936} /* namespace Inkscape */
937
938/*
939 Local Variables:
940 mode:c++
941 c-file-style:"stroustrup"
942 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
943 indent-tabs-mode:nil
944 fill-column:99
945 End:
946*/
947// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Circle shape.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Bezier curve with compile-time specified order.
std::pair< BezierCurveN, BezierCurveN > subdivide(Coord t) const
Divide a Bezier curve into two curves.
Point pointAt(Coord t) const override
Evaluate the curve at a specified time value.
Set of all points at a fixed distance from the center.
Definition circle.h:55
EllipticalArc * arc(Point const &initial, Point const &inner, Point const &final) const
Definition circle.cpp:254
std::vector< ShapeIntersection > intersect(Line const &other) const
Definition circle.cpp:163
virtual Coord length(Coord tolerance=0.01) const
Compute the arc length of this curve.
Definition curve.cpp:56
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
bool isZero(double eps=EPSILON) const
Definition d2.h:109
Set of points with a constant sum of distances from two foci.
Definition ellipse.h:68
Angle rotationAngle() const
Get the angle the X ray makes with the +X axis.
Definition ellipse.h:126
Coord ray(Dim2 d) const
Get one ray of the ellipse.
Definition ellipse.h:124
Elliptical arc curve.
D2< SBasis > toSBasis() const override
Convert the curve to a symmetric power basis polynomial.
constexpr C min() const
constexpr C max() const
virtual Geom::Path interpolateToPath(std::vector< Point > const &points) const =0
static Interpolator * create(InterpolatorType type)
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Store paths to a PathVector.
Definition path-sink.h:226
PathVector const & peek() const
Retrieve the path.
Definition path-sink.h:236
void setStitching(bool s)
Definition path-sink.h:204
void moveTo(Point const &p) override
Move to a different point without creating a segment.
Definition path-sink.h:121
void append(Path const &other)
Definition path-sink.h:181
bool backspace() override
Undo the last segment.
Definition path-sink.h:172
void arcTo(Coord rx, Coord ry, Coord angle, bool large_arc, bool sweep, Point const &p) override
Output an elliptical arc segment.
Definition path-sink.h:161
void lineTo(Point const &p) override
Output a line segment.
Definition path-sink.h:137
void flush() override
Flush any internal state of the generator.
Definition path-sink.h:196
void curveTo(Point const &c0, Point const &c1, Point const &p) override
Output a quadratic Bezier segment.
Definition path-sink.h:153
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
Point finalPoint() const
Get the last point in the path.
Definition path.h:709
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
Point pointAt(Coord t) const
Get the point at the specified time value.
Definition path.cpp:449
size_type size_open() const
Size without the closing segment, even if the path is closed.
Definition path.h:476
Path portion(Coord f, Coord t) const
Definition path.h:645
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
Curve const & curveAt(Coord t, Coord *rest=NULL) const
Get the curve at the specified time value.
Definition path.cpp:440
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
Function defined as discrete pieces.
Definition piecewise.h:71
bool empty() const
Definition piecewise.h:132
Interval domain() const
Definition piecewise.h:215
output_type lastValue() const
Definition piecewise.h:109
output_type firstValue() const
Definition piecewise.h:106
std::vector< T > segs
Definition piecewise.h:76
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Scaling from the origin.
Definition transforms.h:150
void param_set_and_write_new_value(std::vector< StorageType > const &new_vector)
Definition array.h:92
std::vector< StorageType > const & data() const
Definition array.h:48
void registerParameter(Parameter *param)
Definition effect.cpp:1704
Geom::PathVector pathvector_before_effect
Definition effect.h:173
Geom::PathVector pathvector_after_effect
Definition effect.h:174
LivePathEffectObject * getLPEObj()
Definition effect.h:150
void param_setValue(Glib::ustring newvalue, bool write=false)
Definition hidden.cpp:71
Glib::ustring param_getSVGValue() const override
Definition hidden.cpp:53
void doBeforeEffect(SPLPEItem const *lpeItem) override
Is performed each time before the effect is updated.
void doOnApply(SPLPEItem const *lpeitem) override
Is performed a single time when the effect is freshly applied to a path.
Geom::PathVector doEffect_path(Geom::PathVector const &path_in) override
PowerStrokePointArrayParam offset_points
void transform_multiply(Geom::Affine const &postmul, bool set) override
Overridden function to apply transforms for example to powerstroke, jointtype or tapperstroke.
void doOnRemove(SPLPEItem const *lpeitem) override
LPEPowerStroke(LivePathEffectObject *lpeobject)
void doAfterEffect(SPLPEItem const *lpeitem, Geom::PathVector *curve) override
Is performed at the end of the LPE only one time per "lpeitem" in paths/shapes is called in middle of...
void recalculate_controlpoints(Geom::PathVector pv)
call this method to recalculate the controlpoints such that they stay at the same location relative t...
void set_pwd2(Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwd2_in, Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwd2_normal_in)
void param_transform_multiply(Geom::Affine const &postmul, bool) override
void param_set_digits(unsigned digits)
void param_set_range(double min, double max)
void param_set_increments(double step, double page)
void addSlider(bool add_slider_widget)
Definition parameter.h:140
Preference storage class.
Definition preferences.h:61
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:42
Inkscape::LivePathEffect::Effect * get_lpe()
Definition lpeobject.h:51
bool unoptimized()
Definition sp-item.cpp:1653
Inkscape::LivePathEffect::Effect * getFirstPathEffectOfType(int type)
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
Geom::IntPoint size
double c[8][4]
Elliptical arc curve.
Fill/stroke conversion routines for LPEs which draw a stroke.
BezierCurveN< 3 > CubicBezier
Cubic (order 3) Bezier curve.
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Affine reflection(Point const &vector, Point const &origin)
Reflects objects about line.
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:586
size_t count_pathvector_curves(Geom::PathVector const &pathv)
Definition geom.cpp:747
Specific geometry functions for Inkscape, not provided my lib2geom.
SPItem * item
Interpolators for lists of points.
PowerStroke LPE effect, see lpe-powerstroke.cpp.
Geom::Point start
Geom::Point end
Various utility functions.
Definition affine.h:22
Path path_from_sbasis(D2< SBasis > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
Coord length(LineSegment const &seg)
D2< Piecewise< SBasis > > make_cuts_independent(Piecewise< D2< SBasis > > const &a)
Definition d2-sbasis.cpp:75
Bezier reverse(const Bezier &a)
Definition bezier.h:342
SBasisN< n > divide(SBasisN< n > const &a, SBasisN< n > const &b, int k)
Piecewise< D2< SBasis > > paths_to_pw(PathVector const &paths)
Definition path.cpp:1123
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
Coord nearest_time(Point const &p, Curve const &c)
Definition curve.h:354
Coord distanceSq(Point const &p, Rect const &rect)
Definition rect.cpp:158
void build_from_sbasis(PathBuilder &pb, D2< SBasis > const &B, double tol, bool only_cubicbeziers)
Make a path from a d2 sbasis.
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
static float sign(double number)
Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise.
static Point intersection_point(Point origin_a, Point vector_a, Point origin_b, Point vector_b)
static Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2< Geom::SBasis > const &sbasis_in)
D2< T > compose(D2< T > const &a, T const &b)
Definition d2.h:405
std::vector< double > roots(SBasis const &s)
std::vector< Crossing > Crossings
Definition crossing.h:126
Crossings crossings(Curve const &a, Curve const &b)
static Circle touching_circle(D2< SBasis > const &curve, double t, double tol=0.01)
Find circle that touches inside of the curve, with radius matching the curvature, at time value t.
void sbasis_to_bezier(Bezier &bz, SBasis const &sb, size_t sz=0)
Changes the basis of p to be bernstein.
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
Point unitTangentAt(D2< SBasis > const &a, Coord t, unsigned n=3)
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
OptInterval bounds_fast(Bezier const &b)
Definition bezier.cpp:305
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
static Ellipse find_ellipse(Point P, Point Q, Point O)
document this! very quick: this finds the ellipse with minimum eccentricity passing through point P a...
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Point middle_point(LineSegment const &_segment)
Piecewise< D2< SBasis > > unitVector(D2< SBasis > const &vect, double tol=.01, unsigned order=3)
static Geom::Path path_from_piecewise_fix_cusps(Geom::Piecewise< Geom::D2< Geom::SBasis > > const &B, Geom::Piecewise< Geom::SBasis > const &y, LineJoinType jointype, double miter_limit, double tol=Geom::EPSILON)
void lpe_shape_convert_stroke_and_fill(SPShape *shape)
Prepares a SPShape's fill and stroke for use in a path effect by setting the existing stroke properti...
void lpe_shape_revert_stroke_and_fill(SPShape *shape, double width)
Applies the fill of the SPShape to its stroke, sets the stroke width to the provided parameter,...
static bool compare_offsets(Geom::Point first, Geom::Point second)
static const Util::EnumDataConverter< unsigned > LineCapTypeConverter(LineCapTypeData, sizeof(LineCapTypeData)/sizeof(*LineCapTypeData))
static const Util::EnumData< unsigned > LineJoinTypeData[]
static const Util::EnumDataConverter< unsigned > LineJoinTypeConverter(LineJoinTypeData, sizeof(LineJoinTypeData)/sizeof(*LineJoinTypeData))
static const Util::EnumData< unsigned > InterpolatorTypeData[]
static const Util::EnumDataConverter< unsigned > InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData))
Helper class to stream background task notifications as a series of messages.
Geom::Path spiro_run(const spiro_cp *src, int src_len)
Definition spiro.cpp:23
Path intersection.
callback interface for SVG path data
Singleton class to access the preferences file in a convenient way.
auto len
Definition safe-printf.h:21
std::vector< double > & solutions
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:27
double y
Definition spiro.h:22
double x
Definition spiro.h:21
Definition curve.h:24
SPStyle - a style object for SPItem objects.
double width
Glib::RefPtr< Gtk::Adjustment > smooth