summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohan B. C. Engelen <jbc.engelen@swissonline.ch>2007-08-14 20:54:48 +0000
committerjohanengelen <johanengelen@users.sourceforge.net>2007-08-14 20:54:48 +0000
commit55d43e4e27e0ba58a47fad70957dfa989aa173ad (patch)
tree2ccfbac1c50023d08ae32975c876fa2478c1ad2a /src
parentFix for bug #1752113; added set_preview_widget_active(false) to FileSaveDialo... (diff)
downloadinkscape-55d43e4e27e0ba58a47fad70957dfa989aa173ad.tar.gz
inkscape-55d43e4e27e0ba58a47fad70957dfa989aa173ad.zip
Commit LivePathEffect branch to trunk!
(disabled extension/internal/bitmap/*.* in build.xml to fix compilation) (bzr r3472)
Diffstat (limited to 'src')
-rw-r--r--src/2geom/Makefile_insert77
-rw-r--r--src/2geom/basic-intersection.cpp359
-rw-r--r--src/2geom/basic-intersection.h34
-rw-r--r--src/2geom/bezier-to-sbasis.h89
-rw-r--r--src/2geom/bezier-utils.cpp1004
-rw-r--r--src/2geom/bezier-utils.h96
-rw-r--r--src/2geom/bezier.h141
-rw-r--r--src/2geom/choose.h67
-rw-r--r--src/2geom/circle-circle.cpp131
-rw-r--r--src/2geom/circulator.h149
-rw-r--r--src/2geom/concepts.h145
-rw-r--r--src/2geom/conjugate_gradient.cpp134
-rw-r--r--src/2geom/conjugate_gradient.h46
-rw-r--r--src/2geom/convex-cover.cpp449
-rw-r--r--src/2geom/convex-cover.h174
-rw-r--r--src/2geom/coord.h66
-rw-r--r--src/2geom/d2.cpp177
-rw-r--r--src/2geom/d2.h475
-rw-r--r--src/2geom/geom.cpp218
-rw-r--r--src/2geom/geom.h68
-rw-r--r--src/2geom/interval.h219
-rw-r--r--src/2geom/isnan.h57
-rw-r--r--src/2geom/linear.h172
-rw-r--r--src/2geom/makefile.in17
-rw-r--r--src/2geom/matrix.cpp243
-rw-r--r--src/2geom/matrix.h158
-rw-r--r--src/2geom/path.cpp367
-rw-r--r--src/2geom/path.h632
-rw-r--r--src/2geom/piecewise.cpp180
-rw-r--r--src/2geom/piecewise.h690
-rw-r--r--src/2geom/point-l.h86
-rw-r--r--src/2geom/point-ops.h25
-rw-r--r--src/2geom/point.cpp164
-rw-r--r--src/2geom/point.h229
-rw-r--r--src/2geom/poly-dk-solve.cpp64
-rw-r--r--src/2geom/poly-dk-solve.h5
-rw-r--r--src/2geom/poly-laguerre-solve.cpp147
-rw-r--r--src/2geom/poly-laguerre-solve.h10
-rw-r--r--src/2geom/poly.cpp197
-rw-r--r--src/2geom/poly.h219
-rw-r--r--src/2geom/quadtree.cpp131
-rw-r--r--src/2geom/quadtree.h41
-rw-r--r--src/2geom/rect.h97
-rw-r--r--src/2geom/sbasis-2d.cpp72
-rw-r--r--src/2geom/sbasis-2d.h325
-rw-r--r--src/2geom/sbasis-geometric.cpp378
-rw-r--r--src/2geom/sbasis-geometric.h83
-rw-r--r--src/2geom/sbasis-math.cpp289
-rw-r--r--src/2geom/sbasis-math.h92
-rw-r--r--src/2geom/sbasis-poly.cpp45
-rw-r--r--src/2geom/sbasis-poly.h29
-rw-r--r--src/2geom/sbasis-roots.cpp352
-rw-r--r--src/2geom/sbasis-to-bezier.cpp214
-rw-r--r--src/2geom/sbasis-to-bezier.h20
-rw-r--r--src/2geom/sbasis.cpp490
-rw-r--r--src/2geom/sbasis.h301
-rw-r--r--src/2geom/solve-bezier-one-d.cpp268
-rw-r--r--src/2geom/solve-bezier-parametric.cpp227
-rw-r--r--src/2geom/solver.h38
-rw-r--r--src/2geom/sturm.h62
-rw-r--r--src/2geom/svg-path-parser.cpp1603
-rw-r--r--src/2geom/svg-path-parser.h81
-rw-r--r--src/2geom/svg-path.cpp97
-rw-r--r--src/2geom/svg-path.h126
-rw-r--r--src/2geom/transforms.cpp48
-rw-r--r--src/2geom/transforms.h131
-rw-r--r--src/2geom/utils.h79
-rw-r--r--src/Makefile.am9
-rw-r--r--src/Makefile_insert3
-rw-r--r--src/attributes.cpp4
-rw-r--r--src/attributes.h6
-rw-r--r--src/live_effects/Makefile_insert27
-rw-r--r--src/live_effects/effect.cpp232
-rw-r--r--src/live_effects/effect.h108
-rw-r--r--src/live_effects/lpe-gears.cpp266
-rw-r--r--src/live_effects/lpe-gears.h38
-rw-r--r--src/live_effects/lpe-skeletalstrokes.cpp161
-rw-r--r--src/live_effects/lpe-skeletalstrokes.h49
-rw-r--r--src/live_effects/lpe-slant.cpp62
-rw-r--r--src/live_effects/lpe-slant.h40
-rw-r--r--src/live_effects/lpe-test-doEffect-stack.cpp96
-rw-r--r--src/live_effects/lpe-test-doEffect-stack.h42
-rw-r--r--src/live_effects/lpeobject-reference.cpp161
-rw-r--r--src/live_effects/lpeobject-reference.h71
-rw-r--r--src/live_effects/lpeobject.cpp260
-rw-r--r--src/live_effects/lpeobject.h52
-rw-r--r--src/live_effects/makefile.in17
-rw-r--r--src/live_effects/n-art-bpath-2geom.cpp416
-rw-r--r--src/live_effects/n-art-bpath-2geom.h33
-rw-r--r--src/live_effects/parameter/Makefile_insert18
-rw-r--r--src/live_effects/parameter/enum.h87
-rw-r--r--src/live_effects/parameter/makefile.in17
-rw-r--r--src/live_effects/parameter/parameter.cpp105
-rw-r--r--src/live_effects/parameter/parameter.h84
-rw-r--r--src/live_effects/parameter/path.cpp166
-rw-r--r--src/live_effects/parameter/path.h56
-rw-r--r--src/live_effects/parameter/point.cpp166
-rw-r--r--src/live_effects/parameter/point.h57
-rw-r--r--src/live_effects/todo.txt19
-rw-r--r--src/menus-skeleton.h2
-rw-r--r--src/node-context.cpp224
-rw-r--r--src/nodepath.cpp202
-rw-r--r--src/nodepath.h17
-rw-r--r--src/selection-chemistry.cpp5
-rw-r--r--src/selection-chemistry.h1
-rw-r--r--src/shape-editor.cpp36
-rw-r--r--src/shape-editor.h1
-rw-r--r--src/sp-ellipse.cpp29
-rw-r--r--src/sp-object-repr.cpp5
-rw-r--r--src/sp-object.cpp1
-rw-r--r--src/sp-path.cpp187
-rw-r--r--src/sp-path.h6
-rw-r--r--src/sp-shape.cpp179
-rw-r--r--src/sp-shape.h22
-rw-r--r--src/sp-spiral.cpp34
-rw-r--r--src/sp-star.cpp33
-rw-r--r--src/ui/dialog/Makefile_insert2
-rw-r--r--src/ui/dialog/dialog-manager.cpp2
-rw-r--r--src/ui/dialog/livepatheffect-editor.cpp290
-rw-r--r--src/ui/dialog/livepatheffect-editor.h85
-rw-r--r--src/ui/widget/Makefile_insert3
-rw-r--r--src/ui/widget/combo-enums.h36
-rw-r--r--src/ui/widget/point.cpp237
-rw-r--r--src/ui/widget/point.h96
-rw-r--r--src/ui/widget/registered-enums.h110
-rw-r--r--src/ui/widget/registered-widget.cpp323
-rw-r--r--src/ui/widget/registered-widget.h187
-rw-r--r--src/verbs.cpp5
-rw-r--r--src/verbs.h1
129 files changed, 18593 insertions, 395 deletions
diff --git a/src/2geom/Makefile_insert b/src/2geom/Makefile_insert
new file mode 100644
index 000000000..a100c418d
--- /dev/null
+++ b/src/2geom/Makefile_insert
@@ -0,0 +1,77 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+2geom/all: 2geom/lib2geom.a
+
+2geom/clean:
+ rm -f 2geom/lib2geom.a $(2geom_lib2geom_a_OBJECTS)
+
+2geom_lib2geom_a_SOURCES = \
+ 2geom/bezier-to-sbasis.h \
+ 2geom/bezier-utils.cpp \
+ 2geom/bezier-utils.h \
+ 2geom/choose.h \
+ 2geom/circle-circle.cpp \
+ 2geom/circulator.h \
+ 2geom/concepts.h \
+ 2geom/conjugate_gradient.cpp \
+ 2geom/conjugate_gradient.h \
+ 2geom/convex-cover.cpp \
+ 2geom/convex-cover.h \
+ 2geom/coord.h \
+ 2geom/d2.cpp \
+ 2geom/d2.h \
+ 2geom/geom.cpp \
+ 2geom/geom.h \
+ 2geom/interval.h \
+ 2geom/isnan.h \
+ 2geom/linear.h \
+ 2geom/matrix.cpp \
+ 2geom/matrix.h \
+ 2geom/path.cpp \
+ 2geom/path.h \
+ 2geom/piecewise.cpp \
+ 2geom/piecewise.h \
+ 2geom/point.cpp \
+ 2geom/point.h \
+ 2geom/point-l.h \
+ 2geom/point-ops.h \
+ 2geom/poly.cpp \
+ 2geom/poly.h \
+ 2geom/poly-dk-solve.cpp \
+ 2geom/poly-dk-solve.h \
+ 2geom/poly-laguerre-solve.cpp \
+ 2geom/poly-laguerre-solve.h \
+ 2geom/quadtree.cpp \
+ 2geom/quadtree.h \
+ 2geom/rect.h \
+ 2geom/sbasis-2d.cpp \
+ 2geom/sbasis-2d.h \
+ 2geom/sbasis.cpp \
+ 2geom/sbasis.h \
+ 2geom/sbasis-geometric.cpp \
+ 2geom/sbasis-geometric.h \
+ 2geom/sbasis-math.cpp \
+ 2geom/sbasis-math.h \
+ 2geom/sbasis-poly.cpp \
+ 2geom/sbasis-poly.h \
+ 2geom/sbasis-roots.cpp \
+ 2geom/sbasis-to-bezier.cpp \
+ 2geom/sbasis-to-bezier.h \
+ 2geom/solve-bezier-one-d.cpp \
+ 2geom/solve-bezier-parametric.cpp \
+ 2geom/solver.h \
+ 2geom/sturm.h \
+ 2geom/svg-path.cpp \
+ 2geom/svg-path.h \
+ 2geom/svg-path-parser.cpp \
+ 2geom/svg-path-parser.h \
+ 2geom/transforms.cpp \
+ 2geom/transforms.h \
+ 2geom/utils.h \
+ 2geom/basic-intersection.cpp \
+ 2geom/basic-intersection.h \
+ 2geom/bezier.h
+
+
+
+
diff --git a/src/2geom/basic-intersection.cpp b/src/2geom/basic-intersection.cpp
new file mode 100644
index 000000000..a5b827023
--- /dev/null
+++ b/src/2geom/basic-intersection.cpp
@@ -0,0 +1,359 @@
+#include "basic-intersection.h"
+
+using std::vector;
+namespace Geom {
+
+class OldBezier {
+public:
+ std::vector<Geom::Point> p;
+ OldBezier() {
+ }
+ void split(double t, OldBezier &a, OldBezier &b) const;
+
+ ~OldBezier() {}
+
+ void bounds(double &minax, double &maxax,
+ double &minay, double &maxay) {
+ // Compute bounding box for a
+ minax = p[0][X]; // These are the most likely to be extremal
+ maxax = p.back()[X];
+ if( minax > maxax )
+ std::swap(minax, maxax);
+ for(unsigned i = 1; i < p.size()-1; i++) {
+ if( p[i][X] < minax )
+ minax = p[i][X];
+ else if( p[i][X] > maxax )
+ maxax = p[i][X];
+ }
+
+ minay = p[0][Y]; // These are the most likely to be extremal
+ maxay = p.back()[Y];
+ if( minay > maxay )
+ std::swap(minay, maxay);
+ for(unsigned i = 1; i < p.size()-1; i++) {
+ if( p[i][Y] < minay )
+ minay = p[i][Y];
+ else if( p[i][Y] > maxay )
+ maxay = p[i][Y];
+ }
+
+ }
+
+};
+
+static std::vector<std::pair<double, double> >
+find_intersections( OldBezier a, OldBezier b);
+
+static std::vector<std::pair<double, double> >
+find_self_intersections(OldBezier const &Sb, D2<SBasis> const & A);
+
+std::vector<std::pair<double, double> >
+find_intersections( vector<Geom::Point> const & A,
+ vector<Geom::Point> const & B) {
+ OldBezier a, b;
+ a.p = A;
+ b.p = B;
+ return find_intersections(a,b);
+}
+
+std::vector<std::pair<double, double> >
+find_self_intersections(OldBezier const &Sb) {
+ throw NotImplemented();
+}
+
+std::vector<std::pair<double, double> >
+find_self_intersections(D2<SBasis> const & A) {
+ OldBezier Sb;
+ Sb.p = sbasis_to_bezier(A);
+ return find_self_intersections(Sb, A);
+}
+
+
+static std::vector<std::pair<double, double> >
+find_self_intersections(OldBezier const &Sb, D2<SBasis> const & A) {
+
+
+ vector<double> dr = roots(derivative(A[X]));
+ {
+ vector<double> dyr = roots(derivative(A[Y]));
+ dr.insert(dr.begin(), dyr.begin(), dyr.end());
+ }
+ dr.push_back(0);
+ dr.push_back(1);
+ // We want to be sure that we have no empty segments
+ sort(dr.begin(), dr.end());
+ unique(dr.begin(), dr.end());
+
+ std::vector<std::pair<double, double> > all_si;
+
+ vector<OldBezier> pieces;
+ {
+ OldBezier in = Sb, l, r;
+ for(unsigned i = 0; i < dr.size()-1; i++) {
+ in.split((dr[i+1]-dr[i]) / (1 - dr[i]), l, r);
+ pieces.push_back(l);
+ in = r;
+ }
+ }
+ for(unsigned i = 0; i < dr.size()-1; i++) {
+ for(unsigned j = i+1; j < dr.size()-1; j++) {
+ std::vector<std::pair<double, double> > section =
+ find_intersections( pieces[i], pieces[j]);
+ for(unsigned k = 0; k < section.size(); k++) {
+ double l = section[k].first;
+ double r = section[k].second;
+// XXX: This condition will prune out false positives, but it might create some false negatives. Todo: Confirm it is correct.
+ if(j == i+1)
+ if((l == 1) && (r == 0))
+ continue;
+ all_si.push_back(std::make_pair((1-l)*dr[i] + l*dr[i+1],
+ (1-r)*dr[j] + r*dr[j+1]));
+ }
+ }
+ }
+
+ // Because i is in order, all_si should be roughly already in order?
+ //sort(all_si.begin(), all_si.end());
+ //unique(all_si.begin(), all_si.end());
+
+ return all_si;
+}
+
+/* The value of 1.0 / (1L<<14) is enough for most applications */
+const double INV_EPS = (1L<<14);
+
+/*
+ * split the curve at the midpoint, returning an array with the two parts
+ * Temporary storage is minimized by using part of the storage for the result
+ * to hold an intermediate value until it is no longer needed.
+ */
+void OldBezier::split(double t, OldBezier &left, OldBezier &right) const {
+ const unsigned sz = p.size();
+ Geom::Point Vtemp[sz][sz];
+
+ /* Copy control points */
+ std::copy(p.begin(), p.end(), Vtemp[0]);
+
+ /* Triangle computation */
+ for (unsigned i = 1; i < sz; i++) {
+ for (unsigned j = 0; j < sz - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+
+ left.p.resize(sz);
+ right.p.resize(sz);
+ for (unsigned j = 0; j < sz; j++)
+ left.p[j] = Vtemp[j][0];
+ for (unsigned j = 0; j < sz; j++)
+ right.p[j] = Vtemp[sz-1-j][j];
+}
+
+
+/*
+ * Test the bounding boxes of two OldBezier curves for interference.
+ * Several observations:
+ * First, it is cheaper to compute the bounding box of the second curve
+ * and test its bounding box for interference than to use a more direct
+ * approach of comparing all control points of the second curve with
+ * the various edges of the bounding box of the first curve to test
+ * for interference.
+ * Second, after a few subdivisions it is highly probable that two corners
+ * of the bounding box of a given OldBezier curve are the first and last
+ * control point. Once this happens once, it happens for all subsequent
+ * subcurves. It might be worth putting in a test and then short-circuit
+ * code for further subdivision levels.
+ * Third, in the final comparison (the interference test) the comparisons
+ * should both permit equality. We want to find intersections even if they
+ * occur at the ends of segments.
+ * Finally, there are tighter bounding boxes that can be derived. It isn't
+ * clear whether the higher probability of rejection (and hence fewer
+ * subdivisions and tests) is worth the extra work.
+ */
+
+bool intersect_BB( OldBezier a, OldBezier b ) {
+ double minax, maxax, minay, maxay;
+ a.bounds(minax, maxax, minay, maxay);
+ double minbx, maxbx, minby, maxby;
+ b.bounds(minbx, maxbx, minby, maxby);
+ // Test bounding box of b against bounding box of a
+ // Not >= : need boundary case
+ return not( ( minax > maxbx ) || ( minay > maxby )
+ || ( minbx > maxax ) || ( minby > maxay ) );
+}
+
+/*
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found. The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference. If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected. (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another. Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
+void recursively_intersect( OldBezier a, double t0, double t1, int deptha,
+ OldBezier b, double u0, double u1, int depthb,
+ std::vector<std::pair<double, double> > &parameters)
+{
+ if( deptha > 0 )
+ {
+ OldBezier A[2];
+ a.split(0.5, A[0], A[1]);
+ double tmid = (t0+t1)*0.5;
+ deptha--;
+ if( depthb > 0 )
+ {
+ OldBezier B[2];
+ b.split(0.5, B[0], B[1]);
+ double umid = (u0+u1)*0.5;
+ depthb--;
+ if( intersect_BB( A[0], B[0] ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( A[1], B[0] ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( A[0], B[1] ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ B[1], umid, u1, depthb,
+ parameters );
+ if( intersect_BB( A[1], B[1] ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ B[1], umid, u1, depthb,
+ parameters );
+ }
+ else
+ {
+ if( intersect_BB( A[0], b ) )
+ recursively_intersect( A[0], t0, tmid, deptha,
+ b, u0, u1, depthb,
+ parameters );
+ if( intersect_BB( A[1], b ) )
+ recursively_intersect( A[1], tmid, t1, deptha,
+ b, u0, u1, depthb,
+ parameters );
+ }
+ }
+ else
+ if( depthb > 0 )
+ {
+ OldBezier B[2];
+ b.split(0.5, B[0], B[1]);
+ double umid = (u0 + u1)*0.5;
+ depthb--;
+ if( intersect_BB( a, B[0] ) )
+ recursively_intersect( a, t0, t1, deptha,
+ B[0], u0, umid, depthb,
+ parameters );
+ if( intersect_BB( a, B[1] ) )
+ recursively_intersect( a, t0, t1, deptha,
+ B[0], umid, u1, depthb,
+ parameters );
+ }
+ else // Both segments are fully subdivided; now do line segments
+ {
+ double xlk = a.p.back()[X] - a.p[0][X];
+ double ylk = a.p.back()[Y] - a.p[0][Y];
+ double xnm = b.p.back()[X] - b.p[0][X];
+ double ynm = b.p.back()[Y] - b.p[0][Y];
+ double xmk = b.p[0][X] - a.p[0][X];
+ double ymk = b.p[0][Y] - a.p[0][Y];
+ double det = xnm * ylk - ynm * xlk;
+ if( 1.0 + det == 1.0 )
+ return;
+ else
+ {
+ double detinv = 1.0 / det;
+ double s = ( xnm * ymk - ynm *xmk ) * detinv;
+ double t = ( xlk * ymk - ylk * xmk ) * detinv;
+ if( ( s < 0.0 ) || ( s > 1.0 ) || ( t < 0.0 ) || ( t > 1.0 ) )
+ return;
+ parameters.push_back(std::pair<double, double>(t0 + s * ( t1 - t0 ),
+ u0 + t * ( u1 - u0 )));
+ }
+ }
+}
+
+inline double log4( double x ) { return log(x)/log(4.); }
+
+/*
+ * Wang's theorem is used to estimate the level of subdivision required,
+ * but only if the bounding boxes interfere at the top level.
+ * Assuming there is a possible intersection, recursively_intersect is
+ * used to find all the parameters corresponding to intersection points.
+ * these are then sorted and returned in an array.
+ */
+
+double Lmax(Point p) {
+ return std::max(fabs(p[X]), fabs(p[Y]));
+}
+
+unsigned wangs_theorem(OldBezier a) {
+ return 12; // seems a good approximation!
+ double la1 = Lmax( ( a.p[2] - a.p[1] ) - (a.p[1] - a.p[0]) );
+ double la2 = Lmax( ( a.p[3] - a.p[2] ) - (a.p[2] - a.p[1]) );
+ double l0 = std::max(la1, la2);
+ unsigned ra;
+ if( l0 * 0.75 * M_SQRT2 + 1.0 == 1.0 )
+ ra = 0;
+ else
+ ra = (unsigned)ceil( log4( M_SQRT2 * 6.0 / 8.0 * INV_EPS * l0 ) );
+ std::cout << ra << std::endl;
+ return ra;
+}
+
+std::vector<std::pair<double, double> > find_intersections( OldBezier a, OldBezier b)
+{
+ std::vector<std::pair<double, double> > parameters;
+ if( intersect_BB( a, b ) )
+ {
+ recursively_intersect( a, 0., 1., wangs_theorem(a),
+ b, 0., 1., wangs_theorem(b),
+ parameters);
+ }
+ std::sort(parameters.begin(), parameters.end());
+ return parameters;
+}
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/basic-intersection.h b/src/2geom/basic-intersection.h
new file mode 100644
index 000000000..76abcce2a
--- /dev/null
+++ b/src/2geom/basic-intersection.h
@@ -0,0 +1,34 @@
+#include "sbasis.h"
+#include "bezier-to-sbasis.h"
+#include "sbasis-to-bezier.h"
+#include "d2.h"
+
+namespace Geom {
+
+std::vector<std::pair<double, double> >
+find_intersections( D2<SBasis> const & A,
+ D2<SBasis> const & B);
+
+std::vector<std::pair<double, double> >
+find_self_intersections(D2<SBasis> const & A);
+
+// Bezier form
+std::vector<std::pair<double, double> >
+find_intersections( std::vector<Point> const & A,
+ std::vector<Point> const & B);
+
+std::vector<std::pair<double, double> >
+find_self_intersections(std::vector<Point> const & A);
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/bezier-to-sbasis.h b/src/2geom/bezier-to-sbasis.h
new file mode 100644
index 000000000..03c99a9bd
--- /dev/null
+++ b/src/2geom/bezier-to-sbasis.h
@@ -0,0 +1,89 @@
+/*
+ * bezier-to-sbasis.h
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef _BEZIER_TO_SBASIS
+#define _BEZIER_TO_SBASIS
+
+#include "coord.h"
+
+#include "d2.h"
+#include "point.h"
+
+namespace Geom{
+
+template <unsigned order>
+struct bezier_to_sbasis_impl {
+ static inline SBasis compute(Coord const *handles) {
+ return multiply(Linear(1, 0), bezier_to_sbasis_impl<order-1>::compute(handles)) +
+ multiply(Linear(0, 1), bezier_to_sbasis_impl<order-1>::compute(handles+1));
+ }
+};
+
+template <>
+struct bezier_to_sbasis_impl<1> {
+ static inline SBasis compute(Coord const *handles) {
+ return Linear(handles[0], handles[1]);
+ }
+};
+
+template <>
+struct bezier_to_sbasis_impl<0> {
+ static inline SBasis compute(Coord const *handles) {
+ return Linear(handles[0], handles[0]);
+ }
+};
+
+template <unsigned order>
+inline SBasis bezier_to_sbasis(Coord const *handles) {
+ return bezier_to_sbasis_impl<order>::compute(handles);
+}
+
+template <unsigned order, typename T>
+inline D2<SBasis> handles_to_sbasis(T const &handles) {
+ double v[2][order+1];
+ for(unsigned i = 0; i <= order; i++)
+ for(unsigned j = 0; j < 2; j++)
+ v[j][i] = handles[i][j];
+ return D2<SBasis>(bezier_to_sbasis<order>(v[0]),
+ bezier_to_sbasis<order>(v[1]));
+}
+
+};
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/bezier-utils.cpp b/src/2geom/bezier-utils.cpp
new file mode 100644
index 000000000..76c90a915
--- /dev/null
+++ b/src/2geom/bezier-utils.cpp
@@ -0,0 +1,1004 @@
+#define __SP_BEZIER_UTILS_C__
+
+/** \file
+ * Bezier interpolation for inkscape drawing code.
+ */
+/*
+ * Original code published in:
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Peter Moulder <pmoulder@mail.csse.monash.edu.au>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ * Copyright (C) 2003,2004 Monash University
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#define SP_HUGE 1e5
+#define noBEZIER_DEBUG
+
+#ifdef HAVE_IEEEFP_H
+# include <ieefp.h>
+#endif
+
+#include "bezier-utils.h"
+
+#include "isnan.h"
+#include <assert.h>
+
+namespace Geom{
+
+typedef Point BezierCurve[];
+
+/* Forward declarations */
+static void generate_bezier(Point b[], Point const d[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2, double tolerance_sq);
+static void estimate_lengths(Point bezier[],
+ Point const data[], double const u[], unsigned len,
+ Point const &tHat1, Point const &tHat2);
+static void estimate_bi(Point b[4], unsigned ei,
+ Point const data[], double const u[], unsigned len);
+static void reparameterize(Point const d[], unsigned len, double u[], BezierCurve const bezCurve);
+static double NewtonRaphsonRootFind(BezierCurve const Q, Point const &P, double u);
+static Point darray_center_tangent(Point const d[], unsigned center, unsigned length);
+static Point darray_right_tangent(Point const d[], unsigned const len);
+static unsigned copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[]);
+static void chord_length_parameterize(Point const d[], double u[], unsigned len);
+static double compute_max_error_ratio(Point const d[], double const u[], unsigned len,
+ BezierCurve const bezCurve, double tolerance,
+ unsigned *splitPoint);
+static double compute_hook(Point const &a, Point const &b, double const u, BezierCurve const bezCurve,
+ double const tolerance);
+
+
+static Point const unconstrained_tangent(0, 0);
+
+
+/*
+ * B0, B1, B2, B3 : Bezier multipliers
+ */
+
+#define B0(u) ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B1(u) ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) )
+#define B2(u) ( 3 * u * u * ( 1.0 - u ) )
+#define B3(u) ( u * u * u )
+
+#ifdef BEZIER_DEBUG
+# define DOUBLE_ASSERT(x) assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) )
+# define BEZIER_ASSERT(b) do { \
+ DOUBLE_ASSERT((b)[0][X]); DOUBLE_ASSERT((b)[0][Y]); \
+ DOUBLE_ASSERT((b)[1][X]); DOUBLE_ASSERT((b)[1][Y]); \
+ DOUBLE_ASSERT((b)[2][X]); DOUBLE_ASSERT((b)[2][Y]); \
+ DOUBLE_ASSERT((b)[3][X]); DOUBLE_ASSERT((b)[3][Y]); \
+ } while(0)
+#else
+# define DOUBLE_ASSERT(x) do { } while(0)
+# define BEZIER_ASSERT(b) do { } while(0)
+#endif
+
+
+/**
+ * Fit a single-segment Bezier curve to a set of digitized points.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic(Point *bezier, Point const *data, int len, double error)
+{
+ return bezier_fit_cubic_r(bezier, data, len, error, 1);
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, with
+ * possible weedout of identical points and NaNs.
+ *
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ *
+ * \return Number of segments generated, or -1 on error.
+ */
+int
+bezier_fit_cubic_r(Point bezier[], Point const data[], int const len, double const error, unsigned const max_beziers)
+{
+ if(bezier == NULL ||
+ data == NULL ||
+ len <= 0 ||
+ max_beziers >= (1ul << (31 - 2 - 1 - 3)))
+ return -1;
+
+ Point *uniqued_data = new Point[len];
+ unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data);
+
+ if ( uniqued_len < 2 ) {
+ delete[] uniqued_data;
+ return 0;
+ }
+
+ /* Call fit-cubic function with recursion. */
+ int const ret = bezier_fit_cubic_full(bezier, NULL, uniqued_data, uniqued_len,
+ unconstrained_tangent, unconstrained_tangent,
+ error, max_beziers);
+ delete[] uniqued_data;
+ return ret;
+}
+
+/**
+ * Copy points from src to dest, filter out points containing NaN and
+ * adjacent points with equal x and y.
+ * \return length of dest
+ */
+static unsigned
+copy_without_nans_or_adjacent_duplicates(Point const src[], unsigned src_len, Point dest[])
+{
+ unsigned si = 0;
+ for (;;) {
+ if ( si == src_len ) {
+ return 0;
+ }
+ if (!is_nan(src[si][X]) &&
+ !is_nan(src[si][Y])) {
+ dest[0] = Point(src[si]);
+ ++si;
+ break;
+ }
+ }
+ unsigned di = 0;
+ for (; si < src_len; ++si) {
+ Point const src_pt = Point(src[si]);
+ if ( src_pt != dest[di]
+ && !is_nan(src_pt[X])
+ && !is_nan(src_pt[Y])) {
+ dest[++di] = src_pt;
+ }
+ }
+ unsigned dest_len = di + 1;
+ assert( dest_len <= src_len );
+ return dest_len;
+}
+
+/**
+ * Fit a multi-segment Bezier curve to a set of digitized points, without
+ * possible weedout of identical points and NaNs.
+ *
+ * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1].
+ * \param max_beziers Maximum number of generated segments
+ * \param Result array, must be large enough for n. segments * 4 elements.
+ */
+int
+bezier_fit_cubic_full(Point bezier[], int split_points[],
+ Point const data[], int const len,
+ Point const &tHat1, Point const &tHat2,
+ double const error, unsigned const max_beziers)
+{
+ int const maxIterations = 4; /* std::max times to try iterating */
+
+ if(!(bezier != NULL) ||
+ !(data != NULL) ||
+ !(len > 0) ||
+ !(max_beziers >= 1) ||
+ !(error >= 0.0))
+ return -1;
+
+ if ( len < 2 ) return 0;
+
+ if ( len == 2 ) {
+ /* We have 2 points, which can be fitted trivially. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+ double const dist = distance(bezier[0], bezier[3]) / 3.0;
+ if (is_nan(dist)) {
+ /* Numerical problem, fall back to straight line segment. */
+ bezier[1] = bezier[0];
+ bezier[2] = bezier[3];
+ } else {
+ bezier[1] = ( is_zero(tHat1)
+ ? ( 2 * bezier[0] + bezier[3] ) / 3.
+ : bezier[0] + dist * tHat1 );
+ bezier[2] = ( is_zero(tHat2)
+ ? ( bezier[0] + 2 * bezier[3] ) / 3.
+ : bezier[3] + dist * tHat2 );
+ }
+ BEZIER_ASSERT(bezier);
+ return 1;
+ }
+
+ /* Parameterize points, and attempt to fit curve */
+ unsigned splitPoint; /* Point to split point set at. */
+ bool is_corner;
+ {
+ double *u = new double[len];
+ chord_length_parameterize(data, u, len);
+ if ( u[len - 1] == 0.0 ) {
+ /* Zero-length path: every point in data[] is the same.
+ *
+ * (Clients aren't allowed to pass such data; handling the case is defensive
+ * programming.)
+ */
+ delete[] u;
+ return 0;
+ }
+
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+
+ /* Find max deviation of points to fitted curve. */
+ double const tolerance = sqrt(error + 1e-9);
+ double maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+
+ /* If error not too large, then try some reparameterization and iteration. */
+ if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) {
+ for (int i = 0; i < maxIterations; i++) {
+ generate_bezier(bezier, data, u, len, tHat1, tHat2, error);
+ reparameterize(data, len, u, bezier);
+ maxErrorRatio = compute_max_error_ratio(data, u, len, bezier, tolerance, &splitPoint);
+ if ( fabs(maxErrorRatio) <= 1.0 ) {
+ BEZIER_ASSERT(bezier);
+ delete[] u;
+ return 1;
+ }
+ }
+ }
+ delete[] u;
+ is_corner = (maxErrorRatio < 0);
+ }
+
+ if (is_corner) {
+ assert(splitPoint < unsigned(len));
+ if (splitPoint == 0) {
+ if (is_zero(tHat1)) {
+ /* Got spike even with unconstrained initial tangent. */
+ ++splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2,
+ error, max_beziers);
+ }
+ } else if (splitPoint == unsigned(len - 1)) {
+ if (is_zero(tHat2)) {
+ /* Got spike even with unconstrained final tangent. */
+ --splitPoint;
+ } else {
+ return bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent,
+ error, max_beziers);
+ }
+ }
+ }
+
+ if ( 1 < max_beziers ) {
+ /*
+ * Fitting failed -- split at max error point and fit recursively
+ */
+ unsigned const rec_max_beziers1 = max_beziers - 1;
+
+ Point recTHat2, recTHat1;
+ if (is_corner) {
+ if(!(0 < splitPoint && splitPoint < unsigned(len - 1)))
+ return -1;
+ recTHat1 = recTHat2 = unconstrained_tangent;
+ } else {
+ /* Unit tangent vector at splitPoint. */
+ recTHat2 = darray_center_tangent(data, splitPoint, len);
+ recTHat1 = -recTHat2;
+ }
+ int const nsegs1 = bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1,
+ tHat1, recTHat2, error, rec_max_beziers1);
+ if ( nsegs1 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[1]: recursive call failed\n");
+#endif
+ return -1;
+ }
+ assert( nsegs1 != 0 );
+ if (split_points != NULL) {
+ split_points[nsegs1 - 1] = splitPoint;
+ }
+ unsigned const rec_max_beziers2 = max_beziers - nsegs1;
+ int const nsegs2 = bezier_fit_cubic_full(bezier + nsegs1*4,
+ ( split_points == NULL
+ ? NULL
+ : split_points + nsegs1 ),
+ data + splitPoint, len - splitPoint,
+ recTHat1, tHat2, error, rec_max_beziers2);
+ if ( nsegs2 < 0 ) {
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic[2]: recursive call failed\n");
+#endif
+ return -1;
+ }
+
+#ifdef BEZIER_DEBUG
+ g_print("fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n",
+ nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers);
+#endif
+ return nsegs1 + nsegs2;
+ } else {
+ return -1;
+ }
+}
+
+
+/**
+ * Fill in \a bezier[] based on the given data and tangent requirements, using
+ * a least-squares fit.
+ *
+ * Each of tHat1 and tHat2 should be either a zero vector or a unit vector.
+ * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise,
+ * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3].
+ *
+ * \param tolerance_sq Used only for an initial guess as to tangent directions
+ * when \a tHat1 or \a tHat2 is zero.
+ */
+static void
+generate_bezier(Point bezier[],
+ Point const data[], double const u[], unsigned const len,
+ Point const &tHat1, Point const &tHat2,
+ double const tolerance_sq)
+{
+ bool const est1 = is_zero(tHat1);
+ bool const est2 = is_zero(tHat2);
+ Point est_tHat1( est1
+ ? darray_left_tangent(data, len, tolerance_sq)
+ : tHat1 );
+ Point est_tHat2( est2
+ ? darray_right_tangent(data, len, tolerance_sq)
+ : tHat2 );
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ /* We find that darray_right_tangent tends to produce better results
+ for our current freehand tool than full estimation. */
+ if (est1) {
+ estimate_bi(bezier, 1, data, u, len);
+ if (bezier[1] != bezier[0]) {
+ est_tHat1 = unit_vector(bezier[1] - bezier[0]);
+ }
+ estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2);
+ }
+}
+
+
+static void
+estimate_lengths(Point bezier[],
+ Point const data[], double const uPrime[], unsigned const len,
+ Point const &tHat1, Point const &tHat2)
+{
+ double C[2][2]; /* Matrix C. */
+ double X[2]; /* Matrix X. */
+
+ /* Create the C and X matrices. */
+ C[0][0] = 0.0;
+ C[0][1] = 0.0;
+ C[1][0] = 0.0;
+ C[1][1] = 0.0;
+ X[0] = 0.0;
+ X[1] = 0.0;
+
+ /* First and last control points of the Bezier curve are positioned exactly at the first and
+ last data points. */
+ bezier[0] = data[0];
+ bezier[3] = data[len - 1];
+
+ for (unsigned i = 0; i < len; i++) {
+ /* Bezier control point coefficients. */
+ double const b0 = B0(uPrime[i]);
+ double const b1 = B1(uPrime[i]);
+ double const b2 = B2(uPrime[i]);
+ double const b3 = B3(uPrime[i]);
+
+ /* rhs for eqn */
+ Point const a1 = b1 * tHat1;
+ Point const a2 = b2 * tHat2;
+
+ C[0][0] += dot(a1, a1);
+ C[0][1] += dot(a1, a2);
+ C[1][0] = C[0][1];
+ C[1][1] += dot(a2, a2);
+
+ /* Additional offset to the data point from the predicted point if we were to set bezier[1]
+ to bezier[0] and bezier[2] to bezier[3]. */
+ Point const shortfall
+ = ( data[i]
+ - ( ( b0 + b1 ) * bezier[0] )
+ - ( ( b2 + b3 ) * bezier[3] ) );
+ X[0] += dot(a1, shortfall);
+ X[1] += dot(a2, shortfall);
+ }
+
+ /* We've constructed a pair of equations in the form of a matrix product C * alpha = X.
+ Now solve for alpha. */
+ double alpha_l, alpha_r;
+
+ /* Compute the determinants of C and X. */
+ double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
+ if ( det_C0_C1 != 0 ) {
+ /* Apparently Kramer's rule. */
+ double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+ double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+ alpha_l = det_X_C1 / det_C0_C1;
+ alpha_r = det_C0_X / det_C0_C1;
+ } else {
+ /* The matrix is under-determined. Try requiring alpha_l == alpha_r.
+ *
+ * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same
+ * variable in the equations. We can do this by adding the columns of C to form a single
+ * column, to be multiplied by alpha to give the column vector X.
+ *
+ * We try each row in turn.
+ */
+ double const c0 = C[0][0] + C[0][1];
+ if (c0 != 0) {
+ alpha_l = alpha_r = X[0] / c0;
+ } else {
+ double const c1 = C[1][0] + C[1][1];
+ if (c1 != 0) {
+ alpha_l = alpha_r = X[1] / c1;
+ } else {
+ /* Let the below code handle this. */
+ alpha_l = alpha_r = 0.;
+ }
+ }
+ }
+
+ /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get
+ coincident control points that lead to divide by zero in any subsequent
+ NewtonRaphsonRootFind() call.) */
+ /// \todo Check whether this special-casing is necessary now that
+ /// NewtonRaphsonRootFind handles non-positive denominator.
+ if ( alpha_l < 1.0e-6 ||
+ alpha_r < 1.0e-6 )
+ {
+ alpha_l = alpha_r = distance(data[0], data[len-1]) / 3.0;
+ }
+
+ /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and
+ right, respectively. */
+ bezier[1] = alpha_l * tHat1 + bezier[0];
+ bezier[2] = alpha_r * tHat2 + bezier[3];
+
+ return;
+}
+
+static double lensq(Point const p) {
+ return dot(p, p);
+}
+
+static void
+estimate_bi(Point bezier[4], unsigned const ei,
+ Point const data[], double const u[], unsigned const len)
+{
+ if(!(1 <= ei && ei <= 2))
+ return;
+ unsigned const oi = 3 - ei;
+ double num[2] = {0., 0.};
+ double den = 0.;
+ for (unsigned i = 0; i < len; ++i) {
+ double const ui = u[i];
+ double const b[4] = {
+ B0(ui),
+ B1(ui),
+ B2(ui),
+ B3(ui)
+ };
+
+ for (unsigned d = 0; d < 2; ++d) {
+ num[d] += b[ei] * (b[0] * bezier[0][d] +
+ b[oi] * bezier[oi][d] +
+ b[3] * bezier[3][d] +
+ - data[i][d]);
+ }
+ den -= b[ei] * b[ei];
+ }
+
+ if (den != 0.) {
+ for (unsigned d = 0; d < 2; ++d) {
+ bezier[ei][d] = num[d] / den;
+ }
+ } else {
+ bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) / 3.;
+ }
+}
+
+/**
+ * Given set of points and their parameterization, try to find a better assignment of parameter
+ * values for the points.
+ *
+ * \param d Array of digitized points.
+ * \param u Current parameter values.
+ * \param bezCurve Current fitted curve.
+ * \param len Number of values in both d and u arrays.
+ * Also the size of the array that is allocated for return.
+ */
+static void
+reparameterize(Point const d[],
+ unsigned const len,
+ double u[],
+ BezierCurve const bezCurve)
+{
+ assert( 2 <= len );
+
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* Otherwise, consider including 0 and last in the below loop. */
+
+ for (unsigned i = 1; i < last; i++) {
+ u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]);
+ }
+}
+
+/**
+ * Use Newton-Raphson iteration to find better root.
+ *
+ * \param Q Current fitted curve
+ * \param P Digitized point
+ * \param u Parameter value for "P"
+ *
+ * \return Improved u
+ */
+static double
+NewtonRaphsonRootFind(BezierCurve const Q, Point const &P, double const u)
+{
+ assert( 0.0 <= u );
+ assert( u <= 1.0 );
+
+ /* Generate control vertices for Q'. */
+ Point Q1[3];
+ for (unsigned i = 0; i < 3; i++) {
+ Q1[i] = 3.0 * ( Q[i+1] - Q[i] );
+ }
+
+ /* Generate control vertices for Q''. */
+ Point Q2[2];
+ for (unsigned i = 0; i < 2; i++) {
+ Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] );
+ }
+
+ /* Compute Q(u), Q'(u) and Q''(u). */
+ Point const Q_u = bezier_pt(3, Q, u);
+ Point const Q1_u = bezier_pt(2, Q1, u);
+ Point const Q2_u = bezier_pt(1, Q2, u);
+
+ /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the
+ distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the
+ distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum
+ distance from P to Q(u)). */
+ Point const diff = Q_u - P;
+ double numerator = dot(diff, Q1_u);
+ double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u);
+
+ double improved_u;
+ if ( denominator > 0. ) {
+ /* One iteration of Newton-Raphson:
+ improved_u = u - f(u)/f'(u) */
+ improved_u = u - ( numerator / denominator );
+ } else {
+ /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather
+ than local minimum), so we move an arbitrary amount in the right direction. */
+ if ( numerator > 0. ) {
+ improved_u = u * .98 - .01;
+ } else if ( numerator < 0. ) {
+ /* Deliberately asymmetrical, to reduce the chance of cycling. */
+ improved_u = .031 + u * .98;
+ } else {
+ improved_u = u;
+ }
+ }
+
+ if (!is_finite(improved_u)) {
+ improved_u = u;
+ } else if ( improved_u < 0.0 ) {
+ improved_u = 0.0;
+ } else if ( improved_u > 1.0 ) {
+ improved_u = 1.0;
+ }
+
+ /* Ensure that improved_u isn't actually worse. */
+ {
+ double const diff_lensq = lensq(diff);
+ for (double proportion = .125; ; proportion += .125) {
+ if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) {
+ if ( proportion > 1.0 ) {
+ //g_warning("found proportion %g", proportion);
+ improved_u = u;
+ break;
+ }
+ improved_u = ( ( 1 - proportion ) * improved_u +
+ proportion * u );
+ } else {
+ break;
+ }
+ }
+ }
+
+ DOUBLE_ASSERT(improved_u);
+ return improved_u;
+}
+
+/**
+ * Evaluate a Bezier curve at parameter value \a t.
+ *
+ * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. Must be less
+ * than 4.
+ * \param V The control points for the Bezier curve. Must have (\a degree+1)
+ * elements.
+ * \param t The "parameter" value, specifying whereabouts along the curve to
+ * evaluate. Typically in the range [0.0, 1.0].
+ *
+ * Let s = 1 - t.
+ * BezierII(1, V) gives (s, t) * V, i.e. t of the way
+ * from V[0] to V[1].
+ * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V.
+ * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V.
+ *
+ * The derivative of BezierII(i, V) with respect to t
+ * is i * BezierII(i-1, V'), where for all j, V'[j] =
+ * V[j + 1] - V[j].
+ */
+Point
+bezier_pt(unsigned const degree, Point const V[], double const t)
+{
+ /** Pascal's triangle. */
+ static int const pascal[4][4] = {{1},
+ {1, 1},
+ {1, 2, 1},
+ {1, 3, 3, 1}};
+ assert( degree < 4);
+ double const s = 1.0 - t;
+
+ /* Calculate powers of t and s. */
+ double spow[4];
+ double tpow[4];
+ spow[0] = 1.0; spow[1] = s;
+ tpow[0] = 1.0; tpow[1] = t;
+ for (unsigned i = 1; i < degree; ++i) {
+ spow[i + 1] = spow[i] * s;
+ tpow[i + 1] = tpow[i] * t;
+ }
+
+ Point ret = spow[degree] * V[0];
+ for (unsigned i = 1; i <= degree; ++i) {
+ ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i];
+ }
+ return ret;
+}
+
+/*
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ * Approximate unit tangents at endpoints and "center" of digitized curve
+ */
+
+/**
+ * Estimate the (forward) tangent at point d[first + 0.5].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ * \pre (2 \<= len) and (d[0] != d[1]).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len)
+{
+ assert( len >= 2 );
+ assert( d[0] != d[1] );
+ return unit_vector( d[1] - d[0] );
+}
+
+/**
+ * Estimates the (backward) tangent at d[last - 0.5].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+static Point
+darray_right_tangent(Point const d[], unsigned const len)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ unsigned const prev = last - 1;
+ assert( d[last] != d[prev] );
+ return unit_vector( d[prev] - d[last] );
+}
+
+/**
+ * Estimate the (forward) tangent at point d[0].
+ *
+ * Unlike the center and right versions, this calculates the tangent in
+ * the way one might expect, i.e., wrt increasing index into d.
+ *
+ * \pre 2 \<= len.
+ * \pre d[0] != d[1].
+ * \pre all[p in d] in_svg_plane(p).
+ * \post is_unit_vector(ret).
+ **/
+Point
+darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ for (unsigned i = 1;;) {
+ Point const pi(d[i]);
+ Point const t(pi - d[0]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ ++i;
+ if (i == len) {
+ return ( distsq == 0
+ ? darray_left_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[last].
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre 2 \<= len.
+ * \pre d[len - 1] != d[len - 2].
+ * \pre all[p in d] in_svg_plane(p).
+ */
+Point
+darray_right_tangent(Point const d[], unsigned const len, double const tolerance_sq)
+{
+ assert( 2 <= len );
+ assert( 0 <= tolerance_sq );
+ unsigned const last = len - 1;
+ for (unsigned i = last - 1;; i--) {
+ Point const pi(d[i]);
+ Point const t(pi - d[last]);
+ double const distsq = dot(t, t);
+ if ( tolerance_sq < distsq ) {
+ return unit_vector(t);
+ }
+ if (i == 0) {
+ return ( distsq == 0
+ ? darray_right_tangent(d, len)
+ : unit_vector(t) );
+ }
+ }
+}
+
+/**
+ * Estimates the (backward) tangent at d[center], by averaging the two
+ * segments connected to d[center] (and then normalizing the result).
+ *
+ * \note The tangent is "backwards", i.e. it is with respect to
+ * decreasing index rather than increasing index.
+ *
+ * \pre (0 \< center \< len - 1) and d is uniqued (at least in
+ * the immediate vicinity of \a center).
+ */
+static Point
+darray_center_tangent(Point const d[],
+ unsigned const center,
+ unsigned const len)
+{
+ assert( center != 0 );
+ assert( center < len - 1 );
+
+ Point ret;
+ if ( d[center + 1] == d[center - 1] ) {
+ /* Rotate 90 degrees in an arbitrary direction. */
+ Point const diff = d[center] - d[center - 1];
+ ret = rot90(diff);
+ } else {
+ ret = d[center - 1] - d[center + 1];
+ }
+ ret.normalize();
+ return ret;
+}
+
+
+/**
+ * Assign parameter values to digitized points using relative distances between points.
+ *
+ * \pre Parameter array u must have space for \a len items.
+ */
+static void
+chord_length_parameterize(Point const d[], double u[], unsigned const len)
+{
+ if(!( 2 <= len ))
+ return;
+
+ /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */
+ u[0] = 0.0;
+ for (unsigned i = 1; i < len; i++) {
+ double const dist = distance(d[i], d[i-1]);
+ u[i] = u[i-1] + dist;
+ }
+
+ /* Then scale to [0.0 .. 1.0]. */
+ double tot_len = u[len - 1];
+ if(!( tot_len != 0 ))
+ return;
+ if (is_finite(tot_len)) {
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] /= tot_len;
+ }
+ } else {
+ /* We could do better, but this probably never happens anyway. */
+ for (unsigned i = 1; i < len; ++i) {
+ u[i] = i / (double) ( len - 1 );
+ }
+ }
+
+ /** \todo
+ * It's been reported that u[len - 1] can differ from 1.0 on some
+ * systems (amd64), despite it having been calculated as x / x where x
+ * is isFinite and non-zero.
+ */
+ if (u[len - 1] != 1) {
+ double const diff = u[len - 1] - 1;
+ if (fabs(diff) > 1e-13) {
+ assert(0); // No warnings in 2geom
+ //g_warning("u[len - 1] = %19g (= 1 + %19g), expecting exactly 1",
+ // u[len - 1], diff);
+ }
+ u[len - 1] = 1;
+ }
+
+#ifdef BEZIER_DEBUG
+ assert( u[0] == 0.0 && u[len - 1] == 1.0 );
+ for (unsigned i = 1; i < len; i++) {
+ assert( u[i] >= u[i-1] );
+ }
+#endif
+}
+
+
+
+
+/**
+ * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum
+ * error is non-zero) set \a *splitPoint to the corresponding index.
+ *
+ * \pre 2 \<= len.
+ * \pre u[0] == 0.
+ * \pre u[len - 1] == 1.0.
+ * \post ((ret == 0.0)
+ * || ((*splitPoint \< len - 1)
+ * \&\& (*splitPoint != 0 || ret \< 0.0))).
+ */
+static double
+compute_max_error_ratio(Point const d[], double const u[], unsigned const len,
+ BezierCurve const bezCurve, double const tolerance,
+ unsigned *const splitPoint)
+{
+ assert( 2 <= len );
+ unsigned const last = len - 1;
+ assert( bezCurve[0] == d[0] );
+ assert( bezCurve[3] == d[last] );
+ assert( u[0] == 0.0 );
+ assert( u[last] == 1.0 );
+ /* I.e. assert that the error for the first & last points is zero.
+ * Otherwise we should include those points in the below loop.
+ * The assertion is also necessary to ensure 0 < splitPoint < last.
+ */
+
+ double maxDistsq = 0.0; /* Maximum error */
+ double max_hook_ratio = 0.0;
+ unsigned snap_end = 0;
+ Point prev = bezCurve[0];
+ for (unsigned i = 1; i <= last; i++) {
+ Point const curr = bezier_pt(3, bezCurve, u[i]);
+ double const distsq = lensq( curr - d[i] );
+ if ( distsq > maxDistsq ) {
+ maxDistsq = distsq;
+ *splitPoint = i;
+ }
+ double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance);
+ if (max_hook_ratio < hook_ratio) {
+ max_hook_ratio = hook_ratio;
+ snap_end = i;
+ }
+ prev = curr;
+ }
+
+ double const dist_ratio = sqrt(maxDistsq) / tolerance;
+ double ret;
+ if (max_hook_ratio <= dist_ratio) {
+ ret = dist_ratio;
+ } else {
+ assert(0 < snap_end);
+ ret = -max_hook_ratio;
+ *splitPoint = snap_end - 1;
+ }
+ assert( ret == 0.0
+ || ( ( *splitPoint < last )
+ && ( *splitPoint != 0 || ret < 0. ) ) );
+ return ret;
+}
+
+/**
+ * Whereas compute_max_error_ratio() checks for itself that each data point
+ * is near some point on the curve, this function checks that each point on
+ * the curve is near some data point (or near some point on the polyline
+ * defined by the data points, or something like that: we allow for a
+ * "reasonable curviness" from such a polyline). "Reasonable curviness"
+ * means we draw a circle centred at the midpoint of a..b, of radius
+ * proportional to the length |a - b|, and require that each point on the
+ * segment of bezCurve between the parameters of a and b be within that circle.
+ * If any point P on the bezCurve segment is outside of that allowable
+ * region (circle), then we return some metric that increases with the
+ * distance from P to the circle.
+ *
+ * Given that this is a fairly arbitrary criterion for finding appropriate
+ * places for sharp corners, we test only one point on bezCurve, namely
+ * the point on bezCurve with parameter halfway between our estimated
+ * parameters for a and b. (Alternatives are taking the farthest of a
+ * few parameters between those of a and b, or even using a variant of
+ * NewtonRaphsonFindRoot() for finding the maximum rather than minimum
+ * distance.)
+ */
+static double
+compute_hook(Point const &a, Point const &b, double const u, BezierCurve const bezCurve,
+ double const tolerance)
+{
+ Point const P = bezier_pt(3, bezCurve, u);
+ double const dist = distance((a+b)*.5, P);
+ if (dist < tolerance) {
+ return 0;
+ }
+ double const allowed = distance(a, b) + tolerance;
+ return dist / allowed;
+ /** \todo
+ * effic: Hooks are very rare. We could start by comparing
+ * distsq, only resorting to the more expensive L2 in cases of
+ * uncertainty.
+ */
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/bezier-utils.h b/src/2geom/bezier-utils.h
new file mode 100644
index 000000000..fba4333ff
--- /dev/null
+++ b/src/2geom/bezier-utils.h
@@ -0,0 +1,96 @@
+#ifndef __SP_BEZIER_UTILS_H__
+#define __SP_BEZIER_UTILS_H__
+
+/*
+ * An Algorithm for Automatically Fitting Digitized Curves
+ * by Philip J. Schneider
+ * from "Graphics Gems", Academic Press, 1990
+ *
+ * Authors:
+ * Philip J. Schneider
+ * Lauris Kaplinski <lauris@ximian.com>
+ *
+ * Copyright (C) 1990 Philip J. Schneider
+ * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include "point.h"
+
+namespace Geom{
+
+/* Bezier approximation utils */
+Point bezier_pt(unsigned degree, Point const V[], double t);
+
+int bezier_fit_cubic(Point bezier[], Point const data[], int len, double error);
+
+int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error,
+ unsigned max_beziers);
+
+int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int len,
+ Point const &tHat1, Point const &tHat2,
+ double error, unsigned max_beziers);
+
+Point darray_left_tangent(Point const d[], unsigned const len);
+Point darray_left_tangent(Point const d[], unsigned const len, double const tolerance_sq);
+Point darray_right_tangent(Point const d[], unsigned const length, double const tolerance_sq);
+
+template <typename iterator>
+static void
+cubic_bezier_poly_coeff(iterator b, Point *pc) {
+ double c[10] = {1,
+ -3, 3,
+ 3, -6, 3,
+ -1, 3, -3, 1};
+
+ int cp = 0;
+
+ for(int i = 0; i < 4; i++) {
+ pc[i] = Point(0,0);
+ ++b;
+ }
+ for(int i = 0; i < 4; i++) {
+ --b;
+ for(int j = 0; j <= i; j++) {
+ pc[3 - j] += c[cp]*(*b);
+ cp++;
+ }
+ }
+}
+
+}
+#endif /* __SP_BEZIER_UTILS_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/bezier.h b/src/2geom/bezier.h
new file mode 100644
index 000000000..7949e006c
--- /dev/null
+++ b/src/2geom/bezier.h
@@ -0,0 +1,141 @@
+/*
+ * bezier.h
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_BEZIER_H
+#define SEEN_BEZIER_H
+
+#include "coord.h"
+#include "isnan.h"
+#include "bezier-to-sbasis.h"
+
+namespace Geom {
+
+template <unsigned order>
+class Bezier {
+private:
+ Coord c_[order+1];
+
+protected:
+ Bezier(Coord c[]) {
+ std::copy(c, c+order+1, c_);
+ }
+
+public:
+ template <unsigned required_order>
+ static void assert_order(Bezier<required_order> const *) {}
+
+ Bezier() {}
+
+ //Construct an order-0 bezier (constant Bézier)
+ explicit Bezier(Coord c0) {
+ assert_order<0>(this);
+ c_[0] = c0;
+ }
+
+ //Construct an order-1 bezier (linear Bézier)
+ Bezier(Coord c0, Coord c1) {
+ assert_order<1>(this);
+ c_[0] = c0; c_[1] = c1;
+ }
+
+ //Construct an order-2 bezier (quadratic Bézier)
+ Bezier(Coord c0, Coord c1, Coord c2) {
+ assert_order<2>(this);
+ c_[0] = c0; c_[1] = c1; c_[2] = c2;
+ }
+
+ //Construct an order-3 bezier (cubic Bézier)
+ Bezier(Coord c0, Coord c1, Coord c2, Coord c3) {
+ assert_order<3>(this);
+ c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3;
+ }
+
+ inline unsigned degree() const { return order; }
+
+ //IMPL: FragmentConcept
+ typedef Coord output_type;
+ inline bool isZero() const {
+ for(int i = 0; i <= order; i++) {
+ if(c_[i] != 0) return false;
+ }
+ return true;
+ }
+ inline bool isFinite() const {
+ for(int i = 0; i <= order; i++) {
+ if(!is_finite(c_[i])) return false;
+ }
+ return true;
+ }
+ inline Coord at0() const { return c_[0]; }
+ inline Coord at1() const { return c_[order]; }
+
+ inline SBasis toSBasis() const { return bezier_to_sbasis<order>(c_); }
+
+ inline Interval bounds_fast() const { return Interval::fromArray(c_, order+1); }
+ //TODO: better bounds exact
+ inline Interval bounds_exact() const { return toSBasis().bounds_exact(); }
+ inline Interval bounds_local(double u, double v) const { return toSBasis().bounds_local(u, v); }
+
+ //Only mutator
+ inline Coord &operator[](int index) { return c_[index]; }
+ inline Coord const &operator[](int index) const { return c_[index]; }
+
+ Maybe<int> winding(Point p) const {
+ return sbasis_winding(toSBasis(), p);
+ }
+
+ Point pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs) const {
+ //TODO
+ return Point(0,0);
+ }
+};
+
+template<unsigned order>
+Bezier<order> reverse(const Bezier<order> & a) {
+ Bezier<order> result;
+ for(int i = 0; i <= order; i++)
+ result[i] = a[order - i];
+ return result;
+}
+
+template<unsigned order>
+vector<Point> bezier_points(const D2<Bezier<order> > & a) {
+ vector<Point> result;
+ for(int i = 0; i <= order; i++) {
+ Point p;
+ for(unsigned d = 0; d < 2; d++) p[d] = a[d][i];
+ result[i] = p;
+ }
+ return result;
+}
+
+}
+#endif //SEEN_BEZIER_H
diff --git a/src/2geom/choose.h b/src/2geom/choose.h
new file mode 100644
index 000000000..169d77c79
--- /dev/null
+++ b/src/2geom/choose.h
@@ -0,0 +1,67 @@
+/*
+ * choose.h
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef _CHOOSE_H
+#define _CHOOSE_H
+
+// XXX: Can we keep only the left terms easily?
+// this would more than halve the array
+// row index becomes n2 = n/2, row2 = n2*(n2+1)/2, row = row2*2+(n&1)?n2:0
+// we could also leave off the ones
+
+template <typename T>
+T choose(unsigned n, unsigned k) {
+ static std::vector<T> pascals_triangle;
+ static unsigned rows_done = 0;
+ // indexing is (0,0,), (1,0), (1,1), (2, 0)...
+ // to get (i, j) i*(i+1)/2 + j
+ if(k < 0 || k > n) return 0;
+ if(rows_done <= n) {// we haven't got there yet
+ if(rows_done == 0) {
+ pascals_triangle.push_back(1);
+ rows_done = 1;
+ }
+ while(rows_done <= n) {
+ unsigned p = pascals_triangle.size() - rows_done;
+ pascals_triangle.push_back(1);
+ for(unsigned i = 0; i < rows_done-1; i++) {
+ pascals_triangle.push_back(pascals_triangle[p]
+ + pascals_triangle[p+1]);
+ p++;
+ }
+ pascals_triangle.push_back(1);
+ rows_done ++;
+ }
+ }
+ unsigned row = (n*(n+1))/2;
+ return pascals_triangle[row+k];
+}
+
+#endif
diff --git a/src/2geom/circle-circle.cpp b/src/2geom/circle-circle.cpp
new file mode 100644
index 000000000..262f8879a
--- /dev/null
+++ b/src/2geom/circle-circle.cpp
@@ -0,0 +1,131 @@
+/* circle_circle_intersection() *
+ * Determine the points where 2 circles in a common plane intersect.
+ *
+ * int circle_circle_intersection(
+ * // center and radius of 1st circle
+ * double x0, double y0, double r0,
+ * // center and radius of 2nd circle
+ * double x1, double y1, double r1,
+ * // 1st intersection point
+ * double *xi, double *yi,
+ * // 2nd intersection point
+ * double *xi_prime, double *yi_prime)
+ *
+ * This is a public domain work. 3/26/2005 Tim Voght
+ * Ported to lib2geom, 2006 Nathan Hurst
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+#include <stdio.h>
+#include <math.h>
+#include "point.h"
+
+namespace Geom{
+
+int circle_circle_intersection(Point X0, double r0,
+ Point X1, double r1,
+ Point & p0, Point & p1)
+{
+ /* dx and dy are the vertical and horizontal distances between
+ * the circle centers.
+ */
+ Point D = X1 - X0;
+
+ /* Determine the straight-line distance between the centers. */
+ double d = L2(D);
+
+ /* Check for solvability. */
+ if (d > (r0 + r1))
+ {
+ /* no solution. circles do not intersect. */
+ return 0;
+ }
+ if (d <= fabs(r0 - r1))
+ {
+ /* no solution. one circle is contained in the other */
+ return 1;
+ }
+
+ /* 'point 2' is the point where the line through the circle
+ * intersection points crosses the line between the circle
+ * centers.
+ */
+
+ /* Determine the distance from point 0 to point 2. */
+ double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
+
+ /* Determine the coordinates of point 2. */
+ Point p2 = X0 + D * (a/d);
+
+ /* Determine the distance from point 2 to either of the
+ * intersection points.
+ */
+ double h = sqrt((r0*r0) - (a*a));
+
+ /* Now determine the offsets of the intersection points from
+ * point 2.
+ */
+ Point r = (h/d)*rot90(D);
+
+ /* Determine the absolute intersection points. */
+ p0 = p2 + r;
+ p1 = p2 - r;
+
+ return 2;
+}
+
+};
+
+
+#ifdef TEST
+
+void run_test(double x0, double y0, double r0,
+ double x1, double y1, double r1)
+{
+ double x3, y3, x3_prime, y3_prime;
+
+ printf("x0=%F, y0=%F, r0=%F, x1=%F, y1=%F, r1=%F :\n",
+ x0, y0, r0, x1, y1, r1);
+ Geom::Point p0, p1;
+ Geom::circle_circle_intersection(Geom::Point(x0, y0), r0,
+ Geom::Point(x1, y1), r1,
+ p0, p1);
+ printf(" x3=%F, y3=%F, x3_prime=%F, y3_prime=%F\n",
+ p0[0], p0[1], p1[0], p1[1]);
+}
+
+int main(void)
+{
+ /* Add more! */
+ run_test(-1.0, -1.0, 1.5, 1.0, 1.0, 2.0);
+ run_test(1.0, -1.0, 1.5, -1.0, 1.0, 2.0);
+ run_test(-1.0, 1.0, 1.5, 1.0, -1.0, 2.0);
+ run_test(1.0, 1.0, 1.5, -1.0, -1.0, 2.0);
+ exit(0);
+}
+#endif
+
diff --git a/src/2geom/circulator.h b/src/2geom/circulator.h
new file mode 100644
index 000000000..4c7f83bad
--- /dev/null
+++ b/src/2geom/circulator.h
@@ -0,0 +1,149 @@
+/*
+ * ciculator.h
+ *
+ * Copyright 2006 MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_Circulator_H
+#define SEEN_Circulator_H
+
+#include <iterator>
+
+namespace Geom {
+
+template <typename Iterator>
+class Circulator {
+public:
+ typedef std::random_access_iterator_tag std::iterator_category;
+ typedef std::iterator_traits<Iterator>::value_type value_type;
+ typedef std::iterator_traits<Iterator>::difference_type difference_type;
+ typedef std::iterator_traits<Iterator>::pointer pointer;
+ typedef std::iterator_traits<Iterator>::reference reference;
+
+ Circulator(Iterator const &first,
+ Iterator const &last,
+ Iterator const &pos)
+ : _first(first), _last(last), _pos(pos)
+ {
+ match_random_access(std::iterator_category(first));
+ }
+
+ reference operator*() const {
+ return *_pos;
+ }
+ pointer operator->() const {
+ return &*_pos;
+ }
+
+ Circulator &operator++() {
+ if ( _first == _last ) return *this;
+ ++_pos;
+ if ( _pos == _last ) _pos = _first;
+ return *this;
+ }
+ Circulator operator++(int) {
+ Circulator saved=*this;
+ ++(*this);
+ return saved;
+ }
+
+ Circulator &operator--() {
+ if ( _pos == _first ) _pos = _last;
+ --_pos;
+ return *this;
+ }
+ Circulator operator--(int) {
+ Circulator saved=*this;
+ --(*this);
+ return saved;
+ }
+
+ Circulator &operator+=(int n) {
+ _pos = _offset(n);
+ return *this;
+ }
+ Circulator operator+(int n) const {
+ return Circulator(_first, _last, _offset(n));
+ }
+ Circulator &operator-=(int n) {
+ _pos = _offset(-n);
+ return *this;
+ }
+ Circulator operator-(int n) const {
+ return Circulator(_first, _last, _offset(-n));
+ }
+
+ difference_type operator-(Circulator const &other) {
+ return _pos - other._pos;
+ }
+
+ reference operator[n] const {
+ return *_offset(n);
+ }
+
+private:
+ void match_random_access(random_access_iterator_tag) {}
+
+ Iterator _offset(int n) {
+ difference_type range=( _last - _first );
+ difference_type offset=( _pos - _first + n );
+
+ if ( offset < 0 ) {
+ // modulus not well-defined for negative numbers in C++
+ offset += ( ( -offset / range ) + 1 ) * range;
+ } else if ( offset >= range ) {
+ offset %= range;
+ }
+ return _first + offset;
+ }
+
+ Iterator _first;
+ Iterator _last;
+ Iterator _pos;
+};
+
+}
+
+template <typename T>
+Geom::Circulator<T> operator+(int n, Geom::Circulator<T> const &c) {
+ return c + n;
+}
+
+#endif // SEEN_Circulator_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/concepts.h b/src/2geom/concepts.h
new file mode 100644
index 000000000..42192c445
--- /dev/null
+++ b/src/2geom/concepts.h
@@ -0,0 +1,145 @@
+/*
+ * concepts.h - Declares various mathematical concepts, for restriction of template parameters
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_CONCEPTS_H
+#define SEEN_CONCEPTS_H
+
+#include "sbasis.h"
+#include "interval.h"
+#include "point.h"
+
+#include <boost/concept_check.hpp>
+
+namespace Geom {
+
+//forward decls
+template <typename T> class D2;
+
+template <typename T> struct ResultTraits;
+
+template <> struct ResultTraits<double> {
+ typedef Interval bounds_type;
+ typedef SBasis sb_type;
+};
+
+template <> struct ResultTraits<Point > {
+ typedef D2<Interval> bounds_type;
+ typedef D2<SBasis> sb_type;
+};
+
+//A concept for one-dimensional functions defined on [0,1]
+template <typename T>
+struct FragmentConcept {
+ typedef typename T::output_type OutputType;
+ typedef typename ResultTraits<OutputType>::bounds_type BoundsType;
+ typedef typename ResultTraits<OutputType>::sb_type SbType;
+ T t;
+ double d;
+ OutputType o;
+ bool b;
+ BoundsType i;
+ Interval dom;
+ void constraints() {
+ t = T(o);
+ b = t.isZero();
+ b = t.isFinite();
+ o = t.at0();
+ o = t.at1();
+ o = t.valueAt(d);
+ o = t(d);
+ SbType sb = t.toSBasis();
+ t = reverse(t);
+ i = bounds_fast(t);
+ i = bounds_exact(t);
+ i = bounds_local(t, dom);
+ /*With portion, Interval makes some sense, but instead I'm opting for
+ doubles, for the following reasons:
+ A) This way a reversed portion may be specified
+ B) Performance might be a bit better for piecewise and such
+ C) Interval version provided below
+ */
+ t = portion(t, d, d);
+ }
+};
+
+template <typename T>
+inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); }
+
+template <typename T>
+struct NearConcept {
+ T a, b;
+ double tol;
+ bool res;
+ void constraints() {
+ res = near(a, b, tol);
+ }
+};
+
+template <typename T>
+struct OffsetableConcept {
+ T t;
+ typename T::output_type d;
+ void constraints() {
+ t = t + d; t += d;
+ t = t - d; t -= d;
+ }
+};
+
+template <typename T>
+struct ScalableConcept {
+ T t;
+ typename T::output_type d;
+ void constraints() {
+ t = -t;
+ t = t * d; t *= d;
+ t = t / d; t /= d;
+ }
+};
+
+template <class T>
+struct AddableConcept {
+ T i, j;
+ void constraints() {
+ i += j; i = i + j;
+ i -= j; i = i - j;
+ }
+};
+
+template <class T>
+struct MultiplicableConcept {
+ T i, j;
+ void constraints() {
+ i *= j; i = i * j;
+ }
+};
+
+};
+
+#endif //SEEN_CONCEPTS_H
diff --git a/src/2geom/conjugate_gradient.cpp b/src/2geom/conjugate_gradient.cpp
new file mode 100644
index 000000000..220cd06d0
--- /dev/null
+++ b/src/2geom/conjugate_gradient.cpp
@@ -0,0 +1,134 @@
+/*
+ * conjugate_gradient.cpp
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <valarray>
+#include <cassert>
+#include "conjugate_gradient.h"
+
+/* lifted wholely from wikipedia. */
+
+using std::valarray;
+
+static void
+matrix_times_vector(valarray<double> const &matrix, /* m * n */
+ valarray<double> const &vec, /* n */
+ valarray<double> &result) /* m */
+{
+ unsigned n = vec.size();
+ unsigned m = result.size();
+ assert(m*n == matrix.size());
+ const double* mp = &matrix[0];
+ for (unsigned i = 0; i < m; i++) {
+ double res = 0;
+ for (unsigned j = 0; j < n; j++)
+ res += *mp++ * vec[j];
+ result[i] = res;
+ }
+}
+
+static double Linfty(valarray<double> const &vec) {
+ return std::max(vec.max(), -vec.min());
+}
+
+double
+inner(valarray<double> const &x,
+ valarray<double> const &y) {
+ double total = 0;
+ for(unsigned i = 0; i < x.size(); i++)
+ total += x[i]*y[i];
+ return total;// (x*y).sum(); <- this is more concise, but ineff
+}
+
+void
+conjugate_gradient(double **A,
+ double *x,
+ double *b,
+ unsigned n,
+ double tol,
+ int max_iterations,
+ bool ortho1) {
+ valarray<double> vA(n*n);
+ valarray<double> vx(n);
+ valarray<double> vb(n);
+ for(unsigned i=0;i<n;i++) {
+ vx[i]=x[i];
+ vb[i]=b[i];
+ for(unsigned j=0;j<n;j++) {
+ vA[i*n+j]=A[i][j];
+ }
+ }
+ conjugate_gradient(vA,vx,vb,n,tol,max_iterations,ortho1);
+ for(unsigned i=0;i<n;i++) {
+ x[i]=vx[i];
+ }
+}
+void
+conjugate_gradient(valarray<double> const &A,
+ valarray<double> &x,
+ valarray<double> const &b,
+ unsigned n, double tol,
+ unsigned max_iterations, bool ortho1) {
+ valarray<double> Ap(n), p(n), r(n);
+ matrix_times_vector(A,x,Ap);
+ r=b-Ap;
+ double r_r = inner(r,r);
+ unsigned k = 0;
+ tol *= tol;
+ while(k < max_iterations && r_r > tol) {
+ k++;
+ double r_r_new = r_r;
+ if(k == 1)
+ p = r;
+ else {
+ r_r_new = inner(r,r);
+ p = r + (r_r_new/r_r)*p;
+ }
+ matrix_times_vector(A, p, Ap);
+ double alpha_k = r_r_new / inner(p, Ap);
+ x += alpha_k*p;
+ r -= alpha_k*Ap;
+ r_r = r_r_new;
+ }
+ //printf("njh: %d iters, Linfty = %g L2 = %g\n", k,
+ //std::max(-r.min(), r.max()), sqrt(r_r));
+ // x is solution
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4
diff --git a/src/2geom/conjugate_gradient.h b/src/2geom/conjugate_gradient.h
new file mode 100644
index 000000000..2090aecd2
--- /dev/null
+++ b/src/2geom/conjugate_gradient.h
@@ -0,0 +1,46 @@
+/*
+ * conjugate_gradient.h
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef _CONJUGATE_GRADIENT_H
+#define _CONJUGATE_GRADIENT_H
+
+#include <valarray>
+
+double
+inner(std::valarray<double> const &x,
+ std::valarray<double> const &y);
+
+void
+conjugate_gradient(std::valarray<double> const &A,
+ std::valarray<double> &x,
+ std::valarray<double> const &b,
+ unsigned n, double tol,
+ unsigned max_iterations, bool ortho1);
+#endif // _CONJUGATE_GRADIENT_H
diff --git a/src/2geom/convex-cover.cpp b/src/2geom/convex-cover.cpp
new file mode 100644
index 000000000..1c704e0ed
--- /dev/null
+++ b/src/2geom/convex-cover.cpp
@@ -0,0 +1,449 @@
+/*
+ * convex-cover.cpp
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include "convex-cover.h"
+#include <algorithm>
+#include <map>
+/** Todo:
+ + modify graham scan to work top to bottom, rather than around angles
+ + intersection
+ + minimum distance between convex hulls
+ + maximum distance between convex hulls
+ + hausdorf metric?
+ + check all degenerate cases carefully
+ + check all algorithms meet all invariants
+ + generalise rotating caliper algorithm (iterator/circulator?)
+*/
+
+using std::vector;
+using std::map;
+using std::pair;
+
+namespace Geom{
+
+/*** SignedTriangleArea
+ * returns the area of the triangle defined by p0, p1, p2. A clockwise triangle has positive area.
+ */
+double
+SignedTriangleArea(Point p0, Point p1, Point p2) {
+ return cross((p1 - p0), (p2 - p0));
+}
+
+class angle_cmp{
+public:
+ Point o;
+ angle_cmp(Point o) : o(o) {}
+
+ bool
+ operator()(Point a, Point b) {
+ Point da = a - o;
+ Point db = b - o;
+
+#if 1
+ double aa = da[0];
+ double ab = db[0];
+ if((da[1] == 0) && (db[1] == 0))
+ return da[0] < db[0];
+ if(da[1] == 0)
+ return true; // infinite tangent
+ if(db[1] == 0)
+ return false; // infinite tangent
+ aa = da[0] / da[1];
+ ab = db[0] / db[1];
+ if(aa > ab)
+ return true;
+#else
+ //assert((ata > atb) == (aa < ab));
+ double aa = atan2(da);
+ double ab = atan2(db);
+ if(aa < ab)
+ return true;
+#endif
+ if(aa == ab)
+ return L2sq(da) < L2sq(db);
+ return false;
+ }
+};
+
+void
+ConvexHull::find_pivot() {
+ // Find pivot P;
+ unsigned pivot = 0;
+ for(unsigned i = 1; i < boundary.size(); i++)
+ if(boundary[i] <= boundary[pivot])
+ pivot = i;
+
+ std::swap(boundary[0], boundary[pivot]);
+}
+
+void
+ConvexHull::angle_sort() {
+// sort points by angle (resolve ties in favor of point farther from P);
+// we leave the first one in place as our pivot
+ std::sort(boundary.begin()+1, boundary.end(), angle_cmp(boundary[0]));
+}
+
+void
+ConvexHull::graham_scan() {
+ unsigned stac = 2;
+ for(int i = 2; i < boundary.size(); i++) {
+ double o = SignedTriangleArea(boundary[stac-2],
+ boundary[stac-1],
+ boundary[i]);
+ if(o == 0) { // colinear - dangerous...
+ stac--;
+ } else if(o < 0) { // anticlockwise
+ } else { // remove concavity
+ while(o >= 0 && stac > 2) {
+ stac--;
+ o = SignedTriangleArea(boundary[stac-2],
+ boundary[stac-1],
+ boundary[i]);
+ }
+ }
+ boundary[stac++] = boundary[i];
+ }
+ boundary.resize(stac);
+}
+
+void
+ConvexHull::graham() {
+ find_pivot();
+ angle_sort();
+ graham_scan();
+}
+
+//Mathematically incorrect mod, but more useful.
+int mod(int i, int l) {
+ return i >= 0 ?
+ i % l : (i % l) + l;
+}
+//OPT: usages can often be replaced by conditions
+
+/*** ConvexHull::left
+ * Tests if a point is left (outside) of a particular segment, n. */
+bool
+ConvexHull::is_left(Point p, int n) {
+ return SignedTriangleArea((*this)[n], (*this)[n+1], p) > 0;
+}
+
+/*** ConvexHull::find_positive
+ * May return any number n where the segment n -> n + 1 (possibly looped around) in the hull such
+ * that the point is on the wrong side to be within the hull. Returns -1 if it is within the hull.*/
+int
+ConvexHull::find_left(Point p) {
+ int l = boundary.size(); //Who knows if C++ is smart enough to optimize this?
+ for(int i = 0; i < l; i++) {
+ if(is_left(p, i)) return i;
+ }
+ return -1;
+}
+//OPT: do a spread iteration - quasi-random with no repeats and full coverage.
+
+/*** ConvexHull::contains_point
+ * In order to test whether a point is inside a convex hull we can travel once around the outside making
+ * sure that each triangle made from an edge and the point has positive area. */
+bool
+ConvexHull::contains_point(Point p) {
+ return find_left(p) == -1;
+}
+
+/*** ConvexHull::add_point
+ * to add a point we need to find whether the new point extends the boundary, and if so, what it
+ * obscures. Tarjan? Jarvis?*/
+void
+ConvexHull::merge(Point p) {
+ std::vector<Point> out;
+
+ int l = boundary.size();
+
+ if(l < 2) {
+ boundary.push_back(p);
+ return;
+ }
+
+ bool pushed = false;
+
+ bool pre = is_left(p, -1);
+ for(int i = 0; i < l; i++) {
+ bool cur = is_left(p, i);
+ if(pre) {
+ if(cur) {
+ if(!pushed) {
+ out.push_back(p);
+ pushed = true;
+ }
+ continue;
+ }
+ else if(!pushed) {
+ out.push_back(p);
+ pushed = true;
+ }
+ }
+ out.push_back(boundary[i]);
+ pre = cur;
+ }
+
+ boundary = out;
+}
+//OPT: quickly find an obscured point and find the bounds by extending from there. then push all points not within the bounds in order.
+ //OPT: use binary searches to find the actual starts/ends, use known rights as boundaries. may require cooperation of find_left algo.
+
+/*** ConvexHull::is_clockwise
+ * We require that successive pairs of edges always turn right.
+ * proposed algorithm: walk successive edges and require triangle area is positive.
+ */
+bool
+ConvexHull::is_clockwise() const {
+ if(is_degenerate())
+ return true;
+ Point first = boundary[0];
+ Point second = boundary[1];
+ for(std::vector<Point>::const_iterator it(boundary.begin()+2), e(boundary.end());
+ it != e;) {
+ if(SignedTriangleArea(first, second, *it) > 0)
+ return false;
+ first = second;
+ second = *it;
+ ++it;
+ }
+ return true;
+}
+
+/*** ConvexHull::top_point_first
+ * We require that the first point in the convex hull has the least y coord, and that off all such points on the hull, it has the least x coord.
+ * proposed algorithm: track lexicographic minimum while walking the list.
+ */
+bool
+ConvexHull::top_point_first() const {
+ std::vector<Point>::const_iterator pivot = boundary.begin();
+ for(std::vector<Point>::const_iterator it(boundary.begin()+1),
+ e(boundary.end());
+ it != e; it++) {
+ if((*it)[1] < (*pivot)[1])
+ pivot = it;
+ else if(((*it)[1] == (*pivot)[1]) &&
+ ((*it)[0] < (*pivot)[0]))
+ pivot = it;
+ }
+ return pivot == boundary.begin();
+}
+//OPT: since the Y values are orderly there should be something like a binary search to do this.
+
+/*** ConvexHull::no_colinear_points
+ * We require that no three vertices are colinear.
+proposed algorithm: We must be very careful about rounding here.
+*/
+bool
+ConvexHull::no_colinear_points() const {
+
+}
+
+bool
+ConvexHull::meets_invariants() const {
+ return is_clockwise() && top_point_first() && no_colinear_points();
+}
+
+/*** ConvexHull::is_degenerate
+ * We allow three degenerate cases: empty, 1 point and 2 points. In many cases these should be handled explicitly.
+ */
+bool
+ConvexHull::is_degenerate() const {
+ return boundary.size() < 3;
+}
+
+
+/* Here we really need a rotating calipers implementation. This implementation is slow and incorrect.
+ This incorrectness is a problem because it throws off the algorithms. Perhaps I will come up with
+ something better tomorrow. The incorrectness is in the order of the bridges - they must be in the
+ order of traversal around. Since the a->b and b->a bridges are seperated, they don't need to be merge
+ order, just the order of the traversal of the host hull. Currently some situations make a n->0 bridge
+ first.*/
+pair< map<int, int>, map<int, int> >
+bridges(ConvexHull a, ConvexHull b) {
+ map<int, int> abridges;
+ map<int, int> bbridges;
+
+ for(int ia = 0; ia < a.boundary.size(); ia++) {
+ for(int ib = 0; ib < b.boundary.size(); ib++) {
+ Point d = b[ib] - a[ia];
+ Geom::Coord e = cross(d, a[ia - 1] - a[ia]), f = cross(d, a[ia + 1] - a[ia]);
+ Geom::Coord g = cross(d, b[ib - 1] - a[ia]), h = cross(d, b[ib + 1] - a[ia]);
+ if (e > 0 && f > 0 && g > 0 && h > 0) abridges[ia] = ib;
+ else if(e < 0 && f < 0 && g < 0 && h < 0) bbridges[ib] = ia;
+ }
+ }
+
+ return make_pair(abridges, bbridges);
+}
+
+std::vector<Point> bridge_points(ConvexHull a, ConvexHull b) {
+ vector<Point> ret;
+ pair< map<int, int>, map<int, int> > indices = bridges(a, b);
+ for(map<int, int>::iterator it = indices.first.begin(); it != indices.first.end(); it++) {
+ ret.push_back(a[it->first]);
+ ret.push_back(b[it->second]);
+ }
+ for(map<int, int>::iterator it = indices.second.begin(); it != indices.second.end(); it++) {
+ ret.push_back(b[it->first]);
+ ret.push_back(a[it->second]);
+ }
+ return ret;
+}
+
+unsigned find_bottom_right(ConvexHull const &a) {
+ unsigned it = 1;
+ while(it < a.boundary.size() &&
+ a.boundary[it][Y] > a.boundary[it-1][Y])
+ it++;
+ return it-1;
+}
+
+/*** ConvexHull sweepline_intersection(ConvexHull a, ConvexHull b);
+ * find the intersection between two convex hulls. The intersection is also a convex hull.
+ * (Proof: take any two points both in a and in b. Any point between them is in a by convexity,
+ * and in b by convexity, thus in both. Need to prove still finite bounds.)
+ * This algorithm works by sweeping a line down both convex hulls in parallel, working out the left and right edges of the new hull.
+ */
+ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b) {
+ ConvexHull ret;
+
+ int al = 0;
+ int bl = 0;
+
+ while(al+1 < a.boundary.size() &&
+ (a.boundary[al+1][Y] > b.boundary[bl][Y])) {
+ al++;
+ }
+ while(bl+1 < b.boundary.size() &&
+ (b.boundary[bl+1][Y] > a.boundary[al][Y])) {
+ bl++;
+ }
+ // al and bl now point to the top of the first pair of edges that overlap in y value
+ double sweep_y = std::min(a.boundary[al][Y],
+ b.boundary[bl][Y]);
+}
+
+/*** ConvexHull intersection(ConvexHull a, ConvexHull b);
+ * find the intersection between two convex hulls. The intersection is also a convex hull.
+ * (Proof: take any two points both in a and in b. Any point between them is in a by convexity,
+ * and in b by convexity, thus in both. Need to prove still finite bounds.)
+ */
+ConvexHull intersection(ConvexHull a, ConvexHull b) {
+ ConvexHull ret;
+ int ai = 0, bi = 0;
+ int aj = a.boundary.size() - 1;
+ int bj = b.boundary.size() - 1;
+
+ /*while (true) {
+ if(a[ai]
+ }*/
+ return ret;
+}
+
+/*** ConvexHull merge(ConvexHull a, ConvexHull b);
+ * find the smallest convex hull that surrounds a and b.
+ */
+ConvexHull merge(ConvexHull a, ConvexHull b) {
+ ConvexHull ret;
+
+ pair< map<int, int>, map<int, int> > bpair = bridges(a, b);
+ map<int, int> ab = bpair.first;
+ map<int, int> bb = bpair.second;
+
+ ab[-1] = 0;
+ bb[-1] = 0;
+
+ int i = -1;
+
+ if(a.boundary[0][1] > b.boundary[0][1]) goto start_b;
+ while(true) {
+ for(; ab.count(i) == 0; i++) {
+ ret.boundary.push_back(a[i]);
+ if(i >= a.boundary.size()) return ret;
+ }
+ if(ab[i] == 0 && i != -1) break;
+ i = ab[i];
+ start_b:
+
+ for(; bb.count(i) == 0; i++) {
+ ret.boundary.push_back(b[i]);
+ if(i >= b.boundary.size()) return ret;
+ }
+ if(bb[i] == 0 && i != -1) break;
+ i = bb[i];
+ }
+ return ret;
+}
+
+ConvexHull graham_merge(ConvexHull a, ConvexHull b) {
+ ConvexHull result;
+
+ // we can avoid the find pivot step because of top_point_first
+ if(b.boundary[0] <= a.boundary[0])
+ std::swap(a, b);
+
+ result.boundary = a.boundary;
+ result.boundary.insert(result.boundary.end(),
+ b.boundary.begin(), b.boundary.end());
+
+/** if we modified graham scan to work top to bottom as proposed in lect754.pdf we could replace the
+ angle sort with a simple merge sort type algorithm. furthermore, we could do the graham scan
+ online, avoiding a bunch of memory copies. That would probably be linear. -- njh*/
+ result.angle_sort();
+ result.graham_scan();
+
+ return result;
+}
+//TODO: reinstate
+/*ConvexCover::ConvexCover(Path const &sp) : path(&sp) {
+ cc.reserve(sp.size());
+ for(Geom::Path::const_iterator it(sp.begin()), end(sp.end()); it != end; ++it) {
+ cc.push_back(ConvexHull((*it).begin(), (*it).end()));
+ }
+}*/
+
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
+
diff --git a/src/2geom/convex-cover.h b/src/2geom/convex-cover.h
new file mode 100644
index 000000000..5c1f33ff6
--- /dev/null
+++ b/src/2geom/convex-cover.h
@@ -0,0 +1,174 @@
+#ifndef GEOM_CONVEX_COVER_H
+#define GEOM_CONVEX_COVER_H
+
+/*
+ * convex-cover.h
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+/** A convex cover is a sequence of convex polygons that completely cover the path. For now a
+ * convex hull class is included here (the convex-hull header is wrong)
+ */
+
+#include "point.h"
+#include <vector>
+
+namespace Geom{
+
+/** ConvexHull
+ * A convexhull is a convex region - every point between two points in the convex hull is also in
+ * the convex hull. It is defined by a set of points travelling in a clockwise direction. We require the first point to be top most, and of the topmost, leftmost.
+
+ * An empty hull has no points, we allow a single point or two points degenerate cases.
+
+ * We could provide the centroid as a member for efficient direction determination. We can update the
+ * centroid with all operations with the same time complexity as the operation.
+ */
+
+class ConvexHull{
+public: // XXX: should be private :)
+ // extracts the convex hull of boundary. internal use only
+ void find_pivot();
+ void angle_sort();
+ void graham_scan();
+ void graham();
+public:
+ std::vector<Point> boundary;
+ //Point centroid;
+
+ void merge(Point p);
+ bool contains_point(Point p);
+
+ inline Point operator[](int i) const {
+ int l = boundary.size();
+ if(l == 0) return Point();
+ return boundary[i >= 0 ? i % l : (i % l) + l];
+ }
+
+ /*inline Point &operator[](unsigned i) {
+ int l = boundary.size();
+ if(l == 0) return Point();
+ return boundary[i >= 0 ? i % l : i % l + l];
+ }*/
+
+public:
+ ConvexHull() {}
+ ConvexHull(std::vector<Point> const & points) {
+ boundary = points;
+ graham();
+ }
+
+ template <typename T>
+ ConvexHull(T b, T e) :boundary(b,e) {}
+
+public:
+ /** Is the convex hull clockwise? We use the definition of clockwise from point.h
+ **/
+ bool is_clockwise() const;
+ bool no_colinear_points() const;
+ bool top_point_first() const;
+ bool meets_invariants() const;
+
+ // contains no points
+ bool empty() const { return boundary.empty();}
+
+ // contains exactly one point
+ bool singular() const { return boundary.size() == 1;}
+
+ // all points are on a line
+ bool linear() const { return boundary.size() == 2;}
+ bool is_degenerate() const;
+
+ // area of the convex hull
+ double area() const;
+
+ // furthest point in a direction (lg time)
+ Point const * furthest(Point direction) const;
+
+ bool is_left(Point p, int n);
+ int find_left(Point p);
+};
+
+// do two convex hulls intersect?
+bool intersectp(ConvexHull a, ConvexHull b);
+
+std::vector<Point> bridge_points(ConvexHull a, ConvexHull b);
+
+// find the convex hull intersection
+ConvexHull intersection(ConvexHull a, ConvexHull b);
+ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b);
+
+// find the convex hull of a set of convex hulls
+ConvexHull merge(ConvexHull a, ConvexHull b);
+
+// naive approach
+ConvexHull graham_merge(ConvexHull a, ConvexHull b);
+
+unsigned find_bottom_right(ConvexHull const &a);
+
+/*** Arbitrary transform operator.
+ * Take a convex hull and apply an arbitrary convexity preserving transform.
+ * we should be concerned about singular tranforms here.
+ */
+template <class T> ConvexHull operator*(ConvexHull const &p, T const &m) {
+ ConvexHull pr;
+
+ pr.boundary.reserve(p.boundary.size());
+
+ for(unsigned i = 0; i < p.boundary.size(); i++) {
+ pr.boundary.push_back(p.boundary[i]*m);
+ }
+ return pr;
+}
+
+//TODO: reinstate
+/*class ConvexCover{
+public:
+ Path const* path;
+ std::vector<ConvexHull> cc;
+
+ ConvexCover(Path const &sp);
+};*/
+
+};
+
+#endif //2GEOM_CONVEX_COVER_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/coord.h b/src/2geom/coord.h
new file mode 100644
index 000000000..6636b4ad7
--- /dev/null
+++ b/src/2geom/coord.h
@@ -0,0 +1,66 @@
+/*
+ * coord.h
+ *
+ * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_Geom_COORD_H
+#define SEEN_Geom_COORD_H
+
+#include <cmath>
+
+namespace Geom {
+
+/**
+ * A "real" type with sufficient precision for coordinates.
+ *
+ * You may safely assume that double (or even float) provides enough precision for storing
+ * on-canvas points, and hence that double provides enough precision for dot products of
+ * differences of on-canvas points.
+ */
+typedef double Coord;
+
+const Coord EPSILON = 1e-5; //1e-18;
+
+//IMPL: NearConcept
+inline bool near(Coord a, Coord b, double eps=EPSILON) { return fabs(a-b) <= eps; }
+
+} /* namespace Geom */
+
+
+#endif /* !SEEN_Geom_COORD_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/d2.cpp b/src/2geom/d2.cpp
new file mode 100644
index 000000000..86538062b
--- /dev/null
+++ b/src/2geom/d2.cpp
@@ -0,0 +1,177 @@
+/*
+ * d2.cpp - Lifts one dimensional objects into 2d
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include "d2.h"
+
+namespace Geom {
+
+SBasis L2(D2<SBasis> const & a, unsigned k) { return sqrt(dot(a, a), k); }
+double L2(D2<double> const & a) { return hypot(a[0], a[1]); }
+
+D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b) {
+ return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y]));
+}
+
+D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b) {
+ return D2<SBasis>(multiply(a, b[X]), multiply(a, b[Y]));
+}
+
+D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms) {
+ return D2<SBasis>(truncate(a[X], terms), truncate(a[Y], terms));
+}
+
+unsigned sbasis_size(D2<SBasis> const & a) {
+ return std::max((unsigned) a[0].size(), (unsigned) a[1].size());
+}
+
+//TODO: Is this sensical? shouldn't it be like pythagorean or something?
+double tail_error(D2<SBasis> const & a, unsigned tail) {
+ return std::max(a[0].tailError(tail), a[1].tailError(tail));
+}
+
+Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a) {
+ Piecewise<SBasis> x = partition(a[0], a[1].cuts), y = partition(a[1], a[0].cuts);
+ assert(x.size() == y.size());
+ Piecewise<D2<SBasis> > ret;
+ for(unsigned i = 0; i < x.size(); i++)
+ ret.push_seg(D2<SBasis>(x[i], y[i]));
+ ret.cuts.insert(ret.cuts.end(), x.cuts.begin(), x.cuts.end());
+ return ret;
+}
+
+D2<Piecewise<SBasis> > make_cuts_independant(Piecewise<D2<SBasis> > const &a) {
+ D2<Piecewise<SBasis> > ret;
+ for(unsigned d = 0; d < 2; d++) {
+ for(unsigned i = 0; i < a.size(); i++)
+ ret[d].push_seg(a[i][d]);
+ ret[d].cuts.insert(ret[d].cuts.end(), a.cuts.begin(), a.cuts.end());
+ }
+ return ret;
+}
+
+Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &M){
+ Piecewise<D2<SBasis> > result;
+ if (M.empty()) return M;
+ result.push_cut(M.cuts[0]);
+ for (unsigned i=0; i<M.size(); i++){
+ result.push(rot90(M[i]),M.cuts[i+1]);
+ }
+ return result;
+}
+
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a,
+ Piecewise<D2<SBasis> > const &b){
+ Piecewise<SBasis > result;
+ if (a.empty() || b.empty()) return result;
+ Piecewise<D2<SBasis> > aa = partition(a,b.cuts);
+ Piecewise<D2<SBasis> > bb = partition(b,a.cuts);
+
+ result.push_cut(aa.cuts.front());
+ for (unsigned i=0; i<aa.size(); i++){
+ result.push(dot(aa.segs[i],bb.segs[i]),aa.cuts[i+1]);
+ }
+ return result;
+}
+
+Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a,
+ Piecewise<D2<SBasis> > const &b){
+ Piecewise<SBasis > result;
+ if (a.empty() || b.empty()) return result;
+ Piecewise<D2<SBasis> > aa = partition(a,b.cuts);
+ Piecewise<D2<SBasis> > bb = partition(b,a.cuts);
+
+ result.push_cut(aa.cuts.front());
+ for (unsigned i=0; i<a.size(); i++){
+ result.push(cross(aa.segs[i],bb.segs[i]),aa.cuts[i+1]);
+ }
+ return result;
+}
+
+/* Replaced by remove_short_cuts in piecewise.h
+//this recursively removes the shortest cut interval until none is shorter than tol.
+//TODO: code this in a more efficient way!
+Piecewise<D2<SBasis> > remove_short_cuts(Piecewise<D2<SBasis> > const &f, double tol){
+ double min = tol;
+ unsigned idx = f.size();
+ for(unsigned i=0; i<f.size(); i++){
+ if (min > f.cuts[i+1]-f.cuts[i]){
+ min = f.cuts[i+1]-f.cuts[i];
+ idx = int(i);
+ }
+ }
+ if (idx==f.size()){
+ return f;
+ }
+ if (f.size()==1) {
+ //removing this seg would result in an empty pw<d2<sb>>...
+ return f;
+ }
+ Piecewise<D2<SBasis> > new_f=f;
+ for (int dim=0; dim<2; dim++){
+ double v = Hat(f.segs.at(idx)[dim][0]);
+ //TODO: what about closed curves?
+ if (idx>0 && f.segs.at(idx-1).at1()==f.segs.at(idx).at0())
+ new_f.segs.at(idx-1)[dim][0][1] = v;
+ if (idx<f.size() && f.segs.at(idx+1).at0()==f.segs.at(idx).at1())
+ new_f.segs.at(idx+1)[dim][0][0] = v;
+ }
+ double t = (f.cuts.at(idx)+f.cuts.at(idx+1))/2;
+ new_f.cuts.at(idx+1) = t;
+
+ new_f.segs.erase(new_f.segs.begin()+idx);
+ new_f.cuts.erase(new_f.cuts.begin()+idx);
+ return remove_short_cuts(new_f, tol);
+}
+*/
+
+//if tol>0, only force continuity where the jump is smaller than tol.
+Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f,
+ double tol,
+ bool closed){
+ if (f.size()==0) return f;
+ Piecewise<D2<SBasis> > result=f;
+ unsigned cur = (closed)? 0:1;
+ unsigned prev = (closed)? f.size()-1:0;
+ while(cur<f.size()){
+ Point pt0 = f.segs[prev].at1();
+ Point pt1 = f.segs[cur ].at0();
+ if (tol<=0 || L2sq(pt0-pt1)<tol*tol){
+ pt0 = (pt0+pt1)/2;
+ for (unsigned dim=0; dim<2; dim++){
+ result.segs[prev][dim][0][1]=pt0[dim];
+ result.segs[cur ][dim][0][0]=pt0[dim];
+ }
+ }
+ prev = cur++;
+ }
+ return result;
+}
+
+};
diff --git a/src/2geom/d2.h b/src/2geom/d2.h
new file mode 100644
index 000000000..82070519c
--- /dev/null
+++ b/src/2geom/d2.h
@@ -0,0 +1,475 @@
+/*
+ * d2.h - Lifts one dimensional objects into 2d
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef _2GEOM_D2 //If this is change, change the guard in rect.h as well.
+#define _2GEOM_D2
+
+#include "point.h"
+#include "interval.h"
+#include "matrix.h"
+
+#include <boost/concept_check.hpp>
+#include "concepts.h"
+
+namespace Geom{
+
+template <class T>
+class D2{
+ //BOOST_CLASS_REQUIRE(T, boost, AssignableConcept);
+ private:
+ T f[2];
+
+ public:
+ D2() {f[X] = f[Y] = T();}
+ explicit D2(Point const &a) {
+ f[X] = T(a[X]); f[Y] = T(a[Y]);
+ }
+
+ D2(T const &a, T const &b) {
+ f[X] = a;
+ f[Y] = b;
+ }
+
+ //TODO: ask mental about operator= as seen in Point
+
+ T& operator[](unsigned i) { return f[i]; }
+ T const & operator[](unsigned i) const { return f[i]; }
+
+ //IMPL: FragmentConcept
+ typedef Point output_type;
+ bool isZero() const {
+ boost::function_requires<FragmentConcept<T> >();
+ return f[X].isZero() && f[Y].isZero();
+ }
+ bool isFinite() const {
+ boost::function_requires<FragmentConcept<T> >();
+ return f[X].isFinite() && f[Y].isFinite();
+ }
+ Point at0() const {
+ boost::function_requires<FragmentConcept<T> >();
+ return Point(f[X].at0(), f[Y].at0());
+ }
+ Point at1() const {
+ boost::function_requires<FragmentConcept<T> >();
+ return Point(f[X].at1(), f[Y].at1());
+ }
+ Point valueAt(double t) const {
+ boost::function_requires<FragmentConcept<T> >();
+ return (*this)(t);
+ }
+ D2<SBasis> toSBasis() const {
+ boost::function_requires<FragmentConcept<T> >();
+ return D2<SBasis>(f[X].toSBasis(), f[Y].toSBasis());
+ }
+
+ Point operator()(double t) const;
+ Point operator()(double x, double y) const;
+};
+
+template <typename T>
+D2<T> reverse(const D2<T> &a) {
+ boost::function_requires<FragmentConcept<T> >();
+ return D2<T>(reverse(a[X]), reverse(a[Y]));
+}
+
+//IMPL: boost::EqualityComparableConcept
+template <typename T>
+inline bool
+operator==(D2<T> const &a, D2<T> const &b) {
+ boost::function_requires<boost::EqualityComparableConcept<T> >();
+ return a[0]==b[0] && a[1]==b[1];
+}
+template <typename T>
+inline bool
+operator!=(D2<T> const &a, D2<T> const &b) {
+ boost::function_requires<boost::EqualityComparableConcept<T> >();
+ return a[0]!=b[0] || a[1]!=b[1];
+}
+
+//IMPL: NearConcept
+template <typename T>
+inline bool
+near(D2<T> const &a, D2<T> const &b, double tol) {
+ boost::function_requires<NearConcept<T> >();
+ return near(a[0], b[0]) && near(a[1], b[1]);
+}
+
+//IMPL: AddableConcept
+template <typename T>
+inline D2<T>
+operator+(D2<T> const &a, D2<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] + b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator-(D2<T> const &a, D2<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] - b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator+=(D2<T> &a, D2<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] += b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator-=(D2<T> &a, D2<T> const & b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] -= b[i];
+ return a;
+}
+
+//IMPL: ScalableConcept
+template <typename T>
+inline D2<T>
+operator-(D2<T> const & a) {
+ boost::function_requires<ScalableConcept<T> >();
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = -a[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator*(D2<T> const & a, Point const & b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] * b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator/(D2<T> const & a, Point const & b) {
+ boost::function_requires<ScalableConcept<T> >();
+ //TODO: b==0?
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] / b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator*=(D2<T> &a, Point const & b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ for(unsigned i = 0; i < 2; i++)
+ a[i] *= b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator/=(D2<T> &a, Point const & b) {
+ boost::function_requires<ScalableConcept<T> >();
+ //TODO: b==0?
+ for(unsigned i = 0; i < 2; i++)
+ a[i] /= b[i];
+ return a;
+}
+
+template <typename T>
+inline D2<T> operator*(D2<T> const & a, double b) { return D2<T>(a[0]*b, a[1]*b); }
+template <typename T>
+inline D2<T> operator*=(D2<T> & a, double b) { a[0] *= b; a[1] *= b; return a; }
+template <typename T>
+inline D2<T> operator/(D2<T> const & a, double b) { return D2<T>(a[0]/b, a[1]/b); }
+template <typename T>
+inline D2<T> operator/=(D2<T> & a, double b) { a[0] /= b; a[1] /= b; return a; }
+
+template<typename T>
+D2<T> operator*(D2<T> const &v, Matrix const &m) {
+ boost::function_requires<AddableConcept<T> >();
+ boost::function_requires<ScalableConcept<T> >();
+ D2<T> ret;
+ for(unsigned i = 0; i < 2; i++)
+ ret[i] = v[X] * m[i] + v[Y] * m[i + 2] + m[i + 4];
+ return ret;
+}
+
+//IMPL: OffsetableConcept
+template <typename T>
+inline D2<T>
+operator+(D2<T> const & a, Point b) {
+ boost::function_requires<OffsetableConcept<T> >();
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] + b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator-(D2<T> const & a, Point b) {
+ boost::function_requires<OffsetableConcept<T> >();
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = a[i] - b[i];
+ return r;
+}
+template <typename T>
+inline D2<T>
+operator+=(D2<T> & a, Point b) {
+ boost::function_requires<OffsetableConcept<T> >();
+ for(unsigned i = 0; i < 2; i++)
+ a[i] += b[i];
+ return a;
+}
+template <typename T>
+inline D2<T>
+operator-=(D2<T> & a, Point b) {
+ boost::function_requires<OffsetableConcept<T> >();
+ for(unsigned i = 0; i < 2; i++)
+ a[i] -= b[i];
+ return a;
+}
+
+template <typename T>
+inline T
+dot(D2<T> const & a, D2<T> const & b) {
+ boost::function_requires<AddableConcept<T> >();
+ boost::function_requires<MultiplicableConcept<T> >();
+
+ T r;
+ for(unsigned i = 0; i < 2; i++)
+ r += a[i] * b[i];
+ return r;
+}
+
+template <typename T>
+inline T
+cross(D2<T> const & a, D2<T> const & b) {
+ boost::function_requires<ScalableConcept<T> >();
+ boost::function_requires<MultiplicableConcept<T> >();
+
+ return a[1] * b[0] - a[0] * b[1];
+}
+
+
+//equivalent to cw/ccw, for use in situations where rotation direction doesn't matter.
+template <typename T>
+inline D2<T>
+rot90(D2<T> const & a) {
+ boost::function_requires<ScalableConcept<T> >();
+ return D2<T>(-a[Y], a[X]);
+}
+
+//TODO: concepterize the following functions
+template <typename T>
+inline D2<T>
+compose(D2<T> const & a, T const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a[i],b);
+ return r;
+}
+
+template <typename T>
+inline D2<T>
+compose_each(D2<T> const & a, D2<T> const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a[i],b[i]);
+ return r;
+}
+
+template <typename T>
+inline D2<T>
+compose_each(T const & a, D2<T> const & b) {
+ D2<T> r;
+ for(unsigned i = 0; i < 2; i++)
+ r[i] = compose(a,b[i]);
+ return r;
+}
+
+
+template<typename T>
+inline Point
+D2<T>::operator()(double t) const {
+ Point p;
+ for(unsigned i = 0; i < 2; i++)
+ p[i] = (*this)[i](t);
+ return p;
+}
+
+//TODO: we might want to have this take a Point as the parameter.
+template<typename T>
+inline Point
+D2<T>::operator()(double x, double y) const {
+ Point p;
+ for(unsigned i = 0; i < 2; i++)
+ p[i] = (*this)[i](x, y);
+ return p;
+}
+
+
+template<typename T>
+D2<T> derivative(D2<T> const & a) {
+ return D2<T>(derivative(a[X]), derivative(a[Y]));
+}
+
+template<typename T>
+D2<T> integral(D2<T> const & a) {
+ return D2<T>(integral(a[X]), integral(a[Y]));
+}
+
+} //end namespace Geom
+
+
+
+//TODO: implement intersect
+
+
+#include "rect.h"
+#include "sbasis.h"
+#include "sbasis-2d.h"
+#include "piecewise.h"
+
+namespace Geom{
+
+//Some D2 Fragment implementation which requires rect:
+template <typename T>
+Rect bounds_fast(const D2<T> &a) {
+ boost::function_requires<FragmentConcept<T> >();
+ return Rect(bounds_fast(a[X]), bounds_fast(a[Y]));
+}
+template <typename T>
+Rect bounds_exact(const D2<T> &a) {
+ boost::function_requires<FragmentConcept<T> >();
+ return Rect(bounds_exact(a[X]), bounds_exact(a[Y]));
+}
+template <typename T>
+Rect bounds_local(const D2<T> &a, const Interval &t) {
+ boost::function_requires<FragmentConcept<T> >();
+ return Rect(bounds_local(a[X], t), bounds_local(a[Y], t));
+}
+
+//D2<SBasis> specific decls:
+
+inline D2<SBasis> compose(D2<SBasis> const & a, SBasis const & b) {
+ return D2<SBasis>(compose(a[X], b), compose(a[Y], b));
+}
+
+SBasis L2(D2<SBasis> const & a, unsigned k);
+double L2(D2<double> const & a);
+
+inline D2<SBasis> portion(D2<SBasis> const &M, double t0, double t1){
+ return D2<SBasis>(portion(M[0],t0,t1),portion(M[1],t0,t1));
+}
+
+D2<SBasis> multiply(Linear const & a, D2<SBasis> const & b);
+inline D2<SBasis> operator*(Linear const & a, D2<SBasis> const & b) { return multiply(a, b); }
+D2<SBasis> multiply(SBasis const & a, D2<SBasis> const & b);
+inline D2<SBasis> operator*(SBasis const & a, D2<SBasis> const & b) { return multiply(a, b); }
+D2<SBasis> truncate(D2<SBasis> const & a, unsigned terms);
+
+unsigned sbasis_size(D2<SBasis> const & a);
+double tail_error(D2<SBasis> const & a, unsigned tail);
+
+//Piecewise<D2<SBasis> > specific decls:
+
+Piecewise<D2<SBasis> > sectionize(D2<Piecewise<SBasis> > const &a);
+D2<Piecewise<SBasis> > make_cuts_independant(Piecewise<D2<SBasis> > const &a);
+Piecewise<D2<SBasis> > rot90(Piecewise<D2<SBasis> > const &a);
+Piecewise<SBasis> dot(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b);
+Piecewise<SBasis> cross(Piecewise<D2<SBasis> > const &a, Piecewise<D2<SBasis> > const &b);
+
+Piecewise<D2<SBasis> > force_continuity(Piecewise<D2<SBasis> > const &f,
+ double tol=0,
+ bool closed=false);
+
+class CoordIterator
+: public std::iterator<std::input_iterator_tag, SBasis const>
+{
+public:
+ CoordIterator(std::vector<D2<SBasis> >::const_iterator const &iter, unsigned d) : impl_(iter), ix_(d) {}
+
+ inline bool operator==(CoordIterator const &other) { return other.impl_ == impl_; }
+ inline bool operator!=(CoordIterator const &other) { return other.impl_ != impl_; }
+
+ inline SBasis operator*() const {
+ return (*impl_)[ix_];
+ }
+
+ inline CoordIterator &operator++() {
+ ++impl_;
+ return *this;
+ }
+ inline CoordIterator operator++(int) {
+ CoordIterator old=*this;
+ ++(*this);
+ return old;
+ }
+
+private:
+ std::vector<D2<SBasis> >::const_iterator impl_;
+ unsigned ix_;
+};
+
+inline CoordIterator iterateCoord(Piecewise<D2<SBasis> > const &a, unsigned d) {
+ return CoordIterator(a.segs.begin(), d);
+}
+
+//bounds specializations with order
+inline Rect bounds_fast(D2<SBasis> const & s, unsigned order=0) {
+ return Rect(bounds_fast(s[X], order),
+ bounds_fast(s[Y], order));
+}
+inline Rect bounds_local(D2<SBasis> const & s, Interval i, unsigned order=0) {
+ return Rect(bounds_local(s[X], i, order),
+ bounds_local(s[Y], i, order));
+}
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#endif
diff --git a/src/2geom/geom.cpp b/src/2geom/geom.cpp
new file mode 100644
index 000000000..d2f2ef29b
--- /dev/null
+++ b/src/2geom/geom.cpp
@@ -0,0 +1,218 @@
+/**
+ * \file src/geom.cpp
+ * \brief Various geometrical calculations.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "geom.h"
+#include "point.h"
+
+/**
+ * Finds the intersection of the two (infinite) lines
+ * defined by the points p such that dot(n0, p) == d0 and dot(n1, p) == d1.
+ *
+ * If the two lines intersect, then \a result becomes their point of
+ * intersection; otherwise, \a result remains unchanged.
+ *
+ * This function finds the intersection of the two lines (infinite)
+ * defined by n0.X = d0 and x1.X = d1. The algorithm is as follows:
+ * To compute the intersection point use kramer's rule:
+ * \verbatim
+ * convert lines to form
+ * ax + by = c
+ * dx + ey = f
+ *
+ * (
+ * e.g. a = (x2 - x1), b = (y2 - y1), c = (x2 - x1)*x1 + (y2 - y1)*y1
+ * )
+ *
+ * In our case we use:
+ * a = n0.x d = n1.x
+ * b = n0.y e = n1.y
+ * c = d0 f = d1
+ *
+ * so:
+ *
+ * adx + bdy = cd
+ * adx + aey = af
+ *
+ * bdy - aey = cd - af
+ * (bd - ae)y = cd - af
+ *
+ * y = (cd - af)/(bd - ae)
+ *
+ * repeat for x and you get:
+ *
+ * x = (fb - ce)/(bd - ae) \endverbatim
+ *
+ * If the denominator (bd-ae) is 0 then the lines are parallel, if the
+ * numerators are then 0 then the lines coincide.
+ *
+ * \todo Why not use existing but outcommented code below
+ * (HAVE_NEW_INTERSECTOR_CODE)?
+ */
+IntersectorKind
+line_intersection(Geom::Point const &n0, double const d0,
+ Geom::Point const &n1, double const d1,
+ Geom::Point &result)
+{
+ double denominator = dot(Geom::rot90(n0), n1);
+ double X = n1[Geom::Y] * d0 -
+ n0[Geom::Y] * d1;
+ /* X = (-d1, d0) dot (n0[Y], n1[Y]) */
+
+ if (denominator == 0) {
+ if ( X == 0 ) {
+ return coincident;
+ } else {
+ return parallel;
+ }
+ }
+
+ double Y = n0[Geom::X] * d1 -
+ n1[Geom::X] * d0;
+
+ result = Geom::Point(X, Y) / denominator;
+
+ return intersects;
+}
+
+
+
+
+/* ccw exists as a building block */
+int
+intersector_ccw(const Geom::Point& p0, const Geom::Point& p1,
+ const Geom::Point& p2)
+/* Determine which way a set of three points winds. */
+{
+ Geom::Point d1 = p1 - p0;
+ Geom::Point d2 = p2 - p0;
+ /* compare slopes but avoid division operation */
+ double c = dot(Geom::rot90(d1), d2);
+ if(c > 0)
+ return +1; // ccw - do these match def'n in header?
+ if(c < 0)
+ return -1; // cw
+
+ /* Colinear [or NaN]. Decide the order. */
+ if ( ( d1[0] * d2[0] < 0 ) ||
+ ( d1[1] * d2[1] < 0 ) ) {
+ return -1; // p2 < p0 < p1
+ } else if ( dot(d1,d1) < dot(d2,d2) ) {
+ return +1; // p0 <= p1 < p2
+ } else {
+ return 0; // p0 <= p2 <= p1
+ }
+}
+
+/** Determine whether two line segments intersect. This doesn't find
+ the point of intersection, use the line_intersect function above,
+ or the segment_intersection interface below.
+
+ \pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+*/
+static bool
+segment_intersectp(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11)
+{
+ if(p00 == p01) return false;
+ if(p10 == p11) return false;
+
+ /* true iff ( (the p1 segment straddles the p0 infinite line)
+ * and (the p0 segment straddles the p1 infinite line) ). */
+ return ((intersector_ccw(p00,p01, p10)
+ *intersector_ccw(p00, p01, p11)) <=0 )
+ &&
+ ((intersector_ccw(p10,p11, p00)
+ *intersector_ccw(p10, p11, p01)) <=0 );
+}
+
+
+/** Determine whether \& where two line segments intersect.
+
+If the two segments don't intersect, then \a result remains unchanged.
+
+\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+**/
+IntersectorKind
+segment_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result)
+{
+ if(segment_intersectp(p00, p01, p10, p11)) {
+ Geom::Point n0 = (p01 - p00).ccw();
+ double d0 = dot(n0,p00);
+
+ Geom::Point n1 = (p11 - p10).ccw();
+ double d1 = dot(n1,p10);
+ return line_intersection(n0, d0, n1, d1, result);
+ } else {
+ return no_intersection;
+ }
+}
+
+/** Determine whether \& where two line segments intersect.
+
+If the two segments don't intersect, then \a result remains unchanged.
+
+\pre neither segment is zero-length; i.e. p00 != p01 and p10 != p11.
+**/
+IntersectorKind
+line_twopoint_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result)
+{
+ Geom::Point n0 = (p01 - p00).ccw();
+ double d0 = dot(n0,p00);
+
+ Geom::Point n1 = (p11 - p10).ccw();
+ double d1 = dot(n1,p10);
+ return line_intersection(n0, d0, n1, d1, result);
+}
+
+/**
+ * polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon, given its
+ * vertices (x[0], y[0]) ... (x[n-1], y[n-1]). It is assumed that the contour is closed, i.e., that
+ * the vertex following (x[n-1], y[n-1]) is (x[0], y[0]). The algebraic sign of the area is
+ * positive for counterclockwise ordering of vertices in x-y plane; otherwise negative.
+
+ * Returned values:
+ 0 for normal execution;
+ 1 if the polygon is degenerate (number of vertices < 3);
+ 2 if area = 0 (and the centroid is undefined).
+
+ * for now we require the path to be a polyline and assume it is closed.
+**/
+
+int centroid(std::vector<Geom::Point> p, Geom::Point& centroid, double &area) {
+ const unsigned n = p.size();
+ if (n < 3)
+ return 1;
+ Geom::Point centroid_tmp(0,0);
+ double atmp = 0;
+ for (unsigned i = n-1, j = 0; j < n; i = j, j++) {
+ const double ai = -cross(p[j], p[i]);
+ atmp += ai;
+ centroid_tmp += (p[j] + p[i])*ai; // first moment.
+ }
+ area = atmp / 2;
+ if (atmp != 0) {
+ centroid = centroid_tmp / (3 * atmp);
+ return 0;
+ }
+ return 2;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/geom.h b/src/2geom/geom.h
new file mode 100644
index 000000000..5386edbd7
--- /dev/null
+++ b/src/2geom/geom.h
@@ -0,0 +1,68 @@
+/**
+ * \file geom.h
+ * \brief Various geometrical calculations
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ *
+ * Copyright (C) 1999-2002 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+//TODO: move somewhere else
+
+#include <vector>
+#include "point.h"
+
+enum IntersectorKind {
+ intersects = 0,
+ parallel,
+ coincident,
+ no_intersection
+};
+
+int
+intersector_ccw(const Geom::Point& p0, const Geom::Point& p1,
+ const Geom::Point& p2);
+
+/* intersectors */
+
+IntersectorKind
+line_intersection(Geom::Point const &n0, double const d0,
+ Geom::Point const &n1, double const d1,
+ Geom::Point &result);
+
+IntersectorKind
+segment_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result);
+
+IntersectorKind
+line_twopoint_intersect(Geom::Point const &p00, Geom::Point const &p01,
+ Geom::Point const &p10, Geom::Point const &p11,
+ Geom::Point &result);
+
+int centroid(std::vector<Geom::Point> p, Geom::Point& centroid, double &area);
diff --git a/src/2geom/interval.h b/src/2geom/interval.h
new file mode 100644
index 000000000..459f2cd49
--- /dev/null
+++ b/src/2geom/interval.h
@@ -0,0 +1,219 @@
+/*
+ * interval.h - Simple closed interval class
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * Original Rect/Range code by:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+#ifndef SEEN_INTERVAL_H
+#define SEEN_INTERVAL_H
+
+#include <assert.h>
+#include "coord.h"
+
+#include <boost/optional/optional.hpp>
+
+namespace Geom {
+
+//
+class Interval {
+private:
+ Coord _b[2];
+
+public:
+ //TODO: I just know this'll pop up somewhere, starting off someone's interval at 0... I can't see how to avoid this.
+ explicit Interval() { _b[0] = _b[1] = 0; }
+ explicit Interval(Coord u) { _b[0] = _b[1] = u; }
+ Interval(Coord u, Coord v) {
+ if(u < v) {
+ _b[0] = u; _b[1] = v;
+ } else {
+ _b[0] = v; _b[1] = u;
+ }
+ }
+
+ double operator[](unsigned i) const {
+ assert(i < 2);
+ return _b[i];
+ }
+ double& operator[](unsigned i) { return _b[i]; } //Trust the user...
+
+ Coord min() const { return _b[0]; }
+ Coord max() const { return _b[1]; }
+ Coord extent() const { return _b[1] - _b[0]; }
+ Coord middle() const { return (_b[1] + _b[0]) * 0.5; }
+
+ bool isEmpty() const { return _b[0] == _b[1]; }
+ bool contains(Coord val) const { return _b[0] <= val && val <= _b[1]; }
+ bool contains(const Interval & val) const { return _b[0] <= val._b[0] && val._b[1] <= _b[1]; }
+ bool intersects(const Interval & val) const {
+ return contains(val._b[0]) || contains(val._b[1]) || val.contains(*this);
+ }
+
+ static Interval fromArray(const Coord* c, int n) {
+ assert(n > 0);
+ Interval result(c[0]);
+ for(int i = 1; i < n; i++) result.extendTo(c[i]);
+ return result;
+ }
+
+ bool operator==(Interval other) { return _b[0] == other._b[0] && _b[1] == other._b[1]; }
+ bool operator!=(Interval other) { return _b[0] != other._b[0] || _b[1] != other._b[1]; }
+
+ //IMPL: OffsetableConcept
+ //TODO: rename output_type to something else in the concept
+ typedef Coord output_type;
+ Interval operator+(Coord amnt) {
+ return Interval(_b[0] + amnt, _b[1] + amnt);
+ }
+ Interval operator-(Coord amnt) {
+ return Interval(_b[0] - amnt, _b[1] - amnt);
+ }
+ Interval operator+=(Coord amnt) {
+ _b[0] += amnt; _b[1] += amnt;
+ return *this;
+ }
+ Interval operator-=(Coord amnt) {
+ _b[0] -= amnt; _b[1] -= amnt;
+ return *this;
+ }
+
+ //IMPL: ScalableConcept
+ Interval operator-() const { return Interval(*this); }
+ Interval operator*(Coord s) const { return Interval(_b[0]*s, _b[1]*s); }
+ Interval operator/(Coord s) const { return Interval(_b[0]/s, _b[1]/s); }
+ Interval operator*=(Coord s) {
+ if(s < 0) {
+ Coord temp = _b[0];
+ _b[0] = _b[1]*s;
+ _b[1] = temp*s;
+ } else {
+ _b[0] *= s;
+ _b[1] *= s;
+ }
+ return *this;
+ }
+ Interval operator/=(Coord s) {
+ //TODO: what about s=0?
+ if(s < 0) {
+ Coord temp = _b[0];
+ _b[0] = _b[1]/s;
+ _b[1] = temp/s;
+ } else {
+ _b[0] /= s;
+ _b[1] /= s;
+ }
+ return *this;
+ }
+
+ //TODO: NaN handleage for the next two?
+ //TODO: Evaluate if wrap behaviour is proper.
+ //If val > max, then rather than becoming a min==max range, it 'wraps' over
+ void setMin(Coord val) {
+ if(val > _b[1]) {
+ _b[0] = _b[1];
+ _b[1] = val;
+ } else {
+ _b[0] = val;
+ }
+ }
+ //If val < min, then rather than becoming a min==max range, it 'wraps' over
+ void setMax(Coord val) {
+ if(val < _b[0]) {
+ _b[1] = _b[0];
+ _b[0] = val;
+ } else {
+ _b[1] = val;
+ }
+ }
+
+ void extendTo(Coord val) {
+ if(val < _b[0]) _b[0] = val;
+ if(val > _b[1]) _b[1] = val; //no else, as we want to handle NaN
+ }
+
+ void expandBy(double amnt) {
+ _b[0] -= amnt;
+ _b[1] += amnt;
+ }
+
+ void unionWith(const Interval & a) {
+ if(a._b[0] < _b[0]) _b[0] = a._b[0];
+ if(a._b[1] > _b[1]) _b[1] = a._b[1];
+ }
+};
+
+//IMPL: AddableConcept
+inline Interval operator+(const Interval & a, const Interval & b) {
+ return Interval(a.min() + b.min(), a.max() + b.max());
+}
+inline Interval operator-(const Interval & a, const Interval & b) {
+ return Interval(a.min() - b.max(), a.max() - b.min());
+}
+inline Interval operator+=(Interval & a, const Interval & b) { a = a + b; return a; }
+inline Interval operator-=(Interval & a, const Interval & b) { a = a - b; return a; }
+
+//There might be impls of this based off sign checks
+inline Interval operator*(const Interval & a, const Interval & b) {
+ Interval res(a.min() * b.min());
+ res.extendTo(a.min() * b.max());
+ res.extendTo(a.max() * b.min());
+ res.extendTo(a.max() * b.max());
+ return res;
+}
+inline Interval operator*=(Interval & a, const Interval & b) { a = a * b; return a; }
+
+/* reinstate if useful (doesn't do the proper thing for 0 inclusion)
+inline Interval operator/(const Interval & a, const Interval & b) {
+ Interval res(a.min() / b.min());
+ res.extendTo(a.min() / b.max());
+ res.extendTo(a.max() / b.min());
+ res.extendTo(a.max() / b.max());
+ return res;
+}
+inline Interval operator/=(Interval & a, const Interval & b) { a = a / b; return a; }
+*/
+
+// 'union' conflicts with C keyword
+inline Interval unify(const Interval & a, const Interval & b) {
+ return Interval(std::min(a.min(), b.min()),
+ std::max(a.max(), b.max()));
+}
+inline boost::optional<Interval> intersect(const Interval & a, const Interval & b) {
+ Coord u = std::max(a.min(), b.min()),
+ v = std::min(a.max(), b.max());
+ //technically >= might be incorrect, but singulars suck
+ return u >= v ? boost::optional<Interval>()
+ : boost::optional<Interval>(Interval(u, v));
+}
+
+}
+#endif //SEEN_INTERVAL_H
diff --git a/src/2geom/isnan.h b/src/2geom/isnan.h
new file mode 100644
index 000000000..8b9ffae10
--- /dev/null
+++ b/src/2geom/isnan.h
@@ -0,0 +1,57 @@
+#ifndef __ISNAN_H__
+#define __ISNAN_H__
+
+/*
+ * Temporary fix for various misdefinitions of isnan().
+ * isnan() is becoming undef'd in some .h files.
+ * #include this last in your .cpp file to get it right.
+ *
+ * The problem is that isnan and isfinite are part of C99 but aren't part of
+ * the C++ standard (which predates C99).
+ *
+ * Authors:
+ * Inkscape groupies and obsessive-compulsives
+ *
+ * Copyright (C) 2004 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ *
+ * 2005 modification hereby placed in public domain. Probably supercedes the 2004 copyright
+ * for the code itself.
+ */
+
+#include <cmath>
+/* You might try changing the above to <cmath> if you have problems.
+ * Whether you use math.h or cmath, you may need to edit the .cpp file
+ * and/or other .h files to use the same header file.
+ */
+
+#if defined(__isnan)
+# define is_nan(_a) (__isnan(_a)) /* MacOSX/Darwin definition < 10.4 */
+#elif defined(WIN32) || defined(_isnan)
+# define is_nan(_a) (_isnan(_a)) /* Win32 definition */
+#elif defined(isnan) || defined(__FreeBSD__)
+# define is_nan(_a) (isnan(_a)) /* GNU definition */
+#else
+# define is_nan(_a) (std::isnan(_a))
+#endif
+/* If the above doesn't work, then try (a != a).
+ * Also, please report a bug as per http://www.inkscape.org/report_bugs.php,
+ * giving information about what platform and compiler version you're using.
+ */
+
+
+#if defined(__isfinite)
+# define is_finite(_a) (__isfinite(_a)) /* MacOSX/Darwin definition < 10.4 */
+#elif defined(isfinite)
+# define is_finite(_a) (isfinite(_a))
+#else
+# define is_finite(_a) (std::isfinite(_a))
+#endif
+/* If the above doesn't work, then try (finite(_a) && !isNaN(_a)) or (!isNaN((_a) - (_a))).
+ * Also, please report a bug as per http://www.inkscape.org/report_bugs.php,
+ * giving information about what platform and compiler version you're using.
+ */
+
+
+#endif /* __ISNAN_H__ */
diff --git a/src/2geom/linear.h b/src/2geom/linear.h
new file mode 100644
index 000000000..2b346468c
--- /dev/null
+++ b/src/2geom/linear.h
@@ -0,0 +1,172 @@
+/*
+ * linear.h - Linear fragment function class
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_LINEAR_H
+#define SEEN_LINEAR_H
+#include "isnan.h"
+#include "interval.h"
+
+namespace Geom{
+
+inline double lerp(double t, double a, double b) { return a*(1-t) + b*t; }
+
+class SBasis;
+
+class Hat{
+public:
+ Hat () {}
+ Hat(double d) :d(d) {}
+ operator double() const { return d; }
+ double d;
+};
+
+class Tri{
+public:
+ Tri () {}
+ Tri(double d) :d(d) {}
+ operator double() const { return d; }
+ double d;
+};
+
+class Linear{
+public:
+ double a[2];
+ Linear() {}
+ Linear(double aa, double b) {a[0] = aa; a[1] = b;}
+ Linear(Hat h, Tri t) {
+ a[0] = double(h) - double(t)/2;
+ a[1] = double(h) + double(t)/2;
+ }
+
+ Linear(Hat h) {
+ a[0] = double(h);
+ a[1] = double(h);
+ }
+
+ double operator[](const int i) const {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+ double& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < 2);
+ return a[i];
+ }
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+ inline bool isZero() const { return a[0] == 0 && a[1] == 0; }
+ inline bool isFinite() const { return is_finite(a[0]) && is_finite(a[1]); }
+
+ inline double at0() const { return a[0]; }
+ inline double at1() const { return a[1]; }
+
+ inline double valueAt(double t) const { return lerp(t, a[0], a[1]); }
+ inline double operator()(double t) const { return valueAt(t); }
+
+ //defined in sbasis.h
+ inline SBasis toSBasis() const;
+
+ inline Interval bounds_exact() const { return Interval(a[0], a[1]); }
+ inline Interval bounds_fast() const { return bounds_exact(); }
+ inline Interval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); }
+
+ operator Tri() const {
+ return a[1] - a[0];
+ }
+ operator Hat() const {
+ return (a[1] + a[0])/2;
+ }
+};
+
+inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); }
+
+//IMPL: AddableConcept
+inline Linear operator+(Linear const & a, Linear const & b) {
+ return Linear(a[0] + b[0], a[1] + b[1]);
+}
+inline Linear operator-(Linear const & a, Linear const & b) {
+ return Linear(a[0] - b[0], a[1] - b[1]);
+}
+inline Linear& operator+=(Linear & a, Linear const & b) {
+ a[0] += b[0]; a[1] += b[1];
+ return a;
+}
+inline Linear& operator-=(Linear & a, Linear const & b) {
+ a[0] -= b[0]; a[1] -= b[1];
+ return a;
+}
+//IMPL: OffsetableConcept
+inline Linear operator+(Linear const & a, double b) {
+ return Linear(a[0] + b, a[1] + b);
+}
+inline Linear operator-(Linear const & a, double b) {
+ return Linear(a[0] - b, a[1] - b);
+}
+inline Linear& operator+=(Linear & a, double b) {
+ a[0] += b; a[1] += b;
+ return a;
+}
+inline Linear& operator-=(Linear & a, double b) {
+ a[0] -= b; a[1] -= b;
+ return a;
+}
+//IMPL: boost::EqualityComparableConcept
+inline bool operator==(Linear const & a, Linear const & b) {
+ return a[0] == b[0] && a[1] == b[1];
+}
+inline bool operator!=(Linear const & a, Linear const & b) {
+ return a[0] != b[0] || a[1] != b[1];
+}
+//IMPL: ScalableConcept
+inline Linear operator-(Linear const &a) {
+ return Linear(-a[0], -a[1]);
+}
+inline Linear operator*(Linear const & a, double b) {
+ return Linear(a[0]*b, a[1]*b);
+}
+inline Linear operator/(Linear const & a, double b) {
+ return Linear(a[0]/b, a[1]/b);
+}
+inline Linear operator*=(Linear & a, double b) {
+ a[0] *= b; a[1] *= b;
+ return a;
+}
+inline Linear operator/=(Linear & a, double b) {
+ a[0] /= b; a[1] /= b;
+ return a;
+}
+};
+
+#endif //SEEN_LINEAR_H
diff --git a/src/2geom/makefile.in b/src/2geom/makefile.in
new file mode 100644
index 000000000..15dab3f9c
--- /dev/null
+++ b/src/2geom/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) 2geom/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) 2geom/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/2geom/matrix.cpp b/src/2geom/matrix.cpp
new file mode 100644
index 000000000..f53795943
--- /dev/null
+++ b/src/2geom/matrix.cpp
@@ -0,0 +1,243 @@
+#define __Geom_MATRIX_C__
+
+/** \file
+ * Various matrix routines. Currently includes some Geom::Rotate etc. routines too.
+ */
+
+/*
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * This code is in public domain
+ */
+
+#include "utils.h"
+#include "matrix.h"
+#include "point.h"
+
+namespace Geom {
+
+/** Creates a Matrix given an axis and origin point.
+ * The axis is represented as two vectors, which represent skew, rotation, and scaling in two dimensions.
+ * from_basis(Point(1, 0), Point(0, 1), Point(0, 0)) would return the identity matrix.
+
+ \param x_basis the vector for the x-axis.
+ \param y_basis the vector for the y-axis.
+ \param offset the translation applied by the matrix.
+ \return The new Matrix.
+ */
+//NOTE: Inkscape's version is broken, so when including this version, you'll have to search for code with this func
+//TODO: move to Matrix::from_basis
+Matrix from_basis(Point const x_basis, Point const y_basis, Point const offset) {
+ return Matrix(x_basis[X], x_basis[Y],
+ y_basis[X], y_basis[Y],
+ offset [X], offset [Y]);
+}
+
+Point Matrix::xAxis() const {
+ return Point(_c[0], _c[1]);
+}
+
+Point Matrix::yAxis() const {
+ return Point(_c[2], _c[3]);
+}
+
+/** Gets the translation imparted by the Matrix.
+ */
+Point Matrix::translation() const {
+ return Point(_c[4], _c[5]);
+}
+
+void Matrix::setXAxis(Point const &vec) {
+ for(int i = 0; i < 2; i++)
+ _c[i] = vec[i];
+}
+
+void Matrix::setYAxis(Point const &vec) {
+ for(int i = 0; i < 2; i++)
+ _c[i + 2] = vec[i];
+}
+
+/** Sets the translation imparted by the Matrix.
+ */
+void Matrix::setTranslation(Point const &loc) {
+ for(int i = 0; i < 2; i++)
+ _c[i + 4] = loc[i];
+}
+
+/** Calculates the amount of x-scaling imparted by the Matrix. This is the scaling applied to
+ * the original x-axis region. It is \emph{not} the overall x-scaling of the transformation.
+ * Equivalent to L2(m.xAxis())
+ */
+double Matrix::expansionX() const {
+ return sqrt(_c[0] * _c[0] + _c[1] * _c[1]);
+}
+
+/** Calculates the amount of y-scaling imparted by the Matrix. This is the scaling applied before
+ * the other transformations. It is \emph{not} the overall y-scaling of the transformation.
+ * Equivalent to L2(m.yAxis())
+ */
+double Matrix::expansionY() const {
+ return sqrt(_c[2] * _c[2] + _c[3] * _c[3]);
+}
+
+void Matrix::setExpansionX(double val) {
+ double exp_x = expansionX();
+ if(!near(exp_x, 0.0)) { //TODO: best way to deal with it is to skip op?
+ double coef = val / expansionX();
+ for(unsigned i=0;i<2;i++) _c[i] *= coef;
+ }
+}
+
+void Matrix::setExpansionY(double val) {
+ double exp_y = expansionY();
+ if(!near(exp_y, 0.0)) { //TODO: best way to deal with it is to skip op?
+ double coef = val / expansionY();
+ for(unsigned i=2;i<4;i++) _c[i] *= coef;
+ }
+}
+
+/** Sets this matrix to be the Identity Matrix. */
+void Matrix::setIdentity() {
+ _c[0] = 1.0; _c[1] = 0.0;
+ _c[2] = 0.0; _c[3] = 1.0;
+ _c[4] = 0.0; _c[5] = 0.0;
+}
+
+bool Matrix::isIdentity(Coord const eps) const {
+ return near(_c[0], 1.0) && near(_c[1], 0.0) &&
+ near(_c[2], 0.0) && near(_c[3], 1.0) &&
+ near(_c[4], 0.0) && near(_c[5], 0.0);
+}
+
+/** Answers the question "Does this matrix perform a translation, and \em{only} a translation?"
+ \param eps an epsilon value defaulting to EPSILON
+ \return A bool representing yes/no.
+ */
+bool Matrix::isTranslation(Coord const eps) const {
+ return near(_c[0], 1.0) && near(_c[1], 0.0) &&
+ near(_c[2], 0.0) && near(_c[3], 1.0) &&
+ !near(_c[4], 0.0) && !near(_c[5], 0.0);
+}
+
+/** Answers the question "Does this matrix perform a scale, and \em{only} a Scale?"
+ \param eps an epsilon value defaulting to EPSILON
+ \return A bool representing yes/no.
+ */
+bool Matrix::isScale(Coord const eps) const {
+ return !near(_c[0], 1.0) || !near(_c[3], 1.0) && //NOTE: these are the diags, and the next line opposite diags
+ near(_c[1], 0.0) && near(_c[2], 0.0) &&
+ near(_c[4], 0.0) && near(_c[5], 0.0);
+}
+
+/** Answers the question "Does this matrix perform a uniform scale, and \em{only} a uniform scale?"
+ \param eps an epsilon value defaulting to EPSILON
+ \return A bool representing yes/no.
+ */
+bool Matrix::isUniformScale(Coord const eps) const {
+ return !near(_c[0], 1.0) && near(_c[0], _c[3]) &&
+ near(_c[1], 0.0) && near(_c[2], 0.0) &&
+ near(_c[4], 0.0) && near(_c[5], 0.0);
+}
+
+/** Answers the question "Does this matrix perform a rotation, and \em{only} a rotation?"
+ \param eps an epsilon value defaulting to EPSILON
+ \return A bool representing yes/no.
+ */
+bool Matrix::isRotation(Coord const eps) const {
+ return !near(_c[0], _c[3]) && near(_c[1], -_c[2]) &&
+ near(_c[4], 0.0) && near(_c[5], 0.0) &&
+ near(_c[0]*_c[0] + _c[1]*_c[1], 1.0);
+}
+
+/** Returns the Scale/Rotate/skew part of the matrix without the translation part. */
+Matrix Matrix::without_translation() const {
+ return Matrix(_c[0], _c[1], _c[2], _c[3], 0, 0);
+}
+
+/** Attempts to calculate the inverse of a matrix.
+ * This is a Matrix such that m * m.inverse() is very near (hopefully < epsilon difference) the identity Matrix.
+ * \textbf{The Identity Matrix is returned if the matrix has no inverse.}
+ \return The inverse of the Matrix if defined, otherwise the Identity Matrix.
+ */
+Matrix Matrix::inverse() const {
+ Matrix d;
+
+ Geom::Coord const determ = det();
+ if (!near(determ, 0.0)) {
+ Geom::Coord const ideterm = 1.0 / determ;
+
+ d._c[0] = _c[3] * ideterm;
+ d._c[1] = -_c[1] * ideterm;
+ d._c[2] = -_c[2] * ideterm;
+ d._c[3] = _c[0] * ideterm;
+ d._c[4] = -_c[4] * d._c[0] - _c[5] * d._c[2];
+ d._c[5] = -_c[4] * d._c[1] - _c[5] * d._c[3];
+ } else {
+ d.setIdentity();
+ }
+
+ return d;
+}
+
+/** Calculates the determinant of a Matrix. */
+Geom::Coord Matrix::det() const {
+ return _c[0] * _c[3] - _c[1] * _c[2];
+}
+
+/** Calculates the scalar of the descriminant of the Matrix.
+ * This is simply the absolute value of the determinant.
+ */
+Geom::Coord Matrix::descrim2() const {
+ return fabs(det());
+}
+
+/** Calculates the descriminant of the Matrix. */
+Geom::Coord Matrix::descrim() const {
+ return sqrt(descrim2());
+}
+
+Matrix operator*(Matrix const &m0, Matrix const &m1) {
+ Matrix ret;
+ for(int a = 0; a < 5; a += 2) {
+ for(int b = 0; b < 2; b++) {
+ ret[a + b] = m0[a] * m1[b] + m0[a + 1] * m1[b + 2];
+ }
+ }
+ ret[4] += m1[4];
+ ret[5] += m1[5];
+ return ret;
+}
+
+//TODO: What's this!?!
+Matrix elliptic_quadratic_form(Matrix const &m) {
+ double const od = m[0] * m[1] + m[2] * m[3];
+ return Matrix(m[0]*m[0] + m[1]*m[1], od,
+ od, m[2]*m[2] + m[3]*m[3],
+ 0, 0);
+}
+
+Eigen::Eigen(Matrix const &m) {
+ double const B = -m[0] - m[3];
+ double const C = m[0]*m[3] - m[1]*m[2];
+ double const center = -B/2.0;
+ double const delta = sqrt(B*B-4*C)/2.0;
+ values[0] = center + delta; values[1] = center - delta;
+ for (int i = 0; i < 2; i++) {
+ vectors[i] = unit_vector(rot90(Point(m[0]-values[i], m[1])));
+ }
+}
+
+} //namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/matrix.h b/src/2geom/matrix.h
new file mode 100644
index 000000000..6a45b50c8
--- /dev/null
+++ b/src/2geom/matrix.h
@@ -0,0 +1,158 @@
+#ifndef __Geom_MATRIX_H__
+#define __Geom_MATRIX_H__
+
+/** \file
+ * Definition of Geom::Matrix types.
+ *
+ * Main authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>:
+ * Original NRMatrix definition and related macros.
+ *
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>:
+ * Geom::Matrix class version of the above.
+ *
+ * Michael G. Sloan <mgsloan@gmail.com>:
+ * Reorganization and additions.
+ *
+ * This code is in public domain.
+ */
+
+//#include <glib/gmessages.h>
+
+#include "point.h"
+
+namespace Geom {
+
+/**
+ * The Matrix class.
+ *
+ * For purposes of multiplication, points should be thought of as row vectors
+ *
+ * \f$(p_X p_Y 1)\f$
+ *
+ * to be right-multiplied by transformation matrices of the form
+ * \f[
+ \left[
+ \begin{array}{ccc}
+ c_0&c_1&0 \\
+ c_2&c_3&0 \\
+ c_4&c_5&1
+ \end{array}
+ \right]
+ \f]
+ * (so the columns of the matrix correspond to the columns (elements) of the result,
+ * and the rows of the matrix correspond to columns (elements) of the "input").
+ */
+class Matrix {
+ private:
+ Coord _c[6];
+ public:
+ Matrix() {}
+
+ Matrix(Matrix const &m) {
+ for(int i = 0; i < 6; i++) {
+ _c[i] = m[i];
+ }
+ }
+
+ Matrix(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) {
+ _c[0] = c0; _c[1] = c1;
+ _c[2] = c2; _c[3] = c3;
+ _c[4] = c4; _c[5] = c5;
+ }
+
+ Matrix &operator=(Matrix const &m) {
+ for(int i = 0; i < 6; i++)
+ _c[i] = m._c[i];
+ return *this;
+ }
+
+ inline Coord operator[](unsigned const i) const { return _c[i]; }
+ inline Coord &operator[](unsigned const i) { return _c[i]; }
+
+
+ Point xAxis() const;
+ Point yAxis() const;
+ Point translation() const;
+ void setXAxis(Point const &vec);
+ void setYAxis(Point const &vec);
+ void setTranslation(Point const &loc);
+
+ double expansionX() const;
+ double expansionY() const;
+ void setExpansionX(double val);
+ void setExpansionY(double val);
+
+ void setIdentity();
+
+ bool isIdentity(Coord eps = EPSILON) const;
+ bool isTranslation(Coord eps = EPSILON) const;
+ bool isRotation(double eps = EPSILON) const;
+ bool isScale(double eps = EPSILON) const;
+ bool isUniformScale(double eps = EPSILON) const;
+
+ Matrix without_translation() const;
+
+ Matrix inverse() const;
+
+ Coord det() const;
+ Coord descrim2() const;
+ Coord descrim() const;
+};
+
+Matrix operator*(Matrix const &a, Matrix const &b);
+
+/** A function to print out the Matrix (for debugging) */
+inline std::ostream &operator<< (std::ostream &out_file, const Geom::Matrix &m) {
+ out_file << "A: " << m[0] << " C: " << m[2] << " E: " << m[4] << "\n";
+ out_file << "B: " << m[1] << " D: " << m[3] << " F: " << m[5] << "\n";
+ return out_file;
+}
+
+/** Given a matrix m such that unit_circle = m*x, this returns the
+ * quadratic form x*A*x = 1. */
+Matrix elliptic_quadratic_form(Matrix const &m);
+
+/** Given a matrix (ignoring the translation) this returns the eigen
+ * values and vectors. */
+class Eigen{
+public:
+ Point vectors[2];
+ double values[2];
+ Eigen(Matrix const &m);
+};
+
+// Matrix factories
+Matrix from_basis(const Point x_basis, const Point y_basis, const Point offset=Point(0,0));
+
+/** Returns the Identity Matrix. */
+inline Matrix identity() {
+ return Matrix(1.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 0.0);
+}
+
+inline bool operator==(Matrix const &a, Matrix const &b) {
+ for(unsigned i = 0; i < 6; ++i) {
+ if ( a[i] != b[i] ) return false;
+ }
+ return true;
+}
+inline bool operator!=(Matrix const &a, Matrix const &b) { return !( a == b ); }
+
+
+
+} /* namespace Geom */
+
+#endif /* !__Geom_MATRIX_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/path.cpp b/src/2geom/path.cpp
new file mode 100644
index 000000000..91868eb7e
--- /dev/null
+++ b/src/2geom/path.cpp
@@ -0,0 +1,367 @@
+/*
+ * Path - Series of continuous curves
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include "path.h"
+
+namespace Geom {
+
+namespace {
+
+enum Cmp {
+ LESS_THAN=-1,
+ GREATER_THAN=1,
+ EQUAL_TO=0
+};
+
+template <typename T1, typename T2>
+inline Cmp cmp(T1 const &a, T2 const &b) {
+ if ( a < b ) {
+ return LESS_THAN;
+ } else if ( b < a ) {
+ return GREATER_THAN;
+ } else {
+ return EQUAL_TO;
+ }
+}
+
+}
+
+boost::optional<int> CurveHelpers::sbasis_winding(D2<SBasis> const &sb, Point p) {
+ Interval ix = bounds_fast(sb[X]);
+
+ if ( p[X] > ix.max() ) { /* ray does not intersect bbox */
+ return 0;
+ }
+
+ SBasis fy = sb[Y];
+ fy -= p[Y];
+
+ if (fy.empty()) { /* coincident horizontal segment */
+ return boost::optional<int>();
+ }
+
+ if ( p[X] < ix.min() ) { /* ray does not originate in bbox */
+ double y = p[Y];
+ /* winding determined by position of endpoints */
+ Cmp initial_to_ray = cmp(fy[0][0], y);
+ Cmp final_to_ray = cmp(fy[0][1], y);
+ switch (cmp(final_to_ray, initial_to_ray)) {
+ case GREATER_THAN:
+ /* exclude final endpoint */
+ return ( final_to_ray != EQUAL_TO );
+ case LESS_THAN:
+ /* exclude final endpoint */
+ return -( final_to_ray != EQUAL_TO );
+ default:
+ /* any intersections cancel out */
+ return 0;
+ }
+ } else { /* ray originates in bbox */
+ std::vector<double> ts = roots(fy);
+
+ static const unsigned MAX_DERIVATIVES=8;
+ boost::optional<SBasis> ds[MAX_DERIVATIVES];
+ ds[0] = derivative(fy);
+
+ /* winding determined by summing signs of derivatives at intersections */
+ int winding=0;
+ for ( std::vector<double>::iterator ti = ts.begin()
+ ; ti != ts.end()
+ ; ++ti )
+ {
+ double t = *ti;
+ if ( sb[X](t) >= p[X] ) { /* root is ray intersection */
+ for ( boost::optional<SBasis> *di = ds
+ ; di != ( ds + MAX_DERIVATIVES )
+ ; ++di )
+ {
+ if (!*di) {
+ *di = derivative(**(di-1));
+ }
+ switch (cmp((**di)(t), 0)) {
+ case GREATER_THAN:
+ if ( t < 1 ) { /* exclude final endpoint */
+ winding += 1;
+ }
+ goto next_root;
+ case LESS_THAN:
+ if ( t < 1 ) { /* exclude final endpoint */
+ winding -= 1;
+ }
+ goto next_root;
+ default: (void)0;
+ /* give up */
+ };
+ }
+ }
+next_root: (void)0;
+ }
+
+ return winding;
+ }
+}
+
+Rect BezierHelpers::bounds(unsigned degree, Point const *points) {
+ Point min=points[0];
+ Point max=points[0];
+ for ( unsigned i = 1 ; i <= degree ; ++i ) {
+ for ( unsigned axis = 0 ; axis < 2 ; ++axis ) {
+ min[axis] = std::min(min[axis], points[i][axis]);
+ max[axis] = std::max(max[axis], points[i][axis]);
+ }
+ }
+ return Rect(min, max);
+}
+
+Point BezierHelpers::point_and_derivatives_at(Coord t,
+ unsigned degree,
+ Point const *points,
+ unsigned n_derivs,
+ Point *derivs)
+{
+ return Point(0,0); // TODO
+}
+
+Geom::Point
+BezierHelpers::subdivideArr(Coord t, // Parameter value
+ unsigned degree, // Degree of bezier curve
+ Geom::Point const *V, // Control pts
+ Geom::Point *Left, // RETURN left half ctl pts
+ Geom::Point *Right) // RETURN right half ctl pts
+{
+ Geom::Point Vtemp[degree+1][degree+1];
+
+ /* Copy control points */
+ std::copy(V, V+degree+1, Vtemp[0]);
+
+ /* Triangle computation */
+ for (unsigned i = 1; i <= degree; i++) {
+ for (unsigned j = 0; j <= degree - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+
+ for (unsigned j = 0; j <= degree; j++)
+ Left[j] = Vtemp[j][0];
+ for (unsigned j = 0; j <= degree; j++)
+ Right[j] = Vtemp[degree-j][j];
+
+ return (Vtemp[degree][0]);
+}
+
+void Path::swap(Path &other) {
+ std::swap(curves_, other.curves_);
+ std::swap(closed_, other.closed_);
+ std::swap(*final_, *other.final_);
+ curves_[curves_.size()-1] = final_;
+ other.curves_[other.curves_.size()-1] = other.final_;
+}
+
+Rect Path::bounds_fast() const {
+ Rect bounds=front().bounds_fast();
+ for ( const_iterator iter=++begin(); iter != end() ; ++iter ) {
+ bounds.unionWith(iter->bounds_fast());
+ }
+ return bounds;
+}
+
+Rect Path::bounds_exact() const {
+ Rect bounds=front().bounds_exact();
+ for ( const_iterator iter=++begin(); iter != end() ; ++iter ) {
+ bounds.unionWith(iter->bounds_exact());
+ }
+ return bounds;
+}
+
+int Path::winding(Point p) const {
+ int winding = 0;
+ boost::optional<Cmp> ignore = boost::optional<Cmp>();
+ for ( const_iterator iter = begin()
+ ; iter != end_closed()
+ ; ++iter )
+ {
+ boost::optional<int> w = iter->winding(p);
+ if (w) {
+ winding += *w;
+ ignore = boost::optional<Cmp>();
+ } else {
+ Point initial = iter->initialPoint();
+ Point final = iter->finalPoint();
+ switch (cmp(initial[X], final[X])) {
+ case GREATER_THAN:
+ if ( !ignore || *ignore != GREATER_THAN ) { /* ignore repeated */
+ winding += 1;
+ ignore = GREATER_THAN;
+ }
+ break;
+ case LESS_THAN:
+ if ( !ignore || *ignore != LESS_THAN ) { /* ignore repeated */
+ if ( p[X] < final[X] ) { /* ignore final point */
+ winding -= 1;
+ ignore = LESS_THAN;
+ }
+ }
+ break;
+ case EQUAL_TO:
+ /* always ignore null segments */
+ break;
+ }
+ }
+ }
+ return winding;
+}
+
+void Path::append(Curve const &curve) {
+ if ( curves_.front() != final_ && curve.initialPoint() != (*final_)[0] ) {
+ throw ContinuityError();
+ }
+ do_append(curve.duplicate());
+}
+
+void Path::append(D2<SBasis> const &curve) {
+ if ( curves_.front() != final_ ) {
+ for ( int i = 0 ; i < 2 ; ++i ) {
+ if ( curve[i][0][0] != (*final_)[0][i] ) {
+ throw ContinuityError();
+ }
+ }
+ }
+ do_append(new SBasisCurve(curve));
+}
+
+void Path::do_update(Sequence::iterator first_replaced,
+ Sequence::iterator last_replaced,
+ Sequence::iterator first,
+ Sequence::iterator last)
+{
+ // note: modifies the contents of [first,last)
+
+ check_continuity(first_replaced, last_replaced, first, last);
+ delete_range(first_replaced, last_replaced);
+ if ( ( last - first ) == ( last_replaced - first_replaced ) ) {
+ std::copy(first, last, first_replaced);
+ } else {
+ // this approach depends on std::vector's behavior WRT iterator stability
+ curves_.erase(first_replaced, last_replaced);
+ curves_.insert(first_replaced, first, last);
+ }
+
+ if ( curves_.front() != final_ ) {
+ (*final_)[0] = back().finalPoint();
+ (*final_)[1] = front().initialPoint();
+ }
+}
+
+void Path::do_append(Curve *curve) {
+ if ( curves_.front() == final_ ) {
+ (*final_)[1] = curve->initialPoint();
+ }
+ curves_.insert(curves_.end()-1, curve);
+ (*final_)[0] = curve->finalPoint();
+}
+
+void Path::delete_range(Sequence::iterator first, Sequence::iterator last) {
+ for ( Sequence::iterator iter=first ; iter != last ; ++iter ) {
+ delete *iter;
+ }
+}
+
+void Path::check_continuity(Sequence::iterator first_replaced,
+ Sequence::iterator last_replaced,
+ Sequence::iterator first,
+ Sequence::iterator last)
+{
+ if ( first != last ) {
+ if ( first_replaced != curves_.begin() ) {
+ if ( (*first_replaced)->initialPoint() != (*first)->initialPoint() ) {
+ throw ContinuityError();
+ }
+ }
+ if ( last_replaced != (curves_.end()-1) ) {
+ if ( (*(last_replaced-1))->finalPoint() != (*(last-1))->finalPoint() ) {
+ throw ContinuityError();
+ }
+ }
+ } else if ( first_replaced != last_replaced && first_replaced != curves_.begin() && last_replaced != curves_.end()-1) {
+ if ( (*first_replaced)->initialPoint() !=
+ (*(last_replaced-1))->finalPoint() )
+ {
+ throw ContinuityError();
+ }
+ }
+}
+
+Rect SBasisCurve::bounds_fast() const {
+ throw NotImplemented();
+ return Rect(Point(0,0), Point(0,0));
+}
+
+Rect SBasisCurve::bounds_exact() const {
+ throw NotImplemented();
+ return Rect(Point(0,0), Point(0,0));
+}
+
+Point SBasisCurve::pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs) const {
+ throw NotImplemented();
+ return Point(0,0);
+}
+
+Path const &SBasisCurve::subdivide(Coord t, Path &out) const {
+ throw NotImplemented();
+}
+
+Rect SVGEllipticalArc::bounds_fast() const {
+ throw NotImplemented();
+}
+Rect SVGEllipticalArc::bounds_exact() const {
+ throw NotImplemented();
+}
+
+Point SVGEllipticalArc::pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs) const {
+ throw NotImplemented();
+}
+
+D2<SBasis> SVGEllipticalArc::sbasis() const {
+ throw NotImplemented();
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2 :
+*/
+
diff --git a/src/2geom/path.h b/src/2geom/path.h
new file mode 100644
index 000000000..31a7173b7
--- /dev/null
+++ b/src/2geom/path.h
@@ -0,0 +1,632 @@
+/*
+ * Path - Series of continuous curves
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_GEOM_PATH_H
+#define SEEN_GEOM_PATH_H
+
+#include "point.h"
+#include <iterator>
+#include <algorithm>
+#include <exception>
+#include <stdexcept>
+#include <boost/optional/optional.hpp>
+#include "d2.h"
+#include "bezier-to-sbasis.h"
+
+namespace Geom {
+
+class Path;
+
+class Curve {
+public:
+ virtual ~Curve() {}
+
+ virtual Point initialPoint() const = 0;
+ virtual Point finalPoint() const = 0;
+
+ virtual Curve *duplicate() const = 0;
+
+ virtual Rect bounds_fast() const = 0;
+ virtual Rect bounds_exact() const = 0;
+
+ virtual boost::optional<int> winding(Point p) const = 0;
+
+ virtual Path const &subdivide(Coord t, Path &out) const = 0;
+
+ Point pointAt(Coord t) const { return pointAndDerivativesAt(t, 0, NULL); }
+ virtual Point pointAndDerivativesAt(Coord t, unsigned n, Point *ds) const = 0;
+ virtual D2<SBasis> sbasis() const = 0;
+};
+
+struct CurveHelpers {
+protected:
+ static boost::optional<int> sbasis_winding(D2<SBasis> const &sbasis, Point p);
+};
+
+struct BezierHelpers {
+protected:
+ static Rect bounds(unsigned degree, Point const *points);
+ static Point point_and_derivatives_at(Coord t,
+ unsigned degree, Point const *points,
+ unsigned n_derivs, Point *derivs);
+ static Point subdivideArr(Coord t, unsigned degree, Point const *V,
+ Point *Left, Point *Right);
+
+};
+
+template <unsigned bezier_degree>
+class Bezier : public Curve, private CurveHelpers, private BezierHelpers {
+public:
+ template <unsigned required_degree>
+ static void assert_degree(Bezier<required_degree> const *) {}
+
+ Bezier() {}
+
+ // default copy
+ // default assign
+
+ Bezier(Point c0, Point c1) {
+ assert_degree<1>(this);
+ c_[0] = c0;
+ c_[1] = c1;
+ }
+
+ Bezier(Point c0, Point c1, Point c2) {
+ assert_degree<2>(this);
+ c_[0] = c0;
+ c_[1] = c1;
+ c_[2] = c2;
+ }
+
+ Bezier(Point c0, Point c1, Point c2, Point c3) {
+ assert_degree<3>(this);
+ c_[0] = c0;
+ c_[1] = c1;
+ c_[2] = c2;
+ c_[3] = c3;
+ }
+
+ unsigned degree() const { return bezier_degree; }
+
+ Curve *duplicate() const { return new Bezier(*this); }
+
+ Point initialPoint() const { return c_[0]; }
+ Point finalPoint() const { return c_[bezier_degree]; }
+
+ Point &operator[](int index) { return c_[index]; }
+ Point const &operator[](int index) const { return c_[index]; }
+
+ Rect bounds_fast() const { return bounds(bezier_degree, c_); }
+ Rect bounds_exact() const { return bounds(bezier_degree, c_); }
+
+ boost::optional<int> winding(Point p) const {
+ return sbasis_winding(sbasis(), p);
+ }
+
+ Path const &subdivide(Coord t, Path &out) const;
+
+ Point pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs)
+ const
+ {
+ return point_and_derivatives_at(t, bezier_degree, c_, n_derivs, derivs);
+ }
+
+ D2<SBasis> sbasis() const {
+ return handles_to_sbasis<bezier_degree>(c_);
+ }
+
+protected:
+ Bezier(Point c[]) {
+ std::copy(c, c+bezier_degree+1, c_);
+ }
+
+private:
+ Point c_[bezier_degree+1];
+};
+
+// Bezier<0> is meaningless; specialize it out
+template<> class Bezier<0> { Bezier(); };
+
+typedef Bezier<1> LineSegment;
+typedef Bezier<2> QuadraticBezier;
+typedef Bezier<3> CubicBezier;
+
+class SVGEllipticalArc : public Curve, private CurveHelpers {
+public:
+ SVGEllipticalArc() {}
+
+ SVGEllipticalArc(Point initial, double rx, double ry,
+ double x_axis_rotation, bool large_arc,
+ bool sweep, Point final)
+ : initial_(initial), rx_(rx), ry_(ry), x_axis_rotation_(x_axis_rotation),
+ large_arc_(large_arc), sweep_(sweep), final_(final)
+ {}
+
+ Curve *duplicate() const { return new SVGEllipticalArc(*this); }
+
+ Point initialPoint() const { return initial_; }
+ Point finalPoint() const { return final_; }
+
+ Rect bounds_fast() const;
+ Rect bounds_exact() const;
+
+ boost::optional<int> winding(Point p) const {
+ return sbasis_winding(sbasis(), p);
+ }
+
+ Path const &subdivide(Coord t, Path &out) const;
+
+ Point pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs) const;
+
+ D2<SBasis> sbasis() const;
+
+private:
+ Point initial_;
+ double rx_;
+ double ry_;
+ double x_axis_rotation_;
+ bool large_arc_;
+ bool sweep_;
+ Point final_;
+};
+
+class SBasisCurve : public Curve, private CurveHelpers {
+private:
+ SBasisCurve();
+public:
+ explicit SBasisCurve(D2<SBasis> const &coeffs)
+ : coeffs_(coeffs) {}
+
+ Point initialPoint() const {
+ return Point(coeffs_[X][0][0], coeffs_[Y][0][0]);
+ }
+ Point finalPoint() const {
+ return Point(coeffs_[X][0][1], coeffs_[Y][0][1]);
+ }
+
+ Curve *duplicate() const { return new SBasisCurve(*this); }
+
+ Rect bounds_fast() const;
+ Rect bounds_exact() const;
+
+ boost::optional<int> winding(Point p) const {
+ return sbasis_winding(coeffs_, p);
+ }
+
+ Path const &subdivide(Coord t, Path &out) const;
+
+ Point pointAndDerivativesAt(Coord t, unsigned n_derivs, Point *derivs) const;
+
+ D2<SBasis> sbasis() const { return coeffs_; }
+
+private:
+ D2<SBasis> coeffs_;
+};
+
+template <typename IteratorImpl>
+class BaseIterator
+: public std::iterator<std::forward_iterator_tag, Curve const>
+{
+public:
+ BaseIterator() {}
+
+ // default construct
+ // default copy
+
+ bool operator==(BaseIterator const &other) {
+ return other.impl_ == impl_;
+ }
+ bool operator!=(BaseIterator const &other) {
+ return other.impl_ != impl_;
+ }
+
+ Curve const &operator*() const { return **impl_; }
+ Curve const *operator->() const { return *impl_; }
+
+ BaseIterator &operator++() {
+ ++impl_;
+ return *this;
+ }
+ BaseIterator operator++(int) {
+ BaseIterator old=*this;
+ ++(*this);
+ return old;
+ }
+
+private:
+ BaseIterator(IteratorImpl const &pos) : impl_(pos) {}
+
+ IteratorImpl impl_;
+ friend class Path;
+};
+
+template <typename Iterator>
+class DuplicatingIterator
+: public std::iterator<std::input_iterator_tag, Curve *>
+{
+public:
+ DuplicatingIterator() {}
+ DuplicatingIterator(Iterator const &iter) : impl_(iter) {}
+
+ bool operator==(DuplicatingIterator const &other) {
+ return other.impl_ == impl_;
+ }
+ bool operator!=(DuplicatingIterator const &other) {
+ return other.impl_ != impl_;
+ }
+
+ Curve *operator*() const { return (*impl_)->duplicate(); }
+
+ DuplicatingIterator &operator++() {
+ ++impl_;
+ return *this;
+ }
+ DuplicatingIterator operator++(int) {
+ DuplicatingIterator old=*this;
+ ++(*this);
+ return old;
+ }
+
+private:
+ Iterator impl_;
+};
+
+class ContinuityError : public std::runtime_error {
+public:
+ ContinuityError() : runtime_error("non-contiguous path") {}
+ ContinuityError(std::string const &message) : runtime_error(message) {}
+};
+
+class Path {
+private:
+ typedef std::vector<Curve *> Sequence;
+
+public:
+ typedef BaseIterator<Sequence::iterator> iterator;
+ typedef BaseIterator<Sequence::const_iterator> const_iterator;
+ typedef Sequence::size_type size_type;
+ typedef Sequence::difference_type difference_type;
+
+ Path()
+ : final_(new LineSegment()), closed_(false)
+ {
+ curves_.push_back(final_);
+ }
+
+ Path(Path const &other)
+ : final_(new LineSegment()), closed_(other.closed_)
+ {
+ curves_.push_back(final_);
+ insert(begin(), other.begin(), other.end());
+ }
+
+ explicit Path(Point p)
+ : final_(new LineSegment(p, p)), closed_(false)
+ {
+ curves_.push_back(final_);
+ }
+
+ template <typename Impl>
+ Path(BaseIterator<Impl> first, BaseIterator<Impl> last, bool closed=false)
+ : closed_(closed), final_(new LineSegment())
+ {
+ curves_.push_back(final_);
+ insert(begin(), first, last);
+ }
+
+ ~Path() {
+ delete_range(curves_.begin(), curves_.end()-1);
+ delete final_;
+ }
+
+ Path &operator=(Path const &other) {
+ clear();
+ insert(begin(), other.begin(), other.end());
+ close(other.closed_);
+ return *this;
+ }
+
+ void swap(Path &other);
+
+ Curve const &operator[](unsigned i) const { return *curves_[i]; }
+
+ iterator begin() { return curves_.begin(); }
+ iterator end() { return curves_.end()-1; }
+
+ Curve const &front() const { return *curves_[0]; }
+ Curve const &back() const { return *curves_[curves_.size()-2]; }
+
+ const_iterator begin() const { return curves_.begin(); }
+ const_iterator end() const { return curves_.end()-1; }
+
+ const_iterator end_open() const { return curves_.end()-1; }
+ const_iterator end_closed() const { return curves_.end(); }
+ const_iterator end_default() const {
+ return ( closed_ ? end_closed() : end_open() );
+ }
+
+ size_type size() const { return curves_.size()-1; }
+ size_type max_size() const { return curves_.max_size()-1; }
+
+ bool empty() const { return curves_.size() == 1; }
+ bool closed() const { return closed_; }
+ void close(bool closed=true) { closed_ = closed; }
+
+ int winding(Point p) const;
+
+ Rect bounds_fast() const;
+ Rect bounds_exact() const;
+
+ Piecewise<D2<SBasis> > toPwSb() const {
+ Piecewise<D2<SBasis> > ret;
+ ret.push_cut(0);
+ for(unsigned i = 0; i < size() + (closed_ ? 1 : 0); i++) {
+ ret.push(curves_[i]->sbasis(), i+1);
+ }
+ return ret;
+ }
+
+ void insert(iterator pos, Curve const &curve) {
+ Sequence source(1, curve.duplicate());
+ try {
+ do_update(pos.impl_, pos.impl_, source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ template <typename Impl>
+ void insert(iterator pos, BaseIterator<Impl> first, BaseIterator<Impl> last)
+ {
+ Sequence source(DuplicatingIterator<Impl>(first.impl_),
+ DuplicatingIterator<Impl>(last.impl_));
+ try {
+ do_update(pos.impl_, pos.impl_, source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ void clear() {
+ do_update(curves_.begin(), curves_.end()-1,
+ curves_.begin(), curves_.begin());
+ }
+
+ void erase(iterator pos) {
+ do_update(pos.impl_, pos.impl_+1, curves_.begin(), curves_.begin());
+ }
+
+ void erase(iterator first, iterator last) {
+ do_update(first.impl_, last.impl_, curves_.begin(), curves_.begin());
+ }
+
+ void replace(iterator replaced, Curve const &curve) {
+ Sequence source(1, curve.duplicate());
+ try {
+ do_update(replaced.impl_, replaced.impl_+1, source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ void replace(iterator first_replaced, iterator last_replaced,
+ Curve const &curve)
+ {
+ Sequence source(1, curve.duplicate());
+ try {
+ do_update(first_replaced.impl_, last_replaced.impl_,
+ source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ template <typename Impl>
+ void replace(iterator replaced,
+ BaseIterator<Impl> first, BaseIterator<Impl> last)
+ {
+ Sequence source(DuplicatingIterator<Impl>(first.impl_),
+ DuplicatingIterator<Impl>(last.impl_));
+ try {
+ do_update(replaced.impl_, replaced.impl_+1, source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ template <typename Impl>
+ void replace(iterator first_replaced, iterator last_replaced,
+ BaseIterator<Impl> first, BaseIterator<Impl> last)
+ {
+ Sequence source(first.impl_, last.impl_);
+ try {
+ do_update(first_replaced.impl_, last_replaced.impl_,
+ source.begin(), source.end());
+ } catch (...) {
+ delete_range(source.begin(), source.end());
+ throw;
+ }
+ }
+
+ void start(Point p) {
+ clear();
+ (*final_)[0] = (*final_)[1] = p;
+ }
+
+ Point initialPoint() const { return (*final_)[1]; }
+ Point finalPoint() const { return (*final_)[0]; }
+
+ void append(Curve const &curve);
+
+ void append(D2<SBasis> const &curve);
+
+ template <typename CurveType, typename A>
+ void appendNew(A a) {
+ do_append(new CurveType((*final_)[0], a));
+ }
+
+ template <typename CurveType, typename A, typename B>
+ void appendNew(A a, B b) {
+ do_append(new CurveType((*final_)[0], a, b));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C>
+ void appendNew(A a, B b, C c) {
+ do_append(new CurveType((*final_)[0], a, b, c));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D>
+ void appendNew(A a, B b, C c, D d) {
+ do_append(new CurveType((*final_)[0], a, b, c, d));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D, typename E>
+ void appendNew(A a, B b, C c, D d, E e) {
+ do_append(new CurveType((*final_)[0], a, b, c, d, e));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D, typename E, typename F>
+ void appendNew(A a, B b, C c, D d, E e, F f) {
+ do_append(new CurveType((*final_)[0], a, b, c, d, e, f));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D, typename E, typename F,
+ typename G>
+ void appendNew(A a, B b, C c, D d, E e, F f, G g) {
+ do_append(new CurveType((*final_)[0], a, b, c, d, e, f, g));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D, typename E, typename F,
+ typename G, typename H>
+ void appendNew(A a, B b, C c, D d, E e, F f, G g, H h) {
+ do_append(new CurveType((*final_)[0], a, b, c, d, e, f, g, h));
+ }
+
+ template <typename CurveType, typename A, typename B, typename C,
+ typename D, typename E, typename F,
+ typename G, typename H, typename I>
+ void appendNew(A a, B b, C c, D d, E e, F f, G g, H h, I i) {
+ do_append(new CurveType((*final_)[0], a, b, c, d, e, f, g, h, i));
+ }
+
+private:
+ void do_update(Sequence::iterator first_replaced,
+ Sequence::iterator last_replaced,
+ Sequence::iterator first,
+ Sequence::iterator last);
+
+ void do_append(Curve *curve);
+
+ void delete_range(Sequence::iterator first, Sequence::iterator last);
+
+ void check_continuity(Sequence::iterator first_replaced,
+ Sequence::iterator last_replaced,
+ Sequence::iterator first,
+ Sequence::iterator last);
+
+ Sequence curves_;
+ LineSegment *final_;
+ bool closed_;
+};
+
+inline static Piecewise<D2<SBasis> > paths_to_pw(std::vector<Path> paths) {
+ Piecewise<D2<SBasis> > ret = paths[0].toPwSb();
+ for(unsigned i = 1; i < paths.size(); i++) {
+ ret.concat(paths[i].toPwSb());
+ }
+ return ret;
+}
+
+template <unsigned bezier_degree>
+inline Path const &Bezier<bezier_degree>::subdivide(Coord t, Path &out) const {
+ Bezier a, b;
+ subdivideArr(t, bezier_degree, c_, a.c_, b.c_);
+ out.clear();
+ out.close(false);
+ out.append(a);
+ out.append(b);
+ return out;
+}
+
+inline Path const &SVGEllipticalArc::subdivide(Coord t, Path &out) const {
+ SVGEllipticalArc a;
+ SVGEllipticalArc b;
+ a.rx_ = b.rx_ = rx_;
+ a.ry_ = b.ry_ = ry_;
+ a.x_axis_rotation_ = b.x_axis_rotation_ = x_axis_rotation_;
+ a.initial_ = initial_;
+ a.final_ = b.initial_ = pointAt(t);
+ b.final_ = final_;
+ out.clear();
+ out.close(false);
+ out.append(a);
+ out.append(b);
+ return out;
+}
+
+class Set {
+public:
+private:
+};
+
+}
+
+namespace std {
+
+template <>
+inline void swap<Geom::Path>(Geom::Path &a, Geom::Path &b)
+{
+ a.swap(b);
+}
+
+}
+
+#endif // SEEN_GEOM_PATH_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=2:tabstop=8:softtabstop=2 :
diff --git a/src/2geom/piecewise.cpp b/src/2geom/piecewise.cpp
new file mode 100644
index 000000000..dc91ab4a9
--- /dev/null
+++ b/src/2geom/piecewise.cpp
@@ -0,0 +1,180 @@
+/*
+ * piecewise.cpp - Piecewise function class
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ * Copyright 2007 JF Barraud
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include "piecewise.h"
+#include <iterator>
+#include <map>
+
+namespace Geom {
+
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k) {
+ Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<SBasis> ret = Piecewise<SBasis>();
+ assert(pa.size() == pb.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(divide(pa[i], pb[i], k));
+ return ret;
+}
+
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero) {
+ Piecewise<SBasis> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<SBasis> ret = Piecewise<SBasis>();
+ assert(pa.size() == pb.size());
+ for (unsigned i = 0; i < pa.size(); i++){
+ Piecewise<SBasis> divi = divide(pa[i], pb[i], tol, k, zero);
+ divi.setDomain(Interval(pa.cuts[i],pa.cuts[i+1]));
+ ret.concat(divi);
+ }
+ return ret;
+}
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero){
+ return divide(a,Piecewise<SBasis>(b),tol,k,zero);
+}
+Piecewise<SBasis> divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero){
+ return divide(Piecewise<SBasis>(a),b,tol,k,zero);
+}
+Piecewise<SBasis> divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero) {
+ if (b.tailError(0)<2*zero){
+ //TODO: have a better look at sgn(b).
+ double sgn= (b(.5)<0.)?-1.:1;
+ return Piecewise<SBasis>(Linear(sgn/zero)*a);
+ }
+
+ if (fabs(b.at0())>zero && fabs(b.at1())>zero ){
+ SBasis c,r=a;
+ //TODO: what is a good relative tol? atm, c=a/b +/- (tol/a)%...
+
+ k+=1;
+ r.resize(k, Linear(0,0));
+ c.resize(k, Linear(0,0));
+
+ //assert(b.at0()!=0 && b.at1()!=0);
+ for (unsigned i=0; i<k; i++){
+ Linear ci = Linear(r[i][0]/b[0][0],r[i][1]/b[0][1]);
+ c[i]=ci;
+ r-=shift(ci*b,i);
+ }
+
+ if (r.tailError(k)<tol) return Piecewise<SBasis>(c);
+ }
+
+ Piecewise<SBasis> c0,c1;
+ c0 = divide(compose(a,Linear(0.,.5)),compose(b,Linear(0.,.5)),tol,k);
+ c1 = divide(compose(a,Linear(.5,1.)),compose(b,Linear(.5,1.)),tol,k);
+ c0.setDomain(Interval(0.,.5));
+ c1.setDomain(Interval(.5,1.));
+ c0.concat(c1);
+ return c0;
+}
+
+
+//-- compose(pw<T>,SBasis) ---------------
+/*
+ the purpose of the following functions is only to reduce the code in piecewise.h
+ TODO: use a vector<pairs<double,unsigned> > instead of a map<double,unsigned>.
+ */
+
+std::map<double,unsigned> compose_pullback(std::vector<double> const &values, SBasis const &g){
+ std::map<double,unsigned> result;
+
+ std::vector<std::vector<double> > roots = multi_roots(g, values);
+ for(unsigned i=0; i<roots.size(); i++){
+ for(unsigned j=0; j<roots[i].size();j++){
+ result[roots[i][j]]=i;
+ }
+ }
+ // Also map 0 and 1 to the first value above(or =) g(0) and g(1).
+ if(result.count(0.)==0){
+ unsigned i=0;
+ while (i<values.size()&&(g.at0()>values[i])) i++;
+ result[0.]=i;
+ }
+ if(result.count(1.)==0){
+ unsigned i=0;
+ while (i<values.size()&&(g.at1()>values[i])) i++;
+ result[1.]=i;
+ }
+ return(result);
+}
+
+int compose_findSegIdx(std::map<double,unsigned>::iterator const &cut,
+ std::map<double,unsigned>::iterator const &next,
+ std::vector<double> const &levels,
+ SBasis const &g){
+ double t0=(*cut).first;
+ unsigned idx0=(*cut).second;
+ double t1=(*next).first;
+ unsigned idx1=(*next).second;
+ assert(t0<t1);
+ int idx; //idx of the relevant f.segs
+ if (std::max(idx0,idx1)==levels.size()){ //g([t0,t1]) is above the top level,
+ idx=levels.size()-1;
+ } else if (idx0 != idx1){ //g([t0,t1]) crosses from level idx0 to idx1,
+ idx=std::min(idx0,idx1);
+ } else if(g((t0+t1)/2) < levels[idx0]) { //g([t0,t1]) is a 'U' under level idx0,
+ idx=idx0-1;
+ } else if(g((t0+t1)/2) > levels[idx0]) { //g([t0,t1]) is a 'bump' over level idx0,
+ idx=idx0;
+ } else { //g([t0,t1]) is contained in level idx0!...
+ idx = (idx0==levels.size())? idx0-1:idx0;
+ }
+
+ //move idx back from levels f.cuts
+ idx+=1;
+ return idx;
+}
+
+std::vector<double> roots(Piecewise<SBasis> const &f){
+ std::vector<double> result;
+ for (unsigned i=0; i<f.size(); i++){
+ std::vector<double> rts=roots(f.segs[i]);
+ rts=roots(f.segs[i]);
+
+ for (unsigned r=0; r<rts.size(); r++){
+ result.push_back(f.mapToDomain(rts[r], i));
+ }
+ }
+ return result;
+}
+
+}
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/piecewise.h b/src/2geom/piecewise.h
new file mode 100644
index 000000000..8823c21dd
--- /dev/null
+++ b/src/2geom/piecewise.h
@@ -0,0 +1,690 @@
+/*
+ * piecewise.h - Piecewise function class
+ *
+ * Copyright 2007 Michael Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_GEOM_PW_SB_H
+#define SEEN_GEOM_PW_SB_H
+
+#include "sbasis.h"
+#include <vector>
+#include <map>
+
+#include "concepts.h"
+#include "isnan.h"
+#include <boost/concept_check.hpp>
+
+namespace Geom {
+
+template <typename T>
+class Piecewise {
+ BOOST_CLASS_REQUIRE(T, Geom, FragmentConcept);
+
+ public:
+ std::vector<double> cuts;
+ std::vector<T> segs;
+ //segs[i] stretches from cuts[i] to cuts[i+1].
+
+ Piecewise() {}
+
+ explicit Piecewise(const T &s) {
+ push_cut(0.);
+ push_seg(s);
+ push_cut(1.);
+ }
+
+ typedef typename T::output_type output_type;
+
+ explicit Piecewise(const output_type & v) {
+ push_cut(0.);
+ push_seg(T(v));
+ push_cut(1.);
+ }
+
+ inline T operator[](unsigned i) const { return segs[i]; }
+ inline T &operator[](unsigned i) { return segs[i]; }
+ inline output_type operator()(double t) const { return valueAt(t); }
+ inline output_type valueAt(double t) const {
+ unsigned n = segN(t);
+ return segs[n](segT(t, n));
+ }
+ //TODO: maybe it is not a good idea to have this?
+ Piecewise<T> operator()(SBasis f);
+ Piecewise<T> operator()(Piecewise<SBasis>f);
+
+ inline unsigned size() const { return segs.size(); }
+ inline bool empty() const { return segs.empty(); }
+
+ /**Convenience/implementation hiding function to add segment/cut pairs.
+ * Asserts that basic size and order invariants are correct
+ */
+ inline void push(const T &s, double to) {
+ assert(cuts.size() - segs.size() == 1);
+ push_seg(s);
+ push_cut(to);
+ }
+ //Convenience/implementation hiding function to add cuts.
+ inline void push_cut(double c) {
+ assert(cuts.empty() || c > cuts.back());
+ cuts.push_back(c);
+ }
+ //Convenience/implementation hiding function to add segments.
+ inline void push_seg(const T &s) { segs.push_back(s); }
+
+ /**Returns the segment index which corresponds to a 'global' piecewise time.
+ * Also takes optional low/high parameters to expedite the search for the segment.
+ */
+ inline unsigned segN(double t, int low = 0, int high = -1) const {
+ high = (high == -1) ? size() : high;
+ if(t < cuts[0]) return 0;
+ if(t >= cuts[size()]) return size() - 1;
+ while(low < high) {
+ int mid = (high + low) / 2; //Lets not plan on having huge (> INT_MAX / 2) cut sequences
+ double mv = cuts[mid];
+ if(mv < t) {
+ if(t < cuts[mid + 1]) return mid; else low = mid + 1;
+ } else if(t < mv) {
+ if(cuts[mid - 1] < t) return mid - 1; else high = mid - 1;
+ } else {
+ return mid;
+ }
+ }
+ return low;
+ }
+
+ /**Returns the time within a segment, given the 'global' piecewise time.
+ * Also takes an optional index parameter which may be used for efficiency or to find the time on a
+ * segment outside its range. If it is left to its default, -1, it will call segN to find the index.
+ */
+ inline double segT(double t, int i = -1) const {
+ if(i == -1) i = segN(t);
+ assert(i >= 0);
+ return (t - cuts[i]) / (cuts[i+1] - cuts[i]);
+ }
+
+ inline double mapToDomain(double t, unsigned i) const {
+ return (1-t)*cuts[i] + t*cuts[i+1]; //same as: t * (cuts[i+1] - cuts[i]) + cuts[i]
+ }
+
+ //Offsets the piecewise domain
+ inline void offsetDomain(double o) {
+ assert(is_finite(o));
+ if(o != 0)
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] += o;
+ }
+
+ //Scales the domain of the function by a value. 0 will result in an empty Piecewise.
+ inline void scaleDomain(double s) {
+ assert(s > 0);
+ if(s == 0) {
+ cuts.clear(); segs.clear();
+ return;
+ }
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] *= s;
+ }
+
+ //Retrieves the domain in interval form
+ inline Interval domain() const { return Interval(cuts.front(), cuts.back()); }
+
+ //Transforms the domain into another interval
+ inline void setDomain(Interval dom) {
+ if(empty()) return;
+ if(dom.isEmpty()) {
+ cuts.clear(); segs.clear();
+ return;
+ }
+ double cf = cuts.front();
+ double o = dom.min() - cf, s = dom.extent() / (cuts.back() - cf);
+ for(unsigned i = 0; i <= size(); i++)
+ cuts[i] = (cuts[i] - cf) * s + o;
+ }
+
+ //Concatenates this Piecewise function with another, offseting time of the other to match the end.
+ inline void concat(const Piecewise<T> &other) {
+ if(other.empty()) return;
+
+ if(empty()) {
+ cuts = other.cuts; segs = other.segs;
+ return;
+ }
+
+ segs.insert(segs.end(), other.segs.begin(), other.segs.end());
+ double t = cuts.back() - other.cuts.front();
+ for(unsigned i = 0; i < other.size(); i++)
+ push_cut(other.cuts[i + 1] + t);
+ }
+
+ //Like concat, but ensures continuity.
+ inline void continuousConcat(const Piecewise<T> &other) {
+ boost::function_requires<AddableConcept<typename T::output_type> >();
+ if(other.empty()) return;
+ typename T::output_type y = segs.back().at1() - other.segs.front().at0();
+
+ if(empty()) {
+ for(unsigned i = 0; i < other.size(); i++)
+ push_seg(other[i] + y);
+ cuts = other.cuts;
+ return;
+ }
+
+ double t = cuts.back() - other.cuts.front();
+ for(unsigned i = 0; i < other.size(); i++)
+ push(other[i] + y, other.cuts[i + 1] + t);
+ }
+
+ //returns true if the Piecewise<T> meets some basic invariants.
+ inline bool invariants() const {
+ // segs between cuts
+ if(!(segs.size() + 1 == cuts.size() || (segs.empty() && cuts.empty())))
+ return false;
+ // cuts in order
+ for(unsigned i = 0; i < segs.size(); i++)
+ if(cuts[i] >= cuts[i+1])
+ return false;
+ return true;
+ }
+
+};
+
+template<typename T>
+inline Interval bounds_fast(const Piecewise<T> &f) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty()) return Interval(0);
+ Interval ret(bounds_fast(f[0]));
+ for(unsigned i = 1; i < f.size(); i++)
+ ret.unionWith(bounds_fast(f[i]));
+ return ret;
+}
+
+template<typename T>
+inline Interval bounds_exact(const Piecewise<T> &f) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty()) return Interval(0);
+ Interval ret(bounds_exact(f[0]));
+ for(unsigned i = 1; i < f.size(); i++)
+ ret.unionWith(bounds_exact(f[i]));
+ return ret;
+}
+
+template<typename T>
+inline Interval bounds_local(const Piecewise<T> &f, const Interval &m) {
+ boost::function_requires<FragmentConcept<T> >();
+
+ if(f.empty()) return Interval(0);
+ if(m.isEmpty()) return Interval(f(m.min()));
+
+ unsigned fi = f.segN(m.min()), ti = f.segN(m.max());
+ double ft = f.segT(m.min(), fi), tt = f.segT(m.max(), ti);
+
+ if(fi == ti) return bounds_local(f[fi], Interval(ft, tt));
+
+ Interval ret(bounds_local(f[fi], Interval(ft, 1.)));
+ for(unsigned i = fi + 1; i < ti; i++)
+ ret.unionWith(bounds_exact(f[i]));
+ if(tt != 0.) ret.unionWith(bounds_local(f[ti], Interval(0., tt)));
+
+ return ret;
+}
+
+//returns a portion of a piece of a Piecewise<T>, given the piece's index and a to/from time.
+template<typename T>
+T elem_portion(const Piecewise<T> &a, unsigned i, double from, double to) {
+ assert(i < a.size());
+ double rwidth = 1 / (a.cuts[i+1] - a.cuts[i]);
+ return portion( a[i], (from - a.cuts[i]) * rwidth, (to - a.cuts[i]) * rwidth );
+}
+
+/**Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c);
+ * Further subdivides the Piecewise<T> such that there is a cut at every value in c.
+ * Precondition: c sorted lower to higher.
+ *
+ * //Given Piecewise<T> a and b:
+ * Piecewise<T> ac = a.partition(b.cuts);
+ * Piecewise<T> bc = b.partition(a.cuts);
+ * //ac.cuts should be equivalent to bc.cuts
+ */
+template<typename T>
+Piecewise<T> partition(const Piecewise<T> &pw, std::vector<double> const &c) {
+ assert(pw.invariants());
+ if(c.empty()) return Piecewise<T>(pw);
+
+ Piecewise<T> ret = Piecewise<T>();
+ ret.cuts.reserve(c.size() + pw.cuts.size());
+ ret.segs.reserve(c.size() + pw.cuts.size() - 1);
+
+ if(pw.empty()) {
+ ret.cuts = c;
+ for(unsigned i = 0; i < c.size() - 1; i++)
+ ret.push_seg(T());
+ return ret;
+ }
+
+ unsigned si = 0, ci = 0; //Segment index, Cut index
+
+ //if the cuts have something earlier than the Piecewise<T>, add portions of the first segment
+ while(c[ci] < pw.cuts.front() && ci < c.size()) {
+ bool isLast = (ci == c.size()-1 || c[ci + 1] >= pw.cuts.front());
+ ret.push_cut(c[ci]);
+ ret.push_seg( elem_portion(pw, 0, c[ci], isLast ? pw.cuts.front() : c[ci + 1]) );
+ ci++;
+ }
+
+ ret.push_cut(pw.cuts[0]);
+ double prev = pw.cuts[0]; //previous cut
+ //Loop which handles cuts within the Piecewise<T> domain
+ //Should have the cuts = segs + 1 invariant
+ while(si < pw.size() && ci <= c.size()) {
+ if(ci == c.size() && prev <= pw.cuts[si]) { //cuts exhausted, straight copy the rest
+ ret.segs.insert(ret.segs.end(), pw.segs.begin() + si, pw.segs.end());
+ ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + si + 1, pw.cuts.end());
+ return ret;
+ }else if(ci == c.size() || c[ci] >= pw.cuts[si + 1]) { //no more cuts within this segment, finalize
+ if(prev > pw.cuts[si]) { //segment already has cuts, so portion is required
+ ret.push_seg(portion(pw[si], pw.segT(prev, si), 1.0));
+ } else { //plain copy is fine
+ ret.push_seg(pw[si]);
+ }
+ ret.push_cut(pw.cuts[si + 1]);
+ prev = pw.cuts[si + 1];
+ si++;
+ } else if(c[ci] == pw.cuts[si]){ //coincident
+ //Already finalized the seg with the code immediately above
+ ci++;
+ } else { //plain old subdivision
+ ret.push(elem_portion(pw, si, prev, c[ci]), c[ci]);
+ prev = c[ci];
+ ci++;
+ }
+ }
+
+ //input cuts extend further than this Piecewise<T>, extend the last segment.
+ while(ci < c.size()) {
+ if(c[ci] > prev) {
+ ret.push(elem_portion(pw, pw.size() - 1, prev, c[ci]), c[ci]);
+ prev = c[ci];
+ }
+ ci++;
+ }
+ return ret;
+}
+
+/**Piecewise<T> portion(const Piecewise<T> &pw, double from, double to);
+ * Returns a Piecewise<T> with a defined domain of [min(from, to), max(from, to)].
+ */
+template<typename T>
+Piecewise<T> portion(const Piecewise<T> &pw, double from, double to) {
+ if(pw.empty() || from == to) return Piecewise<T>();
+
+ Piecewise<T> ret;
+
+ double temp = from;
+ from = std::min(from, to);
+ to = std::max(temp, to);
+
+ unsigned i = pw.segN(from);
+ ret.push_cut(from);
+ if(i == pw.size() - 1 || to < pw.cuts[i + 1]) { //to/from inhabit the same segment
+ ret.push(elem_portion(pw, i, from, to), to);
+ return ret;
+ }
+ ret.push_seg(portion( pw[i], pw.segT(from, i), 1.0 ));
+ i++;
+ unsigned fi = pw.segN(to, i);
+
+ ret.segs.insert(ret.segs.end(), pw.segs.begin() + i, pw.segs.begin() + fi); //copy segs
+ ret.cuts.insert(ret.cuts.end(), pw.cuts.begin() + i, pw.cuts.begin() + fi + 1); //and their cuts
+
+ ret.push_seg( portion(pw[fi], 0.0, pw.segT(to, fi)));
+ if(to != ret.cuts.back()) ret.push_cut(to);
+ ret.invariants();
+ return ret;
+}
+
+template<typename T>
+Piecewise<T> remove_short_cuts(Piecewise<T> const &f, double tol) {
+ if(f.empty()) return f;
+ Piecewise<T> ret;
+ ret.push_cut(f.cuts[0]);
+ for(unsigned i=0; i<f.size(); i++){
+ if (f.cuts[i+1]-f.cuts[i] >= tol || i==f.size()-1) {
+ ret.push(f[i], f.cuts[i+1]);
+ }
+ }
+ return ret;
+}
+
+template<typename T>
+Piecewise<T> remove_short_cuts_extending(Piecewise<T> const &f, double tol) {
+ if(f.empty()) return f;
+ Piecewise<T> ret;
+ ret.push_cut(f.cuts[0]);
+ double last = f.cuts[0]; // last cut included
+ for(unsigned i=0; i<f.size(); i++){
+ if (f.cuts[i+1]-f.cuts[i] >= tol) {
+ ret.push(elem_portion(f, i, last, f.cuts[i+1]), f.cuts[i+1]);
+ last = f.cuts[i+1];
+ }
+ }
+ return ret;
+}
+
+template<typename T>
+std::vector<double> roots(const Piecewise<T> &pw) {
+ std::vector<double> ret;
+ for(unsigned i = 0; i < pw.size(); i++) {
+ std::vector<double> sr = roots(pw[i]);
+ for (unsigned j = 0; j < sr.size(); j++) ret.push_back(sr[j] * (pw.cuts[i + 1] - pw.cuts[i]) + pw.cuts[i]);
+
+ }
+ return ret;
+}
+
+//IMPL: OffsetableConcept
+template<typename T>
+Piecewise<T> operator+(Piecewise<T> const &a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+//TODO:empty
+ Piecewise<T> ret = Piecewise<T>();
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] + b);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+//TODO: empty
+ Piecewise<T> ret = Piecewise<T>();
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] - b);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator+=(Piecewise<T>& a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+
+ if(a.empty()) { a.push_cut(0.); a.push(T(b), 1.); return a; }
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] += b;
+ return a;
+}
+template<typename T>
+Piecewise<T> operator-=(Piecewise<T>& a, typename T::output_type b) {
+ boost::function_requires<OffsetableConcept<T> >();
+
+ if(a.empty()) { a.push_cut(0.); a.push(T(b), 1.); return a; }
+
+ for(unsigned i = 0;i < a.size();i++)
+ a[i] -= b;
+ return a;
+}
+
+//IMPL: ScalableConcept
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ Piecewise<T> ret;
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(- a[i]);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator*(Piecewise<T> const &a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ if(a.empty()) return Piecewise<T>();
+
+ Piecewise<T> ret;
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] * b);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator/(Piecewise<T> const &a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ //FIXME: b == 0?
+ if(a.empty()) return Piecewise<T>();
+
+ Piecewise<T> ret;
+ ret.cuts = a.cuts;
+ for(unsigned i = 0; i < a.size();i++)
+ ret.push_seg(a[i] / b);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator*=(Piecewise<T>& a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ if(a.empty()) return Piecewise<T>();
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] *= b;
+ return a;
+}
+template<typename T>
+Piecewise<T> operator/=(Piecewise<T>& a, double b) {
+ boost::function_requires<ScalableConcept<T> >();
+
+ //FIXME: b == 0?
+ if(a.empty()) return Piecewise<T>();
+
+ for(unsigned i = 0; i < a.size();i++)
+ a[i] /= b;
+ return a;
+}
+
+//IMPL: AddableConcept
+template<typename T>
+Piecewise<T> operator+(Piecewise<T> const &a, Piecewise<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<T> ret = Piecewise<T>();
+ assert(pa.size() == pb.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] + pb[i]);
+ return ret;
+}
+template<typename T>
+Piecewise<T> operator-(Piecewise<T> const &a, Piecewise<T> const &b) {
+ boost::function_requires<AddableConcept<T> >();
+
+ Piecewise<T> pa = partition(a, b.cuts), pb = partition(b, a.cuts);
+ Piecewise<T> ret = Piecewise<T>();
+ assert(pa.size() == pb.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] - pb[i]);
+ return ret;
+}
+template<typename T>
+inline Piecewise<T> operator+=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a+b;
+ return a;
+}
+template<typename T>
+inline Piecewise<T> operator-=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a-b;
+ return a;
+}
+
+template<typename T1,typename T2>
+Piecewise<T2> operator*(Piecewise<T1> const &a, Piecewise<T2> const &b) {
+ //function_requires<MultiplicableConcept<T1> >();
+ //function_requires<MultiplicableConcept<T2> >();
+
+ Piecewise<T1> pa = partition(a, b.cuts);
+ Piecewise<T2> pb = partition(b, a.cuts);
+ Piecewise<T2> ret = Piecewise<T2>();
+ assert(pa.size() == pb.size());
+ ret.cuts = pa.cuts;
+ for (unsigned i = 0; i < pa.size(); i++)
+ ret.push_seg(pa[i] * pb[i]);
+ return ret;
+}
+
+template<typename T>
+inline Piecewise<T> operator*=(Piecewise<T> &a, Piecewise<T> const &b) {
+ a = a * b;
+ return a;
+}
+
+Piecewise<SBasis> divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, unsigned k);
+//TODO: replace divide(a,b,k) by divide(a,b,tol,k)?
+//TODO: atm, relative error is ~(tol/a)%. Find a way to make it independant of a.
+//Nota: the result is 'truncated' where b is smaller than 'zero': ~ a/max(b,zero).
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(SBasis const &a, Piecewise<SBasis> const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(Piecewise<SBasis> const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3);
+Piecewise<SBasis>
+divide(SBasis const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3);
+
+//Composition: functions called compose_* are pieces of compose that are factored out in pw.cpp.
+std::map<double,unsigned> compose_pullback(std::vector<double> const &cuts, SBasis const &g);
+int compose_findSegIdx(std::map<double,unsigned>::iterator const &cut,
+ std::map<double,unsigned>::iterator const &next,
+ std::vector<double> const &levels,
+ SBasis const &g);
+
+//TODO: add concept check
+template<typename T>
+Piecewise<T> compose(Piecewise<T> const &f, SBasis const &g){
+ Piecewise<T> result;
+ if (f.empty()) return result;
+ if (g.isZero()) return Piecewise<T>(f(0));
+ if (f.size()==1){
+ double t0 = f.cuts[0], width = f.cuts[1] - t0;
+ return (Piecewise<T>) compose(f.segs[0],compose(Linear(-t0 / width, (1-t0) / width), g));
+ }
+
+ //first check bounds...
+ Interval bs = bounds_fast(g);
+ if (f.cuts.front() > bs.max() || bs.min() > f.cuts.back()){
+ int idx = (bs.max() < f.cuts[1]) ? 0 : f.cuts.size()-2;
+ double t0 = f.cuts[idx], width = f.cuts[idx+1] - t0;
+ return (Piecewise<T>) compose(f.segs[idx],compose(Linear(-t0 / width, (1-t0) / width), g));
+ }
+
+ std::vector<double> levels;//we can forget first and last cuts...
+ levels.insert(levels.begin(),f.cuts.begin()+1,f.cuts.end()-1);
+ //TODO: use a std::vector<pairs<double,unsigned> > instead of a map<double,unsigned>.
+ std::map<double,unsigned> cuts_pb = compose_pullback(levels,g);
+
+ //-- Compose each piece of g with the relevant seg of f.
+ result.cuts.push_back(0.);
+ std::map<double,unsigned>::iterator cut=cuts_pb.begin();
+ std::map<double,unsigned>::iterator next=cut; next++;
+ while(next!=cuts_pb.end()){
+ //assert(std::abs(int((*cut).second-(*next).second))<1);
+ //TODO: find a way to recover from this error? the root finder missed some root;
+ // the levels/variations of f might be too close/fast...
+ int idx = compose_findSegIdx(cut,next,levels,g);
+ double t0=(*cut).first;
+ double t1=(*next).first;
+
+ SBasis sub_g=compose(g, Linear(t0,t1));
+ sub_g=compose(Linear(-f.cuts[idx]/(f.cuts[idx+1]-f.cuts[idx]),
+ (1-f.cuts[idx])/(f.cuts[idx+1]-f.cuts[idx])),sub_g);
+ result.push(compose(f[idx],sub_g),t1);
+ cut++;
+ next++;
+ }
+ return(result);
+}
+
+//TODO: add concept check for following composition functions
+template<typename T>
+Piecewise<T> compose(Piecewise<T> const &f, Piecewise<SBasis> const &g){
+ Piecewise<T> result;
+ for(unsigned i = 0; i < g.segs.size(); i++){
+ Piecewise<T> fgi=compose(f, g.segs[i]);
+ fgi.setDomain(Interval(g.cuts[i], g.cuts[i+1]));
+ result.concat(fgi);
+ }
+ return result;
+}
+
+template <typename T>
+Piecewise<T> Piecewise<T>::operator()(SBasis f){return compose((*this),f);}
+template <typename T>
+Piecewise<T> Piecewise<T>::operator()(Piecewise<SBasis>f){return compose((*this),f);}
+
+template<typename T>
+Piecewise<T> integral(Piecewise<T> const &a) {
+ Piecewise<T> result;
+ result.segs.resize(a.segs.size());
+ result.cuts = a.cuts;
+ typename T::output_type c = a.segs[0].at0();
+ for(unsigned i = 0; i < a.segs.size(); i++){
+ result.segs[i] = integral(a.segs[i])*(a.cuts[i+1]-a.cuts[i]);
+ result.segs[i]+= c-result.segs[i].at0();
+ c = result.segs[i].at1();
+ }
+ return result;
+}
+
+template<typename T>
+Piecewise<T> derivative(Piecewise<T> const &a) {
+ Piecewise<T> result;
+ result.segs.resize(a.segs.size());
+ result.cuts = a.cuts;
+ for(unsigned i = 0; i < a.segs.size(); i++){
+ result.segs[i] = derivative(a.segs[i])/(a.cuts[i+1]-a.cuts[i]);
+ }
+ return result;
+}
+
+std::vector<double> roots(Piecewise<SBasis> const &f);
+
+}
+
+#endif //SEEN_GEOM_PW_SB_H
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/point-l.h b/src/2geom/point-l.h
new file mode 100644
index 000000000..cd4768db0
--- /dev/null
+++ b/src/2geom/point-l.h
@@ -0,0 +1,86 @@
+#ifndef SEEN_Geom_POINT_L_H
+#define SEEN_Geom_POINT_L_H
+
+#include <stdexcept>
+#include "point.h"
+
+namespace Geom {
+
+typedef long ICoord;
+
+class IPoint {
+ ICoord _pt[2];
+
+ public:
+ IPoint() { }
+
+ IPoint(ICoord x, ICoord y) {
+ _pt[X] = x;
+ _pt[Y] = y;
+ }
+
+ IPoint(NRPointL const &p) {
+ _pt[X] = p.x;
+ _pt[Y] = p.y;
+ }
+
+ IPoint(IPoint const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ }
+
+ IPoint &operator=(IPoint const &p) {
+ for (unsigned i = 0; i < 2; ++i) {
+ _pt[i] = p._pt[i];
+ }
+ return *this;
+ }
+
+ operator Point() {
+ return Point(_pt[X], _pt[Y]);
+ }
+
+ ICoord operator[](unsigned i) const throw(std::out_of_range) {
+ if ( i > Y ) throw std::out_of_range("index out of range");
+ return _pt[i];
+ }
+
+ ICoord &operator[](unsigned i) throw(std::out_of_range) {
+ if ( i > Y ) throw std::out_of_range("index out of range");
+ return _pt[i];
+ }
+
+ ICoord operator[](Dim2 d) const throw() { return _pt[d]; }
+ ICoord &operator[](Dim2 d) throw() { return _pt[d]; }
+
+ IPoint &operator+=(IPoint const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] += o._pt[i];
+ }
+ return *this;
+ }
+
+ IPoint &operator-=(IPoint const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] -= o._pt[i];
+ }
+ return *this;
+ }
+};
+
+
+} // namespace Geom
+
+#endif /* !SEEN_Geom_POINT_L_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/point-ops.h b/src/2geom/point-ops.h
new file mode 100644
index 000000000..6f5eab56b
--- /dev/null
+++ b/src/2geom/point-ops.h
@@ -0,0 +1,25 @@
+//[[[cog
+import operators
+
+setContext("Point", "Matrix", "Point")
+make({'*':'*='}, {'/':'/='})
+apsnd({'*':'/'}, "b.inverse()")
+
+setContext("Point", "double", "Point")
+make({'*=':'*'}, {'/=':'/'}, {'*':'*'}, {'*':'/'})
+
+setContext("Point", "Point", "bool")
+make({'==':'!='})
+
+setContext("Point", "Point", "Point")
+make({'+=':'+', '-=':'-'})
+]]]
+
+**************
+GENERATED CODE
+**************
+If you wish to modify, move function out of generation region and remove the
+cause of its generation.
+*/
+
+//[[[end]]]
diff --git a/src/2geom/point.cpp b/src/2geom/point.cpp
new file mode 100644
index 000000000..1e2a3463f
--- /dev/null
+++ b/src/2geom/point.cpp
@@ -0,0 +1,164 @@
+#include "point.h"
+#include <assert.h>
+#include "coord.h"
+#include "isnan.h" //temporary fix for isnan()
+#include "matrix.h"
+
+namespace Geom {
+
+/** Scales this vector to make it a unit vector (within rounding error).
+ *
+ * The current version tries to handle infinite coordinates gracefully,
+ * but it's not clear that any callers need that.
+ *
+ * \pre \f$this \neq (0, 0)\f$
+ * \pre Neither component is NaN.
+ * \post \f$-\epsilon<\left|this\right|-1<\epsilon\f$
+ */
+void Point::normalize() {
+ double len = hypot(_pt[0], _pt[1]);
+ if(len == 0) return;
+ if(is_nan(len)) return;
+ static double const inf = 1e400;
+ if(len != inf) {
+ *this /= len;
+ } else {
+ unsigned n_inf_coords = 0;
+ /* Delay updating pt in case neither coord is infinite. */
+ Point tmp;
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ if ( _pt[i] == inf ) {
+ ++n_inf_coords;
+ tmp[i] = 1.0;
+ } else if ( _pt[i] == -inf ) {
+ ++n_inf_coords;
+ tmp[i] = -1.0;
+ } else {
+ tmp[i] = 0.0;
+ }
+ }
+ switch (n_inf_coords) {
+ case 0: {
+ /* Can happen if both coords are near +/-DBL_MAX. */
+ *this /= 4.0;
+ len = hypot(_pt[0], _pt[1]);
+ assert(len != inf);
+ *this /= len;
+ break;
+ }
+ case 1: {
+ *this = tmp;
+ break;
+ }
+ case 2: {
+ *this = tmp * sqrt(0.5);
+ break;
+ }
+ }
+ }
+}
+
+/** Compute the L1 norm, or manhattan distance, of \a p. */
+Coord L1(Point const &p) {
+ Coord d = 0;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ d += fabs(p[i]);
+ }
+ return d;
+}
+
+/** Compute the L infinity, or maximum, norm of \a p. */
+Coord LInfty(Point const &p) {
+ Coord const a(fabs(p[0]));
+ Coord const b(fabs(p[1]));
+ return ( a < b || is_nan(b)
+ ? b
+ : a );
+}
+
+/** Returns true iff p is a zero vector, i.e.\ Point(0, 0).
+ *
+ * (NaN is considered non-zero.)
+ */
+bool
+is_zero(Point const &p)
+{
+ return ( p[0] == 0 &&
+ p[1] == 0 );
+}
+
+bool
+is_unit_vector(Point const &p)
+{
+ return fabs(1.0 - L2(p)) <= 1e-4;
+ /* The tolerance of 1e-4 is somewhat arbitrary. Point::normalize is believed to return
+ points well within this tolerance. I'm not aware of any callers that want a small
+ tolerance; most callers would be ok with a tolerance of 0.25. */
+}
+
+Coord atan2(Point const p) {
+ return std::atan2(p[Y], p[X]);
+}
+
+/** compute the angle turning from a to b. This should give \f$\pi/2\f$ for angle_between(a, rot90(a));
+ * This works by projecting b onto the basis defined by a, rot90(a)
+ */
+Coord angle_between(Point const a, Point const b) {
+ return std::atan2(cross(b,a), dot(b,a));
+}
+
+
+
+/** Returns a version of \a a scaled to be a unit vector (within rounding error).
+ *
+ * The current version tries to handle infinite coordinates gracefully,
+ * but it's not clear that any callers need that.
+ *
+ * \pre a != Point(0, 0).
+ * \pre Neither coordinate is NaN.
+ * \post L2(ret) very near 1.0.
+ */
+Point unit_vector(Point const &a)
+{
+ Point ret(a);
+ ret.normalize();
+ return ret;
+}
+
+Point abs(Point const &b)
+{
+ Point ret;
+ for ( int i = 0 ; i < 2 ; i++ ) {
+ ret[i] = fabs(b[i]);
+ }
+ return ret;
+}
+
+Point operator*(Point const &v, Matrix const &m) {
+ Point ret;
+ for(int i = 0; i < 2; i++) {
+ ret[i] = v[X] * m[i] + v[Y] * m[i + 2] + m[i + 4];
+ }
+ return ret;
+}
+
+Point operator/(Point const &p, Matrix const &m) { return p * m.inverse(); }
+
+Point &Point::operator*=(Matrix const &m)
+{
+ *this = *this * m;
+ return *this;
+}
+
+} //Namespace Geom
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/point.h b/src/2geom/point.h
new file mode 100644
index 000000000..030776915
--- /dev/null
+++ b/src/2geom/point.h
@@ -0,0 +1,229 @@
+#ifndef SEEN_Geom_POINT_H
+#define SEEN_Geom_POINT_H
+
+/** \file
+ * Cartesian point class.
+ */
+
+#include <iostream>
+
+#include "coord.h"
+#include "utils.h"
+
+namespace Geom {
+
+enum Dim2 { X=0, Y=1 };
+
+class Matrix;
+
+/// Cartesian point.
+class Point {
+ Coord _pt[2];
+
+ public:
+ inline Point()
+ { _pt[X] = _pt[Y] = 0; }
+
+ inline Point(Coord x, Coord y) {
+ _pt[X] = x; _pt[Y] = y;
+ }
+
+ inline Point(Point const &p) {
+ for (unsigned i = 0; i < 2; ++i)
+ _pt[i] = p._pt[i];
+ }
+
+ inline Point &operator=(Point const &p) {
+ for (unsigned i = 0; i < 2; ++i)
+ _pt[i] = p._pt[i];
+ return *this;
+ }
+
+ inline Coord operator[](unsigned i) const { return _pt[i]; }
+ inline Coord &operator[](unsigned i) { return _pt[i]; }
+
+ Coord operator[](Dim2 d) const throw() { return _pt[d]; }
+ Coord &operator[](Dim2 d) throw() { return _pt[d]; }
+
+ static inline Point polar(Coord angle, Coord radius) {
+ return Point(radius * std::cos(angle), radius * std::sin(angle));
+ }
+
+ inline Coord length() const { return hypot(_pt[0], _pt[1]); }
+
+ /** Return a point like this point but rotated -90 degrees.
+ (If the y axis grows downwards and the x axis grows to the
+ right, then this is 90 degrees counter-clockwise.)
+ **/
+ Point ccw() const {
+ return Point(_pt[Y], -_pt[X]);
+ }
+
+ /** Return a point like this point but rotated +90 degrees.
+ (If the y axis grows downwards and the x axis grows to the
+ right, then this is 90 degrees clockwise.)
+ **/
+ Point cw() const {
+ return Point(-_pt[Y], _pt[X]);
+ }
+
+ /**
+ \brief A function to lower the precision of the point
+ \param places The number of decimal places that should be in
+ the final number.
+ */
+ inline void round (int places = 0) {
+ _pt[X] = (Coord)(decimal_round((double)_pt[X], places));
+ _pt[Y] = (Coord)(decimal_round((double)_pt[Y], places));
+ return;
+ }
+
+ void normalize();
+
+ inline Point operator+(Point const &o) const {
+ return Point(_pt[X] + o._pt[X], _pt[Y] + o._pt[Y]);
+ }
+ inline Point operator-(Point const &o) const {
+ return Point(_pt[X] - o._pt[X], _pt[Y] - o._pt[Y]);
+ }
+ inline Point &operator+=(Point const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] += o._pt[i];
+ }
+ return *this;
+ }
+ inline Point &operator-=(Point const &o) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) {
+ _pt[i] -= o._pt[i];
+ }
+ return *this;
+ }
+
+ inline Point operator-() const {
+ return Point(-_pt[X], -_pt[Y]);
+ }
+ inline Point operator*(double const s) const {
+ return Point(_pt[X] * s, _pt[Y] * s);
+ }
+ inline Point operator/(double const s) const {
+ //TODO: s == 0?
+ return Point(_pt[X] / s, _pt[Y] / s);
+ }
+ inline Point &operator*=(double const s) {
+ for ( unsigned i = 0 ; i < 2 ; ++i ) _pt[i] *= s;
+ return *this;
+ }
+ inline Point &operator/=(double const s) {
+ //TODO: s == 0?
+ for ( unsigned i = 0 ; i < 2 ; ++i ) _pt[i] /= s;
+ return *this;
+ }
+
+ Point &operator*=(Matrix const &m);
+
+ inline int operator == (const Point &in_pnt) {
+ return ((_pt[X] == in_pnt[X]) && (_pt[Y] == in_pnt[Y]));
+ }
+
+ friend inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_pnt);
+};
+
+inline Point operator*(double const s, Point const &p) { return p * s; }
+
+/** A function to print out the Point. It just prints out the coords
+ on the given output stream */
+inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_pnt) {
+ out_file << "X: " << in_pnt[X] << " Y: " << in_pnt[Y];
+ return out_file;
+}
+
+/** This is a rotation (sort of). */
+inline Point operator^(Point const &a, Point const &b) {
+ Point const ret(a[0] * b[0] - a[1] * b[1],
+ a[1] * b[0] + a[0] * b[1]);
+ return ret;
+}
+
+//IMPL: boost::EqualityComparableConcept
+inline bool operator==(Point const &a, Point const &b) {
+ return (a[X] == b[X]) && (a[Y] == b[Y]);
+}
+inline bool operator!=(Point const &a, Point const &b) {
+ return (a[X] != b[X]) || (a[Y] != b[Y]);
+}
+
+/** This is a lexicographical ordering for points. It is remarkably useful for sweepline algorithms*/
+inline bool operator<=(Point const &a, Point const &b) {
+ return ( ( a[Y] < b[Y] ) ||
+ (( a[Y] == b[Y] ) && ( a[X] < b[X] )));
+}
+
+Coord L1(Point const &p);
+
+/** Compute the L2, or euclidean, norm of \a p. */
+inline Coord L2(Point const &p) { return p.length(); }
+
+/** Compute the square of L2 norm of \a p. Warning: this can overflow where L2 won't.*/
+inline Coord L2sq(Point const &p) { return p[0]*p[0] + p[1]*p[1]; }
+
+double LInfty(Point const &p);
+bool is_zero(Point const &p);
+bool is_unit_vector(Point const &p);
+
+extern double atan2(Point const p);
+/** compute the angle turning from a to b (signed). */
+extern double angle_between(Point const a, Point const b);
+
+//IMPL: NearConcept
+inline bool near(Point const &a, Point const &b, double const eps=EPSILON) {
+ return ( near(a[X],b[X],eps) && near(a[Y],b[Y],eps) );
+}
+
+/** Returns p * Geom::rotate_degrees(90), but more efficient.
+ *
+ * Angle direction in Inkscape code: If you use the traditional mathematics convention that y
+ * increases upwards, then positive angles are anticlockwise as per the mathematics convention. If
+ * you take the common non-mathematical convention that y increases downwards, then positive angles
+ * are clockwise, as is common outside of mathematics.
+ *
+ * There is no rot_neg90 function: use -rot90(p) instead.
+ */
+inline Point rot90(Point const &p) { return Point(-p[Y], p[X]); }
+
+/** Given two points and a parameter t \in [0, 1], return a point
+ * proportionally from a to b by t. Akin to 1 degree bezier.*/
+inline Point lerp(double const t, Point const a, Point const b) { return (a * (1 - t) + b * t); }
+
+Point unit_vector(Point const &a);
+
+/** compute the dot product (inner product) between the vectors a and b. */
+inline Coord dot(Point const &a, Point const &b) { return a[0] * b[0] + a[1] * b[1]; }
+/** Defined as dot(a, b.cw()). */
+inline Coord cross(Point const &a, Point const &b) { return dot(a, b.cw()); }
+
+/** compute the euclidean distance between points a and b. TODO: hypot safer/faster? */
+inline Coord distance (Point const &a, Point const &b) { return L2(a - b); }
+
+/** compute the square of the distance between points a and b. */
+inline Coord distanceSq (Point const &a, Point const &b) { return L2sq(a - b); }
+
+Point abs(Point const &b);
+
+Point operator*(Point const &v, Matrix const &m);
+
+Point operator/(Point const &p, Matrix const &m);
+
+} /* namespace Geom */
+
+#endif /* !SEEN_Geom_POINT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/poly-dk-solve.cpp b/src/2geom/poly-dk-solve.cpp
new file mode 100644
index 000000000..dde230dc4
--- /dev/null
+++ b/src/2geom/poly-dk-solve.cpp
@@ -0,0 +1,64 @@
+#include "poly-dk-solve.h"
+#include <iterator>
+
+/*** implementation of the Durand-Kerner method. seems buggy*/
+
+std::complex<double> evalu(Poly const & p, std::complex<double> x) {
+ std::complex<double> result = 0;
+ std::complex<double> xx = 1;
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result += p[i]*xx;
+ xx *= x;
+ }
+ return result;
+}
+
+std::vector<std::complex<double> > DK(Poly const & ply, const double tol) {
+ std::vector<std::complex<double> > roots;
+ const int N = ply.degree();
+
+ std::complex<double> b(0.4, 0.9);
+ std::complex<double> p = 1;
+ for(int i = 0; i < N; i++) {
+ roots.push_back(p);
+ p *= b;
+ }
+ assert(roots.size() == ply.degree());
+
+ double error = 0;
+ int i;
+ for( i = 0; i < 30; i++) {
+ error = 0;
+ for(int r_i = 0; r_i < N; r_i++) {
+ std::complex<double> denom = 1;
+ std::complex<double> R = roots[r_i];
+ for(int d_i = 0; d_i < N; d_i++) {
+ if(r_i != d_i)
+ denom *= R-roots[d_i];
+ }
+ assert(norm(denom) != 0);
+ std::complex<double> dr = evalu(ply, R)/denom;
+ error += norm(dr);
+ roots[r_i] = R - dr;
+ }
+ /*std::copy(roots.begin(), roots.end(), std::ostream_iterator<std::complex<double> >(std::cout, ",\t"));
+ std::cout << std::endl;*/
+ if(error < tol)
+ break;
+ }
+ //std::cout << error << ", " << i<< std::endl;
+ return roots;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/poly-dk-solve.h b/src/2geom/poly-dk-solve.h
new file mode 100644
index 000000000..9a4dd360f
--- /dev/null
+++ b/src/2geom/poly-dk-solve.h
@@ -0,0 +1,5 @@
+#include "poly.h"
+#include <complex>
+
+std::vector<std::complex<double> >
+DK(Poly const & ply, const double tol=1e-10);
diff --git a/src/2geom/poly-laguerre-solve.cpp b/src/2geom/poly-laguerre-solve.cpp
new file mode 100644
index 000000000..83f049286
--- /dev/null
+++ b/src/2geom/poly-laguerre-solve.cpp
@@ -0,0 +1,147 @@
+#include "poly-laguerre-solve.h"
+#include <iterator>
+
+typedef std::complex<double> cdouble;
+
+cdouble laguerre_internal_complex(Poly const & p,
+ double x0,
+ double tol,
+ bool & quad_root) {
+ cdouble a = 2*tol;
+ cdouble xk = x0;
+ double n = p.degree();
+ quad_root = false;
+ const unsigned shuffle_rate = 10;
+ static double shuffle[] = {0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 1.0};
+ unsigned shuffle_counter = 0;
+ while(std::norm(a) > (tol*tol)) {
+ //std::cout << "xk = " << xk << std::endl;
+ cdouble b = p.back();
+ cdouble d = 0, f = 0;
+ double err = abs(b);
+ double abx = abs(xk);
+ for(int j = p.size()-2; j >= 0; j--) {
+ f = xk*f + d;
+ d = xk*d + b;
+ b = xk*b + p[j];
+ err = abs(b) + abx*err;
+ }
+
+ err *= 1e-7; // magic epsilon for convergence, should be computed from tol
+
+ cdouble px = b;
+ if(abs(b) < err)
+ return xk;
+ //if(std::norm(px) < tol*tol)
+ // return xk;
+ cdouble G = d / px;
+ cdouble H = G*G - f / px;
+
+ //std::cout << "G = " << G << "H = " << H;
+ cdouble radicand = (n - 1)*(n*H-G*G);
+ //assert(radicand.real() > 0);
+ if(radicand.real() < 0)
+ quad_root = true;
+ //std::cout << "radicand = " << radicand << std::endl;
+ if(G.real() < 0) // here we try to maximise the denominator avoiding cancellation
+ a = - sqrt(radicand);
+ else
+ a = sqrt(radicand);
+ //std::cout << "a = " << a << std::endl;
+ a = n / (a + G);
+ //std::cout << "a = " << a << std::endl;
+ if(shuffle_counter % shuffle_rate == 0)
+ ;//a *= shuffle[shuffle_counter / shuffle_rate];
+ xk -= a;
+ shuffle_counter++;
+ if(shuffle_counter >= 90)
+ break;
+ }
+ //std::cout << "xk = " << xk << std::endl;
+ return xk;
+}
+
+double laguerre_internal(Poly const & p,
+ Poly const & pp,
+ Poly const & ppp,
+ double x0,
+ double tol,
+ bool & quad_root) {
+ double a = 2*tol;
+ double xk = x0;
+ double n = p.degree();
+ quad_root = false;
+ while(a*a > (tol*tol)) {
+ //std::cout << "xk = " << xk << std::endl;
+ double px = p(xk);
+ if(px*px < tol*tol)
+ return xk;
+ double G = pp(xk) / px;
+ double H = G*G - ppp(xk) / px;
+
+ //std::cout << "G = " << G << "H = " << H;
+ double radicand = (n - 1)*(n*H-G*G);
+ assert(radicand > 0);
+ //std::cout << "radicand = " << radicand << std::endl;
+ if(G < 0) // here we try to maximise the denominator avoiding cancellation
+ a = - sqrt(radicand);
+ else
+ a = sqrt(radicand);
+ //std::cout << "a = " << a << std::endl;
+ a = n / (a + G);
+ //std::cout << "a = " << a << std::endl;
+ xk -= a;
+ }
+ //std::cout << "xk = " << xk << std::endl;
+ return xk;
+}
+
+
+std::vector<cdouble >
+laguerre(Poly p, const double tol) {
+ std::vector<cdouble > solutions;
+ //std::cout << "p = " << p << " = ";
+ while(p.size() > 1)
+ {
+ double x0 = 0;
+ bool quad_root = false;
+ cdouble sol = laguerre_internal_complex(p, x0, tol, quad_root);
+ //if(abs(sol) > 1) break;
+ Poly dvs;
+ if(quad_root) {
+ dvs.push_back((sol*conj(sol)).real());
+ dvs.push_back(-(sol + conj(sol)).real());
+ dvs.push_back(1.0);
+ //std::cout << "(" << dvs << ")";
+ //solutions.push_back(sol);
+ //solutions.push_back(conj(sol));
+ } else {
+ //std::cout << sol << std::endl;
+ dvs.push_back(-sol.real());
+ dvs.push_back(1.0);
+ solutions.push_back(sol);
+ //std::cout << "(" << dvs << ")";
+ }
+ Poly r;
+ p = divide(p, dvs, r);
+ //std::cout << r << std::endl;
+ }
+ return solutions;
+}
+
+std::vector<double>
+laguerre_real_interval(Poly const & ply,
+ const double lo, const double hi,
+ const double tol) {
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/poly-laguerre-solve.h b/src/2geom/poly-laguerre-solve.h
new file mode 100644
index 000000000..86d10098e
--- /dev/null
+++ b/src/2geom/poly-laguerre-solve.h
@@ -0,0 +1,10 @@
+#include "poly.h"
+#include <complex>
+
+std::vector<std::complex<double> >
+laguerre(Poly ply, const double tol=1e-10);
+
+std::vector<double>
+laguerre_real_interval(Poly ply,
+ const double lo, const double hi,
+ const double tol=1e-10);
diff --git a/src/2geom/poly.cpp b/src/2geom/poly.cpp
new file mode 100644
index 000000000..5c3a30002
--- /dev/null
+++ b/src/2geom/poly.cpp
@@ -0,0 +1,197 @@
+#include "poly.h"
+
+Poly Poly::operator*(const Poly& p) const {
+ Poly result;
+ result.resize(degree() + p.degree()+1);
+
+ for(unsigned i = 0; i < size(); i++) {
+ for(unsigned j = 0; j < p.size(); j++) {
+ result[i+j] += (*this)[i] * p[j];
+ }
+ }
+ return result;
+}
+#ifdef HAVE_GSL
+#include <gsl/gsl_poly.h>
+#endif
+
+/*double Poly::eval(double x) const {
+ return gsl_poly_eval(&coeff[0], size(), x);
+ }*/
+
+void Poly::normalize() {
+ while(back() == 0)
+ pop_back();
+}
+
+void Poly::monicify() {
+ normalize();
+
+ double scale = 1./back(); // unitize
+
+ for(unsigned i = 0; i < size(); i++) {
+ (*this)[i] *= scale;
+ }
+}
+
+
+#ifdef HAVE_GSL
+std::vector<std::complex<double> > solve(Poly const & pp) {
+ Poly p(pp);
+ p.normalize();
+ gsl_poly_complex_workspace * w
+ = gsl_poly_complex_workspace_alloc (p.size());
+
+ gsl_complex_packed_ptr z = new double[p.degree()*2];
+ double* a = new double[p.size()];
+ for(int i = 0; i < p.size(); i++)
+ a[i] = p[i];
+ std::vector<std::complex<double> > roots;
+ //roots.resize(p.degree());
+
+ gsl_poly_complex_solve (a, p.size(), w, z);
+ delete[]a;
+
+ gsl_poly_complex_workspace_free (w);
+
+ for (int i = 0; i < p.degree(); i++) {
+ roots.push_back(std::complex<double> (z[2*i] ,z[2*i+1]));
+ //printf ("z%d = %+.18f %+.18f\n", i, z[2*i], z[2*i+1]);
+ }
+ delete[] z;
+ return roots;
+}
+
+std::vector<double > solve_reals(Poly const & p) {
+ std::vector<std::complex<double> > roots = solve(p);
+ std::vector<double> real_roots;
+
+ for(int i = 0; i < roots.size(); i++) {
+ if(roots[i].imag() == 0) // should be more lenient perhaps
+ real_roots.push_back(roots[i].real());
+ }
+ return real_roots;
+}
+#endif
+
+double polish_root(Poly const & p, double guess, double tol) {
+ Poly dp = derivative(p);
+
+ double fn = p(guess);
+ while(fabs(fn) > tol) {
+ guess -= fn/dp(guess);
+ fn = p(guess);
+ }
+ return guess;
+}
+
+Poly integral(Poly const & p) {
+ Poly result;
+
+ result.reserve(p.size()+1);
+ result.push_back(0); // arbitrary const
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(p[i]/(i+1));
+ }
+ return result;
+
+}
+
+Poly derivative(Poly const & p) {
+ Poly result;
+
+ if(p.size() <= 1)
+ return Poly(0);
+ result.reserve(p.size()-1);
+ for(unsigned i = 1; i < p.size(); i++) {
+ result.push_back(i*p[i]);
+ }
+ return result;
+}
+
+Poly compose(Poly const & a, Poly const & b) {
+ Poly result;
+
+ for(unsigned i = a.size()-1; i >=0; i--) {
+ result = Poly(a[i]) + result * b;
+ }
+ return result;
+
+}
+
+/* This version is backwards - dividing taylor terms
+Poly divide(Poly const &a, Poly const &b, Poly &r) {
+ Poly c;
+ r = a; // remainder
+
+ const unsigned k = a.size();
+ r.resize(k, 0);
+ c.resize(k, 0);
+
+ for(unsigned i = 0; i < k; i++) {
+ double ci = r[i]/b[0];
+ c[i] += ci;
+ Poly bb = ci*b;
+ std::cout << ci <<"*" << b << ", r= " << r << std::endl;
+ r -= bb.shifted(i);
+ }
+
+ return c;
+}
+*/
+
+Poly divide(Poly const &a, Poly const &b, Poly &r) {
+ Poly c;
+ r = a; // remainder
+ assert(b.size() > 0);
+
+ const unsigned k = a.degree();
+ const unsigned l = b.degree();
+ c.resize(k, 0.);
+
+ for(unsigned i = k; i >= l; i--) {
+ assert(i >= 0);
+ double ci = r.back()/b.back();
+ c[i-l] += ci;
+ Poly bb = ci*b;
+ //std::cout << ci <<"*(" << b.shifted(i-l) << ") = "
+ // << bb.shifted(i-l) << " r= " << r << std::endl;
+ r -= bb.shifted(i-l);
+ r.pop_back();
+ }
+ //std::cout << "r= " << r << std::endl;
+ r.normalize();
+ c.normalize();
+
+ return c;
+}
+
+Poly gcd(Poly const &a, Poly const &b, const double tol) {
+ if(a.size() < b.size())
+ return gcd(b, a);
+ if(b.size() <= 0)
+ return a;
+ if(b.size() == 1)
+ return a;
+ Poly r;
+ divide(a, b, r);
+ return gcd(b, r);
+}
+
+
+
+/*Poly divide_out_root(Poly const & p, double x) {
+ assert(1);
+ }*/
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/poly.h b/src/2geom/poly.h
new file mode 100644
index 000000000..4fc68098f
--- /dev/null
+++ b/src/2geom/poly.h
@@ -0,0 +1,219 @@
+#ifndef SEEN_POLY_H
+#define SEEN_POLY_H
+#include <assert.h>
+#include <vector>
+#include <iostream>
+#include <algorithm>
+#include <complex>
+#include "utils.h"
+
+class Poly : public std::vector<double>{
+public:
+ // coeff; // sum x^i*coeff[i]
+
+ //unsigned size() const { return coeff.size();}
+ unsigned degree() const { return size()-1;}
+
+ //double operator[](const int i) const { return (*this)[i];}
+ //double& operator[](const int i) { return (*this)[i];}
+
+ Poly operator+(const Poly& p) const {
+ Poly result;
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ //result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back((*this)[i] + p[i]);
+ }
+ for(unsigned i = min_size; i < size(); i++)
+ result.push_back((*this)[i]);
+ for(unsigned i = min_size; i < p.size(); i++)
+ result.push_back(p[i]);
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator-(const Poly& p) const {
+ Poly result;
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back((*this)[i] - p[i]);
+ }
+ for(unsigned i = min_size; i < size(); i++)
+ result.push_back((*this)[i]);
+ for(unsigned i = min_size; i < p.size(); i++)
+ result.push_back(-p[i]);
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator-=(const Poly& p) {
+ const unsigned out_size = std::max(size(), p.size());
+ const unsigned min_size = std::min(size(), p.size());
+ resize(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ (*this)[i] -= p[i];
+ }
+ for(unsigned i = min_size; i < out_size; i++)
+ (*this)[i] = -p[i];
+ return *this;
+ }
+ Poly operator-(const double k) const {
+ Poly result;
+ const unsigned out_size = size();
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < out_size; i++) {
+ result.push_back((*this)[i]);
+ }
+ result[0] -= k;
+ return result;
+ }
+ Poly operator-() const {
+ Poly result;
+ result.resize(size());
+
+ for(unsigned i = 0; i < size(); i++) {
+ result[i] = -(*this)[i];
+ }
+ return result;
+ }
+ Poly operator*(const double p) const {
+ Poly result;
+ const unsigned out_size = size();
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < out_size; i++) {
+ result.push_back((*this)[i]*p);
+ }
+ assert(result.size() == out_size);
+ return result;
+ }
+// equivalent to multiply by x^terms, discard negative terms
+ Poly shifted(unsigned terms) const {
+ Poly result;
+ const unsigned out_size = std::max(unsigned(0), size()+terms);
+ result.reserve(out_size);
+
+ if(terms < 0) {
+ for(unsigned i = 0; i < out_size; i++) {
+ result.push_back((*this)[i-terms]);
+ }
+ } else {
+ for(unsigned i = 0; i < terms; i++) {
+ result.push_back(0.0);
+ }
+ for(unsigned i = 0; i < size(); i++) {
+ result.push_back((*this)[i]);
+ }
+ }
+
+ assert(result.size() == out_size);
+ return result;
+ }
+ Poly operator*(const Poly& p) const;
+
+ template <typename T>
+ T eval(T x) const {
+ T r = 0;
+ for(int k = size()-1; k >= 0; k--) {
+ r = r*x + T((*this)[k]);
+ }
+ return r;
+ }
+
+ template <typename T>
+ T operator()(T t) const { return (T)eval(t);}
+
+ void normalize();
+
+ void monicify();
+ Poly() {}
+ Poly(const Poly& p) : std::vector<double>(p) {}
+ Poly(const double a) {push_back(a);}
+
+public:
+ template <class T, class U>
+ void val_and_deriv(T x, U &pd) const {
+ pd[0] = back();
+ int nc = size() - 1;
+ int nd = pd.size() - 1;
+ for(unsigned j = 1; j < pd.size(); j++)
+ pd[j] = 0.0;
+ for(int i = nc -1; i >= 0; i--) {
+ int nnd = std::min(nd, nc-i);
+ for(int j = nnd; j >= 1; j--)
+ pd[j] = pd[j]*x + operator[](i);
+ pd[0] = pd[0]*x + operator[](i);
+ }
+ double cnst = 1;
+ for(int i = 2; i <= nd; i++) {
+ cnst *= i;
+ pd[i] *= cnst;
+ }
+ }
+
+ static Poly linear(double ax, double b) {
+ Poly p;
+ p.push_back(b);
+ p.push_back(ax);
+ return p;
+ }
+};
+
+inline Poly operator*(double a, Poly const & b) { return b * a;}
+
+Poly integral(Poly const & p);
+Poly derivative(Poly const & p);
+Poly divide_out_root(Poly const & p, double x);
+Poly compose(Poly const & a, Poly const & b);
+Poly divide(Poly const &a, Poly const &b, Poly &r);
+Poly gcd(Poly const &a, Poly const &b, const double tol=1e-10);
+
+/*** solve(Poly p)
+ * find all p.degree() roots of p.
+ * This function can take a long time with suitably crafted polynomials, but in practice it should be fast. Should we provide special forms for degree() <= 4?
+ */
+std::vector<std::complex<double> > solve(const Poly & p);
+
+/*** solve_reals(Poly p)
+ * find all real solutions to Poly p.
+ * currently we just use solve and pick out the suitably real looking values, there may be a better algorithm.
+ */
+std::vector<double> solve_reals(const Poly & p);
+double polish_root(Poly const & p, double guess, double tol);
+
+inline std::ostream &operator<< (std::ostream &out_file, const Poly &in_poly) {
+ if(in_poly.size() == 0)
+ out_file << "0";
+ else {
+ for(int i = (int)in_poly.size()-1; i >= 0; --i) {
+ if(i == 1) {
+ out_file << "" << in_poly[i] << "*x";
+ out_file << " + ";
+ } else if(i) {
+ out_file << "" << in_poly[i] << "*x^" << i;
+ out_file << " + ";
+ } else
+ out_file << in_poly[i];
+
+ }
+ }
+ return out_file;
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#endif
diff --git a/src/2geom/quadtree.cpp b/src/2geom/quadtree.cpp
new file mode 100644
index 000000000..c033b7604
--- /dev/null
+++ b/src/2geom/quadtree.cpp
@@ -0,0 +1,131 @@
+#include "quadtree.h"
+
+Quad* QuadTree::search(double x0, double y0, double x1, double y1) {
+ Quad *q = root;
+
+ double bxx0 = bx1, bxx1 = bx1;
+ double byy0 = by1, byy1 = by1;
+ while(q) {
+ double cx = (bxx0 + bxx1)/2;
+ double cy = (byy0 + byy1)/2;
+ unsigned i = 0;
+ if(x0 >= cx) {
+ i += 1;
+ bxx0 = cx; // zoom in a quad
+ } else if(x1 <= cx) {
+ bxx1 = cx;
+ } else
+ break;
+ if(y0 >= cy) {
+ i += 2;
+ byy0 = cy;
+ } else if(y1 <= cy) {
+ byy1 = cy;
+ } else
+ break;
+
+ assert(i < 4);
+ Quad *qq = q->children[i];
+ if(qq == 0) break; // last non-null
+ q = qq;
+ }
+ return q;
+}
+
+void QuadTree::insert(double x0, double y0, double x1, double y1, int shape) {
+ // loop until a quad would break the box.
+ if(root == 0) {
+ root = new Quad;
+
+ bx0 = 0;
+ bx1 = 1;
+ by0 = 0;
+ by1 = 1;
+ }
+ Quad *q = root;
+
+ double bxx0 = bx0, bxx1 = bx1;
+ double byy0 = by0, byy1 = by1;
+ while((bxx0 > x0) ||
+ (bxx1 < x1) ||
+ (byy0 > y0) ||
+ (byy1 < y1)) { // too small initial size - double
+ unsigned i = 0;
+ if(bxx0 > x0) {
+ bxx0 = 2*bxx0 - bxx1;
+ i += 1;
+ } else {
+ bxx1 = 2*bxx1 - bxx0;
+ }
+ if(byy0 > y0) {
+ byy0 = 2*byy0 - byy1;
+ i += 2;
+ } else {
+ byy1 = 2*byy1 - byy0;
+ }
+ q = new Quad;
+ q->children[i] = root;
+ root = q;
+ bx0 = bxx0;
+ bx1 = bxx1;
+ by0 = byy0;
+ by1 = byy1;
+ }
+
+ while(q) {
+ double cx = (bxx0 + bxx1)/2;
+ double cy = (byy0 + byy1)/2;
+ unsigned i = 0;
+ assert(x0 >= bxx0);
+ assert(x1 <= bxx1);
+ assert(y0 >= byy0);
+ assert(y1 <= byy1);
+ if(x0 >= cx) {
+ i += 1;
+ bxx0 = cx; // zoom in a quad
+ } else if(x1 <= cx) {
+ bxx1 = cx;
+ } else
+ break;
+ if(y0 >= cy) {
+ i += 2;
+ byy0 = cy;
+ } else if(y1 <= cy) {
+ byy1 = cy;
+ } else
+ break;
+
+ assert(i < 4);
+ Quad *qq = q->children[i];
+ if(qq == 0) {
+ qq = new Quad;
+ q->children[i] = qq;
+ }
+ q = qq;
+ }
+ q->data.push_back(shape);
+}
+void QuadTree::erase(Quad *q, int shape) {
+ for(Quad::iterator i = q->data.begin(); i != q->data.end(); i++) {
+ if(*i == shape) {
+ q->data.erase(i);
+ if(q->data.empty()) {
+
+ }
+ }
+ }
+ return;
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/quadtree.h b/src/2geom/quadtree.h
new file mode 100644
index 000000000..ef583c0b5
--- /dev/null
+++ b/src/2geom/quadtree.h
@@ -0,0 +1,41 @@
+#include <vector>
+#include <cassert>
+
+class Quad{
+public:
+ Quad* children[4];
+ std::vector<int> data;
+ Quad() {
+ for(int i = 0; i < 4; i++)
+ children[i] = 0;
+ }
+ typedef std::vector<int>::iterator iterator;
+};
+
+class QuadTree{
+public:
+ Quad* root;
+ double scale;
+ double bx0, bx1;
+ double by0, by1;
+
+ QuadTree() : root(0), scale(1) {}
+
+ Quad* search(double x0, double y0, double x1, double y1);
+ void insert(double x0, double y0, double x1, double y1, int shape);
+ void erase(Quad *q, int shape);
+};
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/rect.h b/src/2geom/rect.h
new file mode 100644
index 000000000..d96802014
--- /dev/null
+++ b/src/2geom/rect.h
@@ -0,0 +1,97 @@
+//D2<Interval> specialization to Rect:
+
+ /* Authors of original rect class:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * bulia byak <buliabyak@users.sf.net>
+ * MenTaLguY <mental@rydia.net>
+ */
+
+#ifdef _2GEOM_D2 /*This is intentional: we don't actually want anyone to
+ include this, other than D2.h */
+#ifndef _2GEOM_RECT
+#define _2GEOM_RECT
+
+#include "matrix.h"
+#include <boost/optional/optional.hpp>
+
+namespace Geom {
+
+typedef D2<Interval> Rect;
+
+Rect unify(const Rect &, const Rect &);
+
+template<>
+class D2<Interval> {
+ private:
+ Interval f[2];
+ D2<Interval>();// { f[X] = f[Y] = Interval(0, 0); }
+
+ public:
+ D2<Interval>(Interval const &a, Interval const &b) {
+ f[X] = a;
+ f[Y] = b;
+ }
+
+ D2<Interval>(Point const & a, Point const & b) {
+ f[X] = Interval(a[X], b[X]);
+ f[Y] = Interval(a[Y], b[Y]);
+ }
+
+ inline Interval& operator[](unsigned i) { return f[i]; }
+ inline Interval const & operator[](unsigned i) const { return f[i]; }
+
+ inline Point min() const { return Point(f[X].min(), f[Y].min()); }
+ inline Point max() const { return Point(f[X].max(), f[Y].max()); }
+
+ /** returns the four corners of the rectangle in order
+ * (clockwise if +Y is up, anticlockwise if +Y is down) */
+ Point corner(unsigned i) const {
+ switch(i % 4) {
+ case 0: return Point(f[X].min(), f[Y].min());
+ case 1: return Point(f[X].max(), f[Y].min());
+ case 2: return Point(f[X].max(), f[Y].max()); case 3: return Point(f[X].min(), f[Y].max());
+ }
+ }
+
+ inline double width() const { return f[X].extent(); }
+ inline double height() const { return f[Y].extent(); }
+
+ /** returns a vector from min to max. */
+ inline Point dimensions() const { return Point(f[X].extent(), f[Y].extent()); }
+ inline Point midpoint() const { return Point(f[X].middle(), f[Y].middle()); }
+
+ inline double area() const { return f[X].extent() * f[Y].extent(); }
+ inline double maxExtent() const { return std::max(f[X].extent(), f[Y].extent()); }
+
+ inline bool isEmpty() const { return f[X].isEmpty() && f[Y].isEmpty(); }
+ inline bool intersects(Rect const &r) const { return f[X].intersects(r[X]) && f[Y].intersects(r[Y]); }
+ inline bool contains(Rect const &r) const { return f[X].contains(r[X]) && f[Y].contains(r[Y]); }
+ inline bool contains(Point const &p) const { return f[X].contains(p[X]) && f[Y].contains(p[Y]); }
+
+ inline void expandTo(Point p) { f[X].extendTo(p[X]); f[Y].extendTo(p[Y]); }
+ inline void unionWith(Rect const &b) { f[X].unionWith(b[X]); f[Y].unionWith(b[Y]); }
+
+ inline void expandBy(double amnt) { f[X].expandBy(amnt); f[Y].expandBy(amnt); }
+ inline void expandBy(Point const p) { f[X].expandBy(p[X]); f[Y].expandBy(p[Y]); }
+
+ /** Transforms the rect by m. Note that it gives correct results only for scales and translates,
+ in the case of rotations, the area of the rect will grow as it cannot rotate. */
+ inline Rect operator*(Matrix const m) const { return unify(Rect(corner(0) * m, corner(2) * m),
+ Rect(corner(1) * m, corner(3) * m)); }
+};
+
+inline Rect unify(const Rect & a, const Rect & b) {
+ return Rect(unify(a[X], b[X]), unify(a[Y], b[Y]));
+}
+
+inline boost::optional<Rect> intersect(const Rect & a, const Rect & b) {
+ boost::optional<Interval> x = intersect(a[X], b[X]);
+ boost::optional<Interval> y = intersect(a[Y], b[Y]);
+ return x && y ? boost::optional<Rect>(Rect(*x, *y)) : boost::optional<Rect>();
+}
+
+}
+
+#endif //_2GEOM_RECT
+#endif //_2GEOM_D2
diff --git a/src/2geom/sbasis-2d.cpp b/src/2geom/sbasis-2d.cpp
new file mode 100644
index 000000000..e271fed13
--- /dev/null
+++ b/src/2geom/sbasis-2d.cpp
@@ -0,0 +1,72 @@
+#include "sbasis-2d.h"
+
+namespace Geom{
+
+SBasis extract_u(SBasis2d const &a, double u) {
+ SBasis sb;
+ double s = u*(1-u);
+
+ for(unsigned vi = 0; vi < a.vs; vi++) {
+ double sk = 1;
+ Linear bo(0,0);
+ for(unsigned ui = 0; ui < a.us; ui++) {
+ bo += (extract_u(a.index(ui, vi), u))*sk;
+ sk *= s;
+ }
+ sb.push_back(bo);
+ }
+
+ return sb;
+}
+
+SBasis extract_v(SBasis2d const &a, double v) {
+ SBasis sb;
+ double s = v*(1-v);
+
+ for(unsigned ui = 0; ui < a.us; ui++) {
+ double sk = 1;
+ Linear bo(0,0);
+ for(unsigned vi = 0; vi < a.vs; vi++) {
+ bo += (extract_v(a.index(ui, vi), v))*sk;
+ sk *= s;
+ }
+ sb.push_back(bo);
+ }
+
+ return sb;
+}
+
+SBasis compose(Linear2d const &a, D2<SBasis> const &p) {
+ D2<SBasis> omp(-p[X] + 1, -p[Y] + 1);
+ return multiply(omp[0], omp[1])*a[0] +
+ multiply(p[0], omp[1])*a[1] +
+ multiply(omp[0], p[1])*a[2] +
+ multiply(p[0], p[1])*a[3];
+}
+
+SBasis
+compose(SBasis2d const &fg, D2<SBasis> const &p) {
+ SBasis B;
+ SBasis s[2];
+ SBasis ss[2];
+ for(unsigned dim = 0; dim < 2; dim++)
+ s[dim] = p[dim]*(Linear(1) - p[dim]);
+ ss[1] = Linear(1);
+ for(unsigned vi = 0; vi < fg.vs; vi++) {
+ ss[0] = ss[1];
+ for(unsigned ui = 0; ui < fg.us; ui++) {
+ unsigned i = ui + vi*fg.us;
+ B += ss[0]*compose(fg[i], p);
+ ss[0] *= s[0];
+ }
+ ss[1] *= s[1];
+ }
+ return B;
+}
+
+D2<SBasis>
+compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p) {
+ return D2<SBasis>(compose(fg[X], p), compose(fg[Y], p));
+}
+
+};
diff --git a/src/2geom/sbasis-2d.h b/src/2geom/sbasis-2d.h
new file mode 100644
index 000000000..921a740b9
--- /dev/null
+++ b/src/2geom/sbasis-2d.h
@@ -0,0 +1,325 @@
+#ifndef SEEN_SBASIS_2D_H
+#define SEEN_SBASIS_2D_H
+#include <vector>
+#include <cassert>
+#include <algorithm>
+#include "d2.h"
+#include "sbasis.h"
+#include <iostream>
+
+namespace Geom{
+
+class Linear2d{
+public:
+ /*
+ u 0,1
+ v 0,2
+ */
+ double a[4];
+ Linear2d() {}
+ Linear2d(double aa) {
+ for(unsigned i = 0 ; i < 4; i ++)
+ a[i] = aa;
+ }
+ Linear2d(double a00, double a01, double a10, double a11)
+ {
+ a[0] = a00;
+ a[1] = a01;
+ a[2] = a10;
+ a[3] = a11;
+ }
+
+ double operator[](const int i) const {
+ assert(i >= 0);
+ assert(i < 4);
+ return a[i];
+ }
+ double& operator[](const int i) {
+ assert(i >= 0);
+ assert(i < 4);
+ return a[i];
+ }
+ double apply(double u, double v) {
+ return (a[0]*(1-u)*(1-v) +
+ a[1]*u*(1-v) +
+ a[2]*(1-u)*v +
+ a[3]*u*v);
+ }
+};
+
+inline Linear extract_u(Linear2d const &a, double u) {
+ return Linear(a[0]*(1-u) +
+ a[1]*u,
+ a[2]*(1-u) +
+ a[3]*u);
+}
+inline Linear extract_v(Linear2d const &a, double v) {
+ return Linear(a[0]*(1-v) +
+ a[2]*v,
+ a[1]*(1-v) +
+ a[3]*v);
+}
+inline Linear2d operator-(Linear2d const &a) {
+ return Linear2d(-a.a[0], -a.a[1],
+ -a.a[2], -a.a[3]);
+}
+inline Linear2d operator+(Linear2d const & a, Linear2d const & b) {
+ return Linear2d(a[0] + b[0],
+ a[1] + b[1],
+ a[2] + b[2],
+ a[3] + b[3]);
+}
+inline Linear2d operator-(Linear2d const & a, Linear2d const & b) {
+ return Linear2d(a[0] - b[0],
+ a[1] - b[1],
+ a[2] - b[2],
+ a[3] - b[3]);
+}
+inline Linear2d& operator+=(Linear2d & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] += b[i];
+ return a;
+}
+inline Linear2d& operator-=(Linear2d & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] -= b[i];
+ return a;
+}
+inline Linear2d& operator*=(Linear2d & a, double b) {
+ for(unsigned i = 0; i < 4; i++)
+ a[i] *= b;
+ return a;
+}
+
+inline bool operator==(Linear2d const & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ if(a[i] != b[i])
+ return false;
+ return true;
+}
+inline bool operator!=(Linear2d const & a, Linear2d const & b) {
+ for(unsigned i = 0; i < 4; i++)
+ if(a[i] == b[i])
+ return false;
+ return true;
+}
+inline Linear2d operator*(double const a, Linear2d const & b) {
+ return Linear2d(a*b[0], a*b[1],
+ a*b[2], a*b[3]);
+}
+
+class SBasis2d : public std::vector<Linear2d>{
+public:
+ // vector in u,v
+ unsigned us, vs; // number of u terms, v terms
+ SBasis2d() {}
+ SBasis2d(Linear2d const & bo)
+ : us(1), vs(1) {
+ push_back(bo);
+ }
+ SBasis2d(SBasis2d const & a)
+ : std::vector<Linear2d>(a), us(a.us), vs(a.vs) {}
+
+ Linear2d& index(unsigned ui, unsigned vi) {
+ assert(ui < us);
+ assert(vi < vs);
+ return (*this)[ui + vi*us];
+ }
+
+ Linear2d index(unsigned ui, unsigned vi) const {
+ if(ui >= us)
+ return Linear2d(0);
+ if(vi >= vs)
+ return Linear2d(0);
+ return (*this)[ui + vi*us];
+ }
+
+ double apply(double u, double v) const {
+ double s = u*(1-u);
+ double t = v*(1-v);
+ Linear2d p;
+ double tk = 1;
+// XXX rewrite as horner
+ for(unsigned vi = 0; vi < vs; vi++) {
+ double sk = 1;
+ for(unsigned ui = 0; ui < us; ui++) {
+ p += (sk*tk)*index(ui, vi);
+ sk *= s;
+ }
+ tk *= t;
+ }
+ return p.apply(u,v);
+ }
+
+ void clear() {
+ fill(begin(), end(), Linear2d(0));
+ }
+
+ void normalize(); // remove extra zeros
+
+ double tail_error(unsigned tail) const;
+
+ void truncate(unsigned k);
+};
+
+inline SBasis2d operator-(const SBasis2d& p) {
+ SBasis2d result;
+ result.reserve(p.size());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+
+inline SBasis2d operator+(const SBasis2d& a, const SBasis2d& b) {
+ SBasis2d result;
+ result.us = std::max(a.us, b.us);
+ result.vs = std::max(a.vs, b.vs);
+ const unsigned out_size = result.us*result.vs;
+ result.resize(out_size);
+
+ for(unsigned vi = 0; vi < result.vs; vi++) {
+ for(unsigned ui = 0; ui < result.us; ui++) {
+ Linear2d bo;
+ if(ui < a.us && vi < a.vs)
+ bo += a.index(ui, vi);
+ if(ui < b.us && vi < b.vs)
+ bo += b.index(ui, vi);
+ result.index(ui, vi) = bo;
+ }
+ }
+ return result;
+}
+
+inline SBasis2d operator-(const SBasis2d& a, const SBasis2d& b) {
+ SBasis2d result;
+ result.us = std::max(a.us, b.us);
+ result.vs = std::max(a.vs, b.vs);
+ const unsigned out_size = result.us*result.vs;
+ result.resize(out_size);
+
+ for(unsigned vi = 0; vi < result.vs; vi++) {
+ for(unsigned ui = 0; ui < result.us; ui++) {
+ Linear2d bo;
+ if(ui < a.us && vi < a.vs)
+ bo += a.index(ui, vi);
+ if(ui < b.us && vi < b.vs)
+ bo -= b.index(ui, vi);
+ result.index(ui, vi) = bo;
+ }
+ }
+ return result;
+}
+
+
+inline SBasis2d& operator+=(SBasis2d& a, const Linear2d& b) {
+ if(a.size() < 1)
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+
+inline SBasis2d& operator-=(SBasis2d& a, const Linear2d& b) {
+ if(a.size() < 1)
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+inline SBasis2d& operator+=(SBasis2d& a, double b) {
+ if(a.size() < 1)
+ a.push_back(Linear2d(b));
+ else {
+ for(unsigned i = 0; i < 4; i++)
+ a[0] += double(b);
+ }
+ return a;
+}
+
+inline SBasis2d& operator-=(SBasis2d& a, double b) {
+ if(a.size() < 1)
+ a.push_back(Linear2d(-b));
+ else {
+ a[0] -= b;
+ }
+ return a;
+}
+
+inline SBasis2d& operator*=(SBasis2d& a, double b) {
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+
+inline SBasis2d& operator/=(SBasis2d& a, double b) {
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= (1./b);
+ return a;
+}
+
+SBasis2d operator*(double k, SBasis2d const &a);
+SBasis2d operator*(SBasis2d const &a, SBasis2d const &b);
+
+SBasis2d shift(SBasis2d const &a, int sh);
+
+SBasis2d shift(Linear2d const &a, int sh);
+
+SBasis2d truncate(SBasis2d const &a, unsigned terms);
+
+SBasis2d multiply(SBasis2d const &a, SBasis2d const &b);
+
+SBasis2d integral(SBasis2d const &c);
+
+SBasis2d derivative(SBasis2d const &a);
+
+SBasis2d sqrt(SBasis2d const &a, int k);
+
+// return a kth order approx to 1/a)
+SBasis2d reciprocal(Linear2d const &a, int k);
+
+SBasis2d divide(SBasis2d const &a, SBasis2d const &b, int k);
+
+// a(b(t))
+SBasis2d compose(SBasis2d const &a, SBasis2d const &b);
+SBasis2d compose(SBasis2d const &a, SBasis2d const &b, unsigned k);
+SBasis2d inverse(SBasis2d const &a, int k);
+
+// these two should probably be replaced with compose
+SBasis extract_u(SBasis2d const &a, double u);
+SBasis extract_v(SBasis2d const &a, double v);
+
+SBasis compose(Linear2d const &a, D2<SBasis> const &p);
+
+SBasis compose(SBasis2d const &fg, D2<SBasis> const &p);
+
+D2<SBasis> compose_each(D2<SBasis2d> const &fg, D2<SBasis> const &p);
+
+inline std::ostream &operator<< (std::ostream &out_file, const Linear2d &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}, ";
+ out_file << "{" << bo[2] << ", " << bo[3] << "}";
+ return out_file;
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const SBasis2d & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#endif
diff --git a/src/2geom/sbasis-geometric.cpp b/src/2geom/sbasis-geometric.cpp
new file mode 100644
index 000000000..170323c6c
--- /dev/null
+++ b/src/2geom/sbasis-geometric.cpp
@@ -0,0 +1,378 @@
+#include "sbasis-geometric.h"
+#include "sbasis.h"
+#include "sbasis-math.h"
+//#include "solver.h"
+#include "sbasis-geometric.h"
+
+/** Geometric operators on D2<SBasis> (1D->2D).
+ * Copyright 2007 JF Barraud
+ * Copyright 2007 N Hurst
+ *
+ * The functions defined in this header related to 2d geometric operations such as arc length,
+ * unit_vector, curvature, and centroid. Most are built on top of unit_vector, which takes an
+ * arbitrary D2 and returns an D2 with unit length with the same direction.
+ *
+ * Todo/think about:
+ * arclength D2 -> sbasis (giving arclength function)
+ * does uniform_speed return natural parameterisation?
+ * integrate sb2d code from normal-bundle
+ * angle(md<2>) -> sbasis (gives angle from vector - discontinuous?)
+ * osculating circle center?
+ *
+ **/
+
+//namespace Geom{
+using namespace Geom;
+using namespace std;
+
+//Some utils first.
+//TODO: remove this!!
+static vector<double>
+vect_intersect(vector<double> const &a, vector<double> const &b, double tol=0.){
+ vector<double> inter;
+ unsigned i=0,j=0;
+ while ( i<a.size() && j<b.size() ){
+ if (fabs(a[i]-b[j])<tol){
+ inter.push_back(a[i]);
+ i+=1;
+ j+=1;
+ }else if (a[i]<b[j]){
+ i+=1;
+ }else if (a[i]>b[j]){
+ j+=1;
+ }
+ }
+ return inter;
+}
+
+static SBasis divide_by_sk(SBasis const &a, int k) {
+ assert( k<(int)a.size());
+ if(k < 0) return shift(a,-k);
+ SBasis c;
+ c.insert(c.begin(), a.begin()+k, a.end());
+ return c;
+}
+
+static SBasis divide_by_t0k(SBasis const &a, int k) {
+ if(k < 0) {
+ SBasis c = Linear(0,1);
+ for (int i=2; i<=-k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(c);
+ }else{
+ SBasis c = Linear(1,0);
+ for (int i=2; i<=k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(divide_by_sk(c,k));
+ }
+}
+
+static SBasis divide_by_t1k(SBasis const &a, int k) {
+ if(k < 0) {
+ SBasis c = Linear(1,0);
+ for (int i=2; i<=-k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(c);
+ }else{
+ SBasis c = Linear(0,1);
+ for (int i=2; i<=k; i++){
+ c*=c;
+ }
+ c*=a;
+ return(divide_by_sk(c,k));
+ }
+}
+
+static D2<SBasis> RescaleForNonVanishingEnds(D2<SBasis> const &MM, double ZERO=1.e-4){
+ D2<SBasis> M = MM;
+ //TODO: divide by all the s at once!!!
+ while (fabs(M[0].at0())<ZERO &&
+ fabs(M[1].at0())<ZERO &&
+ fabs(M[0].at1())<ZERO &&
+ fabs(M[1].at1())<ZERO){
+ M[0] = divide_by_sk(M[0],1);
+ M[1] = divide_by_sk(M[1],1);
+ }
+ while (fabs(M[0].at0())<ZERO && fabs(M[1].at0())<ZERO){
+ M[0] = divide_by_t0k(M[0],1);
+ M[1] = divide_by_t0k(M[1],1);
+ }
+ while (fabs(M[0].at1())<ZERO && fabs(M[1].at1())<ZERO){
+ M[0] = divide_by_t1k(M[0],1);
+ M[1] = divide_by_t1k(M[1],1);
+ }
+ return M;
+}
+
+//=================================================================
+//TODO: what's this for?!?!
+Piecewise<D2<SBasis> >
+Geom::cutAtRoots(Piecewise<D2<SBasis> > const &M, double ZERO){
+ vector<double> rts;
+ for (unsigned i=0; i<M.size(); i++){
+ vector<double> seg_rts = roots((M.segs[i])[0]);
+ seg_rts = vect_intersect(seg_rts, roots((M.segs[i])[1]), ZERO);
+ Linear mapToDom = Linear(M.cuts[i],M.cuts[i+1]);
+ for (unsigned r=0; r<seg_rts.size(); r++){
+ seg_rts[r]= mapToDom(seg_rts[r]);
+ }
+ rts.insert(rts.end(),seg_rts.begin(),seg_rts.end());
+ }
+ return partition(M,rts);
+}
+
+Piecewise<SBasis>
+Geom::atan2(Piecewise<D2<SBasis> > const &vect, double tol, unsigned order){
+ Piecewise<SBasis> result;
+ Piecewise<D2<SBasis> > v = cutAtRoots(vect);
+ result.cuts.push_back(v.cuts.front());
+ for (unsigned i=0; i<v.size(); i++){
+
+ D2<SBasis> vi = RescaleForNonVanishingEnds(v.segs[i]);
+ SBasis x=vi[0], y=vi[1];
+ Piecewise<SBasis> angle;
+ angle = divide (x*derivative(y)-y*derivative(x), x*x+y*y, tol, order);
+
+ //TODO: I don't understand this - sign.
+ angle = integral(-angle);
+ Point vi0 = vi.at0();
+ angle += -std::atan2(vi0[1],vi0[0]) - angle[0].at0();
+ //TODO: deal with 2*pi jumps form one seg to the other...
+ //TODO: not exact at t=1 because of the integral.
+ //TODO: force continuity?
+
+ angle.setDomain(Interval(v.cuts[i],v.cuts[i+1]));
+ result.concat(angle);
+ }
+ return result;
+}
+Piecewise<SBasis>
+Geom::atan2(D2<SBasis> const &vect, double tol, unsigned order){
+ return atan2(Piecewise<D2<SBasis> >(vect),tol,order);
+}
+
+//unitVector(x,y) is computed as (b,-a) where a and b are solutions of:
+// ax+by=0 (eqn1) and a^2+b^2=1 (eqn2)
+Piecewise<D2<SBasis> >
+Geom::unitVector(D2<SBasis> const &V_in, double tol, unsigned order){
+ D2<SBasis> V = RescaleForNonVanishingEnds(V_in);
+ if (V[0].empty() && V[1].empty())
+ return Piecewise<D2<SBasis> >(D2<SBasis>(Linear(1),SBasis()));
+ SBasis x = V[0], y = V[1], a, b;
+ SBasis r_eqn1, r_eqn2;
+
+ Point v0 = unit_vector(V.at0());
+ Point v1 = unit_vector(V.at1());
+ a.push_back(Linear(-v0[1],-v1[1]));
+ b.push_back(Linear( v0[0], v1[0]));
+
+ r_eqn1 = -(a*x+b*y);
+ r_eqn2 = Linear(1.)-(a*a+b*b);
+
+ for (unsigned k=1; k<=order; k++){
+ double r0 = (k<r_eqn1.size())? r_eqn1.at(k).at0() : 0;
+ double r1 = (k<r_eqn1.size())? r_eqn1.at(k).at1() : 0;
+ double rr0 = (k<r_eqn2.size())? r_eqn2.at(k).at0() : 0;
+ double rr1 = (k<r_eqn2.size())? r_eqn2.at(k).at1() : 0;
+ double a0,a1,b0,b1;// coeffs in a[k] and b[k]
+
+ //the equations to solve at this point are:
+ // a0*x(0)+b0*y(0)=r0 & 2*a0*a(0)+2*b0*b(0)=rr0
+ //and
+ // a1*x(1)+b1*y(1)=r1 & 2*a1*a(1)+2*b1*b(1)=rr1
+ a0 = r0/dot(v0,V(0))*v0[0]-rr0/2*v0[1];
+ b0 = r0/dot(v0,V(0))*v0[1]+rr0/2*v0[0];
+ a1 = r1/dot(v1,V(1))*v1[0]-rr1/2*v1[1];
+ b1 = r1/dot(v1,V(1))*v1[1]+rr1/2*v1[0];
+
+ a.push_back(Linear(a0,a1));
+ b.push_back(Linear(b0,b1));
+ //TODO: use "incremental" rather than explicit formulas.
+ r_eqn1 = -(a*x+b*y);
+ r_eqn2 = Linear(1)-(a*a+b*b);
+ }
+
+ //our candidat is:
+ D2<SBasis> unitV;
+ unitV[0] = b;
+ unitV[1] = -a;
+
+ //is it good?
+ double rel_tol = max(1.,max(V_in[0].tailError(0),V_in[1].tailError(0)))*tol;
+
+ if (r_eqn1.tailError(order)>rel_tol || r_eqn2.tailError(order)>tol){
+ //if not: subdivide and concat results.
+ Piecewise<D2<SBasis> > unitV0, unitV1;
+ unitV0 = unitVector(compose(V,Linear(0,.5)),tol,order);
+ unitV1 = unitVector(compose(V,Linear(.5,1)),tol,order);
+ unitV0.setDomain(Interval(0.,.5));
+ unitV1.setDomain(Interval(.5,1.));
+ unitV0.concat(unitV1);
+ return(unitV0);
+ }else{
+ //if yes: return it as pw.
+ Piecewise<D2<SBasis> > result;
+ result=(Piecewise<D2<SBasis> >)unitV;
+ return result;
+ }
+}
+
+Piecewise<D2<SBasis> >
+Geom::unitVector(Piecewise<D2<SBasis> > const &V, double tol, unsigned order){
+ Piecewise<D2<SBasis> > result;
+ Piecewise<D2<SBasis> > VV = cutAtRoots(V);
+ result.cuts.push_back(VV.cuts.front());
+ for (unsigned i=0; i<VV.size(); i++){
+ Piecewise<D2<SBasis> > unit_seg;
+ unit_seg = unitVector(VV.segs[i],tol, order);
+ unit_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
+ result.concat(unit_seg);
+ }
+ return result;
+}
+
+Piecewise<SBasis>
+Geom::arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol){
+ Piecewise<D2<SBasis> > dM = derivative(M);
+ Piecewise<SBasis> dMlength = sqrt(dot(dM,dM),tol,3);
+ Piecewise<SBasis> length = integral(dMlength);
+ length-=length.segs.front().at0();
+ return length;
+}
+Piecewise<SBasis>
+Geom::arcLengthSb(D2<SBasis> const &M, double tol){
+ return arcLengthSb(Piecewise<D2<SBasis> >(M), tol);
+}
+
+double
+Geom::length(D2<SBasis> const &M,
+ double tol){
+ Piecewise<SBasis> length = arcLengthSb(M, tol);
+ return length.segs.back().at1();
+}
+double
+Geom::length(Piecewise<D2<SBasis> > const &M,
+ double tol){
+ Piecewise<SBasis> length = arcLengthSb(M, tol);
+ return length.segs.back().at1();
+}
+
+
+// incomplete.
+Piecewise<SBasis>
+Geom::curvature(D2<SBasis> const &M, double tol) {
+ D2<SBasis> dM=derivative(M);
+ Piecewise<SBasis> result;
+ Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
+ Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
+ Piecewise<SBasis> k = cross(derivative(unitv),unitv);
+ k = divide(k,dMlength,tol,3);
+ return(k);
+}
+
+Piecewise<SBasis>
+Geom::curvature(Piecewise<D2<SBasis> > const &V, double tol){
+ Piecewise<SBasis> result;
+ Piecewise<D2<SBasis> > VV = cutAtRoots(V);
+ result.cuts.push_back(VV.cuts.front());
+ for (unsigned i=0; i<VV.size(); i++){
+ Piecewise<SBasis> curv_seg;
+ curv_seg = curvature(VV.segs[i],tol);
+ curv_seg.setDomain(Interval(VV.cuts[i],VV.cuts[i+1]));
+ result.concat(curv_seg);
+ }
+ return result;
+}
+
+//=================================================================
+
+Piecewise<D2<SBasis> >
+Geom::arc_length_parametrization(D2<SBasis> const &M,
+ unsigned order,
+ double tol){
+ Piecewise<D2<SBasis> > u;
+ u.push_cut(0);
+
+ Piecewise<SBasis> s = arcLengthSb(Piecewise<D2<SBasis> >(M),tol);
+ for (unsigned i=0; i < s.size();i++){
+ double t0=s.cuts[i],t1=s.cuts[i+1];
+ D2<SBasis> sub_M = compose(M,Linear(t0,t1));
+ D2<SBasis> sub_u;
+ for (unsigned dim=0;dim<2;dim++){
+ SBasis sub_s = s.segs[i];
+ sub_s-=sub_s.at0();
+ sub_s/=sub_s.at1();
+ sub_u[dim]=compose_inverse(sub_M[dim],sub_s, order, tol);
+ }
+ u.push(sub_u,s(t1));
+ }
+ return u;
+}
+
+Piecewise<D2<SBasis> >
+Geom::arc_length_parametrization(Piecewise<D2<SBasis> > const &M,
+ unsigned order,
+ double tol){
+ Piecewise<D2<SBasis> > result;
+ for (unsigned i=0; i<M.size(); i++ ){
+ Piecewise<D2<SBasis> > uniform_seg=arc_length_parametrization(M[i],order,tol);
+ result.concat(uniform_seg);
+ }
+ return(result);
+}
+
+/** centroid using sbasis integration.
+ * This approach uses green's theorem to compute the area and centroid using integrals. For curved
+ * shapes this is much faster than converting to polyline.
+
+ * Returned values:
+ 0 for normal execution;
+ 2 if area is zero, meaning centroid is meaningless.
+
+ * Copyright Nathan Hurst 2006
+ */
+
+unsigned Geom::centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area) {
+ Point centroid_tmp(0,0);
+ double atmp = 0;
+ for(unsigned i = 0; i < p.size(); i++) {
+ SBasis curl = dot(p[i], rot90(derivative(p[i])));
+ SBasis A = integral(curl);
+ D2<SBasis> C = integral(multiply(curl, p[i]));
+ atmp += A.at1() - A.at0();
+ centroid_tmp += C.at1()- C.at0(); // first moment.
+ }
+// join ends
+ centroid_tmp *= 2;
+ Point final = p[p.size()].at1(), initial = p[0].at0();
+ const double ai = cross(final, initial);
+ atmp += ai;
+ centroid_tmp += (final + initial)*ai; // first moment.
+
+ area = atmp / 2;
+ if (atmp != 0) {
+ centroid = centroid_tmp / (3 * atmp);
+ return 0;
+ }
+ return 2;
+}
+
+//}; // namespace
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/sbasis-geometric.h b/src/2geom/sbasis-geometric.h
new file mode 100644
index 000000000..49f371512
--- /dev/null
+++ b/src/2geom/sbasis-geometric.h
@@ -0,0 +1,83 @@
+#ifndef _SBASIS_GEOMETRIC
+#define _SBASIS_GEOMETRIC
+#include "d2.h"
+#include "piecewise.h"
+#include <vector>
+
+/** two-dimensional geometric operators.
+ * Copyright 2007, JFBarraud
+ * Copyright 2007, njh
+ *
+ * These operators are built on a more 'polynomially robust'
+ * transformation to map a function that takes a [0,1] parameter to a
+ * 2d vector into a function that takes the same [0,1] parameter to a
+ * unit vector with the same direction.
+ *
+ * Rather that using (X/sqrt(X))(t) which involves two unstable
+ * operations, sqrt and divide, this approach forms a curve directly
+ * from the various tangent directions at each end (angular jet). As
+ * a result, the final path has a convergence behaviour derived from
+ * that of the sin and cos series. -- njh
+ */
+
+namespace Geom{
+
+Piecewise<D2<SBasis> >
+cutAtRoots(Piecewise<D2<SBasis> > const &M, double tol=1e-4);
+
+Piecewise<SBasis>
+atan2(D2<SBasis> const &vect,
+ double tol=.01, unsigned order=3);
+
+Piecewise<SBasis>
+atan2(Piecewise<D2<SBasis> >const &vect,
+ double tol=.01, unsigned order=3);
+
+Piecewise<D2<SBasis> >
+unitVector(D2<SBasis> const &vect,
+ double tol=.01, unsigned order=3);
+Piecewise<D2<SBasis> >
+unitVector(Piecewise<D2<SBasis> > const &vect,
+ double tol=.01, unsigned order=3);
+
+// Piecewise<D2<SBasis> >
+// uniform_speed(D2<SBasis> const M,
+// double tol=.1);
+
+Piecewise<SBasis> curvature( D2<SBasis> const &M, double tol=.01);
+Piecewise<SBasis> curvature(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+Piecewise<SBasis> arcLengthSb( D2<SBasis> const &M, double tol=.01);
+Piecewise<SBasis> arcLengthSb(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+double length( D2<SBasis> const &M, double tol=.01);
+double length(Piecewise<D2<SBasis> > const &M, double tol=.01);
+
+Piecewise<D2<SBasis> >
+arc_length_parametrization(D2<SBasis> const &M,
+ unsigned order=3,
+ double tol=.01);
+Piecewise<D2<SBasis> >
+arc_length_parametrization(Piecewise<D2<SBasis> > const &M,
+ unsigned order=3,
+ double tol=.01);
+
+
+unsigned centroid(Piecewise<D2<SBasis> > const &p, Point& centroid, double &area);
+
+};
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+
+
diff --git a/src/2geom/sbasis-math.cpp b/src/2geom/sbasis-math.cpp
new file mode 100644
index 000000000..d88c26832
--- /dev/null
+++ b/src/2geom/sbasis-math.cpp
@@ -0,0 +1,289 @@
+/*
+ * sbasis-math.cpp - some std functions to work with (pw)s-basis
+ *
+ * Authors:
+ * Jean-Francois Barraud
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+//this a first try to define sqrt, cos, sin, etc...
+//TODO: define a truncated compose(sb,sb, order) and extend it to pw<sb>.
+//TODO: in all these functions, compute 'order' according to 'tol'.
+
+#include "sbasis-math.h"
+//#define ZERO 1e-3
+
+
+namespace Geom {
+
+
+#include <stdio.h>
+#include <math.h>
+
+//-|x|-----------------------------------------------------------------------
+Piecewise<SBasis> abs(SBasis const &f){
+ return abs(Piecewise<SBasis>(f));
+}
+Piecewise<SBasis> abs(Piecewise<SBasis> const &f){
+ Piecewise<SBasis> absf=partition(f,roots(f));
+ for (unsigned i=0; i<absf.size(); i++){
+ if (absf.segs[i](.5)<0) absf.segs[i]*=-1;
+ }
+ return absf;
+}
+
+//-maxSb(x,y), minSb(x,y)--------------------------------------------------------
+Piecewise<SBasis> maxSb( SBasis const &f, SBasis const &g){
+ return maxSb(Piecewise<SBasis>(f),Piecewise<SBasis>(g));
+}
+Piecewise<SBasis> maxSb(Piecewise<SBasis> const &f, SBasis const &g){
+ return maxSb(f,Piecewise<SBasis>(g));
+}
+Piecewise<SBasis> maxSb( SBasis const &f, Piecewise<SBasis> const &g){
+ return maxSb(Piecewise<SBasis>(f),g);
+}
+Piecewise<SBasis> maxSb(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){
+ Piecewise<SBasis> maxSb=partition(f,roots(f-g));
+ Piecewise<SBasis> gg =partition(g,maxSb.cuts);
+ maxSb = partition(maxSb,gg.cuts);
+ for (unsigned i=0; i<maxSb.size(); i++){
+ if (maxSb.segs[i](.5)<gg.segs[i](.5)) maxSb.segs[i]=gg.segs[i];
+ }
+ return maxSb;
+}
+
+Piecewise<SBasis>
+minSb( SBasis const &f, SBasis const &g){ return -maxSb(-f,-g); }
+Piecewise<SBasis>
+minSb(Piecewise<SBasis> const &f, SBasis const &g){ return -maxSb(-f,-g); }
+Piecewise<SBasis>
+minSb( SBasis const &f, Piecewise<SBasis> const &g){ return -maxSb(-f,-g); }
+Piecewise<SBasis>
+minSb(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g){ return -maxSb(-f,-g); }
+
+
+//-sign(x)---------------------------------------------------------------
+Piecewise<SBasis> signSb(SBasis const &f){
+ return signSb(Piecewise<SBasis>(f));
+}
+Piecewise<SBasis> signSb(Piecewise<SBasis> const &f){
+ Piecewise<SBasis> sign=partition(f,roots(f));
+ for (unsigned i=0; i<sign.size(); i++){
+ sign.segs[i] = (sign.segs[i](.5)<0)? Linear(-1.):Linear(1.);
+ }
+ return sign;
+}
+
+//-Sqrt----------------------------------------------------------
+static Piecewise<SBasis> sqrt_internal(SBasis const &f,
+ double tol,
+ int order){
+ SBasis sqrtf;
+ if(f.isZero() || order == 0){
+ return Piecewise<SBasis>(sqrtf);
+ }
+ if (f.at0()<-tol*tol && f.at1()<-tol*tol){
+ return sqrt_internal(-f,tol,order);
+ }else if (f.at0()>tol*tol && f.at1()>tol*tol){
+ sqrtf.resize(order+1, Linear(0,0));
+ sqrtf[0] = Linear(std::sqrt(f[0][0]), std::sqrt(f[0][1]));
+ SBasis r = f - multiply(sqrtf, sqrtf); // remainder
+ for(unsigned i = 1; int(i) <= order and i<r.size(); i++) {
+ Linear ci(r[i][0]/(2*sqrtf[0][0]), r[i][1]/(2*sqrtf[0][1]));
+ SBasis cisi = shift(ci, i);
+ r -= multiply(shift((sqrtf*2 + cisi), i), SBasis(ci));
+ r.truncate(order+1);
+ sqrtf[i] = ci;
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+ }else{
+ sqrtf = Linear(std::sqrt(fabs(f.at0())), std::sqrt(fabs(f.at1())));
+ }
+
+ double err = (f - multiply(sqrtf, sqrtf)).tailError(0);
+ if (err<tol){
+ return Piecewise<SBasis>(sqrtf);
+ }
+
+ Piecewise<SBasis> sqrtf0,sqrtf1;
+ sqrtf0 = sqrt_internal(compose(f,Linear(0.,.5)),tol,order);
+ sqrtf1 = sqrt_internal(compose(f,Linear(.5,1.)),tol,order);
+ sqrtf0.setDomain(Interval(0.,.5));
+ sqrtf1.setDomain(Interval(.5,1.));
+ sqrtf0.concat(sqrtf1);
+ return sqrtf0;
+}
+
+Piecewise<SBasis> sqrt(SBasis const &f, double tol, int order){
+ return sqrt(maxSb(f,Linear(tol*tol)),tol,order);
+}
+
+Piecewise<SBasis> sqrt(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> result;
+ Piecewise<SBasis> ff=maxSb(f,Linear(tol*tol));
+
+ for (unsigned i=0; i<ff.size(); i++){
+ Piecewise<SBasis> sqrtfi = sqrt_internal(ff.segs[i],tol,order);
+ sqrtfi.setDomain(Interval(ff.cuts[i],ff.cuts[i+1]));
+ result.concat(sqrtfi);
+ }
+ return result;
+}
+
+//-Yet another sin/cos--------------------------------------------------------------
+
+Piecewise<SBasis> sin( SBasis const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));}
+Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));}
+
+Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> result;
+ for (unsigned i=0; i<f.size(); i++){
+ Piecewise<SBasis> cosfi = cos(f.segs[i],tol,order);
+ cosfi.setDomain(Interval(f.cuts[i],f.cuts[i+1]));
+ result.concat(cosfi);
+ }
+ return result;
+}
+
+Piecewise<SBasis> cos( SBasis const &f, double tol, int order){
+ double alpha = (f.at0()+f.at1())/2.;
+ SBasis x = f-alpha;
+ double d = x.tailError(0),err=1;
+ //estimate cos(x)-sum_0^order (-1)^k x^2k/2k! by the first neglicted term
+ for (int i=1; i<=2*order; i++) err*=d/i;
+
+ if (err<tol){
+ SBasis xk=Linear(1), c=Linear(1), s=Linear(0);
+ for (int k=1; k<=2*order; k+=2){
+ xk*=x/k;
+ //take also truncature errors into account...
+ err+=xk.tailError(order);
+ xk.truncate(order);
+ s+=xk;
+ xk*=-x/(k+1);
+ //take also truncature errors into account...
+ err+=xk.tailError(order);
+ xk.truncate(order);
+ c+=xk;
+ }
+ if (err<tol){
+ return Piecewise<SBasis>(std::cos(alpha)*c-std::sin(alpha)*s);
+ }
+ }
+ Piecewise<SBasis> c0,c1;
+ c0 = cos(compose(f,Linear(0.,.5)),tol,order);
+ c1 = cos(compose(f,Linear(.5,1.)),tol,order);
+ c0.setDomain(Interval(0.,.5));
+ c1.setDomain(Interval(.5,1.));
+ c0.concat(c1);
+ return c0;
+}
+
+//--1/x------------------------------------------------------------
+//TODO: this implementation is just wrong. Remove or redo!
+
+void truncateResult(Piecewise<SBasis> &f, int order){
+ if (order>=0){
+ for (unsigned k=0; k<f.segs.size(); k++){
+ f.segs[k].truncate(order);
+ }
+ }
+}
+
+Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol){
+ Piecewise<SBasis> reciprocal_fn;
+ //TODO: deduce R from tol...
+ double R=2.;
+ SBasis reciprocal1_R=reciprocal(Linear(1,R),3);
+ double a=range.min(), b=range.max();
+ if (a*b<0){
+ b=std::max(fabs(a),fabs(b));
+ a=0;
+ }else if (b<0){
+ a=-range.max();
+ b=-range.min();
+ }
+
+ if (a<=tol){
+ reciprocal_fn.push_cut(0);
+ int i0=(int) floor(log(tol)/log(R));
+ a=pow(R,i0);
+ reciprocal_fn.push(Linear(1/a),a);
+ }else{
+ int i0=(int) floor(log(a)/log(R));
+ a=pow(R,i0);
+ reciprocal_fn.cuts.push_back(a);
+ }
+
+ while (a<b){
+ reciprocal_fn.push(reciprocal1_R/a,R*a);
+ a*=R;
+ }
+ if (range.min()<0 || range.max()<0){
+ Piecewise<SBasis>reciprocal_fn_neg;
+ //TODO: define reverse(pw<sb>);
+ reciprocal_fn_neg.cuts.push_back(-reciprocal_fn.cuts.back());
+ for (unsigned i=0; i<reciprocal_fn.size(); i++){
+ int idx=reciprocal_fn.segs.size()-1-i;
+ reciprocal_fn_neg.push_seg(-reverse(reciprocal_fn.segs.at(idx)));
+ reciprocal_fn_neg.push_cut(-reciprocal_fn.cuts.at(idx));
+ }
+ if (range.max()>0){
+ reciprocal_fn_neg.concat(reciprocal_fn);
+ }
+ reciprocal_fn=reciprocal_fn_neg;
+ }
+
+ return(reciprocal_fn);
+}
+
+Piecewise<SBasis> reciprocal(SBasis const &f, double tol, int order){
+ Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(bounds_fast(f), tol);
+ Piecewise<SBasis> result=compose(reciprocal_fn,f);
+ truncateResult(result,order);
+ return(result);
+}
+Piecewise<SBasis> reciprocal(Piecewise<SBasis> const &f, double tol, int order){
+ Piecewise<SBasis> reciprocal_fn=reciprocalOnDomain(bounds_fast(f), tol);
+ Piecewise<SBasis> result=compose(reciprocal_fn,f);
+ truncateResult(result,order);
+ return(result);
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
diff --git a/src/2geom/sbasis-math.h b/src/2geom/sbasis-math.h
new file mode 100644
index 000000000..529641068
--- /dev/null
+++ b/src/2geom/sbasis-math.h
@@ -0,0 +1,92 @@
+/*
+ * sbasis-math.h - some std functions to work with (pw)s-basis
+ *
+ * Authors:
+ * Jean-Francois Barraud
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+//this a first try to define sqrt, cos, sin, etc...
+//TODO: define a truncated compose(sb,sb, order) and extend it to pw<sb>.
+//TODO: in all these functions, compute 'order' according to 'tol'.
+//TODO: use template to define the pw version automatically from the sb version?
+
+#ifndef SEEN_GEOM_SB_CALCULS_H
+#define SEEN_GEOM_SB_CALCULS_H
+
+
+#include "sbasis.h"
+#include "piecewise.h"
+
+namespace Geom{
+//-|x|---------------------------------------------------------------
+Piecewise<SBasis> abs( SBasis const &f);
+Piecewise<SBasis> abs(Piecewise<SBasis>const &f);
+
+//- max(f,g), min(f,g) ----------------------------------------------
+Piecewise<SBasis> maxSb( SBasis const &f, SBasis const &g);
+Piecewise<SBasis> maxSb(Piecewise<SBasis> const &f, SBasis const &g);
+Piecewise<SBasis> maxSb( SBasis const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> maxSb(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> minSb( SBasis const &f, SBasis const &g);
+Piecewise<SBasis> minSb(Piecewise<SBasis> const &f, SBasis const &g);
+Piecewise<SBasis> minSb( SBasis const &f, Piecewise<SBasis> const &g);
+Piecewise<SBasis> minSb(Piecewise<SBasis> const &f, Piecewise<SBasis> const &g);
+
+//-sign(x)---------------------------------------------------------------
+Piecewise<SBasis> signSb( SBasis const &f);
+Piecewise<SBasis> signSb(Piecewise<SBasis>const &f);
+
+//-Sqrt---------------------------------------------------------------
+Piecewise<SBasis> sqrt( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sqrt(Piecewise<SBasis>const &f, double tol=1e-3, int order=3);
+
+//-sin/cos--------------------------------------------------------------
+Piecewise<SBasis> cos( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> cos(Piecewise<SBasis> const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sin( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> sin(Piecewise<SBasis> const &f, double tol=1e-3, int order=3);
+
+//--1/x------------------------------------------------------------
+//TODO: change this...
+Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol=1e-3);
+Piecewise<SBasis> reciprocal( SBasis const &f, double tol=1e-3, int order=3);
+Piecewise<SBasis> reciprocal(Piecewise<SBasis>const &f, double tol=1e-3, int order=3);
+
+}
+
+#endif //SEEN_GEOM_PW_SB_CALCULUS_H
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
diff --git a/src/2geom/sbasis-poly.cpp b/src/2geom/sbasis-poly.cpp
new file mode 100644
index 000000000..e0fa828f9
--- /dev/null
+++ b/src/2geom/sbasis-poly.cpp
@@ -0,0 +1,45 @@
+#include "sbasis-poly.h"
+
+namespace Geom{
+
+SBasis poly_to_sbasis(Poly const & p) {
+ SBasis x = Linear(0, 1);
+ SBasis r;
+
+ for(int i = p.size()-1; i >= 0; i--) {
+ r = SBasis(Linear(p[i], p[i])) + multiply(x, r);
+ }
+ r.normalize();
+ return r;
+
+}
+
+Poly sbasis_to_poly(SBasis const & sb) {
+ Poly S; // (1-x)x = -1*x^2 + 1*x + 0
+ Poly A, B;
+ B.push_back(0);
+ B.push_back(1);
+ A.push_back(1);
+ A.push_back(-1);
+ S = A*B;
+ Poly r;
+
+ for(int i = sb.size()-1; i >= 0; i--) {
+ r = S*r + sb[i][0]*A + sb[i][1]*B;
+ }
+ r.normalize();
+ return r;
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/sbasis-poly.h b/src/2geom/sbasis-poly.h
new file mode 100644
index 000000000..4a8aa1cd8
--- /dev/null
+++ b/src/2geom/sbasis-poly.h
@@ -0,0 +1,29 @@
+#ifndef _SBASIS_TO_POLY
+#define _SBASIS_TO_POLY
+
+#include "poly.h"
+#include "sbasis.h"
+
+/*** Conversion between SBasis and Poly. Not recommended for general
+ * use due to instability.
+ */
+
+namespace Geom{
+
+SBasis poly_to_sbasis(Poly const & p);
+Poly sbasis_to_poly(SBasis const & s);
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+
+#endif
diff --git a/src/2geom/sbasis-roots.cpp b/src/2geom/sbasis-roots.cpp
new file mode 100644
index 000000000..ad2dfbda4
--- /dev/null
+++ b/src/2geom/sbasis-roots.cpp
@@ -0,0 +1,352 @@
+/** root finding for sbasis functions.
+ * Copyright 2006 N Hurst
+ * Copyright 2007 JF Barraud
+ *
+ * It is more efficient to find roots of f(t) = c_0, c_1, ... all at once, rather than iterating.
+ *
+ * Todo/think about:
+ * multi-roots using bernstein method, one approach would be:
+ sort c
+ take median and find roots of that
+ whenever a segment lies entirely on one side of the median,
+ find the median of the half and recurse.
+
+ in essence we are implementing quicksort on a continuous function
+
+ * the gsl poly roots finder is faster than bernstein too, but we don't use it for 3 reasons:
+
+ a) it requires convertion to poly, which is numerically unstable
+
+ b) it requires gsl (which is currently not a dependency, and would bring in a whole slew of unrelated stuff)
+
+ c) it finds all roots, even complex ones. We don't want to accidently treat a nearly real root as a real root
+
+From memory gsl poly roots was about 10 times faster than bernstein in the case where all the roots
+are in [0,1] for polys of order 5. I spent some time working out whether eigenvalue root finding
+could be done directly in sbasis space, but the maths was too hard for me. -- njh
+
+jfbarraud: eigenvalue root finding could be done directly in sbasis space ?
+
+njh: I don't know, I think it should. You would make a matrix whose characteristic polynomial was
+correct, but do it by putting the sbasis terms in the right spots in the matrix. normal eigenvalue
+root finding makes a matrix that is a diagonal + a row along the top. This matrix has the property
+that its characteristic poly is just the poly whose coefficients are along the top row.
+
+Now an sbasis function is a linear combination of the poly coeffs. So it seems to me that you
+should be able to put the sbasis coeffs directly into a matrix in the right spots so that the
+characteristic poly is the sbasis. You'll still have problems b) and c).
+
+We might be able to lift an eigenvalue solver and include that directly into 2geom. Eigenvalues
+also allow you to find intersections of multiple curves but require solving n*m x n*m matrices.
+
+ **/
+
+#include <cmath>
+#include <map>
+
+#include "sbasis.h"
+#include "sbasis-to-bezier.h"
+#include "solver.h"
+
+using namespace std;
+
+namespace Geom{
+
+Interval bounds_exact(SBasis const &a) {
+ Interval result = Interval(a.at0(), a.at1());
+ SBasis df = derivative(a);
+ vector<double>extrema = roots(df);
+ for (unsigned i=0; i<extrema.size(); i++){
+ result.extendTo(a(extrema[i]));
+ }
+ return result;
+}
+
+Interval bounds_fast(const SBasis &sb, int order) {
+ Interval res;
+ for(int j = sb.size()-1; j>=order; j--) {
+ double a=sb[j][0];
+ double b=sb[j][1];
+
+ double t, v;
+ v = res[0];
+ if (v<0) t = ((b-a)/v+1)*0.5;
+ if (v>=0 || t<0 || t>1) {
+ res[0] = std::min(a,b);
+ }else{
+ res[0]=lerp(t, a+v*t, b);
+ }
+
+ v = res[1];
+ if (v>0) t = ((b-a)/v+1)*0.5;
+ if (v<=0 || t<0 || t>1) {
+ res[1] = std::max(a,b);
+ }else{
+ res[1]=lerp(t, a+v*t, b);
+ }
+ }
+ if (order>0) res*=pow(.25,order);
+ return res;
+}
+
+Interval bounds_local(const SBasis &sb, const Interval &i, int order) {
+ double t0=i.min(), t1=i.max(), lo=0., hi=0.;
+ for(int j = sb.size()-1; j>=order; j--) {
+ double a=sb[j][0];
+ double b=sb[j][1];
+
+ double t;
+ if (lo<0) t = ((b-a)/lo+1)*0.5;
+ if (lo>=0 || t<t0 || t>t1) {
+ lo = std::min(a*(1-t0)+b*t0+lo*t0*(1-t0),a*(1-t1)+b*t1+lo*t1*(1-t1));
+ }else{
+ lo = lerp(t, a+lo*t, b);
+ }
+
+ if (hi>0) t = ((b-a)/hi+1)*0.5;
+ if (hi<=0 || t<t0 || t>t1) {
+ hi = std::max(a*(1-t0)+b*t0+hi*t0*(1-t0),a*(1-t1)+b*t1+hi*t1*(1-t1));
+ }else{
+ hi = lerp(t, a+hi*t, b);
+ }
+ }
+ Interval res = Interval(lo,hi);
+ if (order>0) res*=pow(.25,order);
+ return res;
+}
+
+//-- multi_roots ------------------------------------
+// goal: solve f(t)=c for several c at once.
+/* algo: -compute f at both ends of the given segment [a,b].
+ -compute bounds m<df(t)<M for df on the segment.
+ let c and C be the levels below and above f(a):
+ going from f(a) down to c with slope m takes at least time (f(a)-c)/m
+ going from f(a) up to C with slope M takes at least time (C-f(a))/M
+ From this we conclude there are no roots before a'=a+min((f(a)-c)/m,(C-f(a))/M).
+ Do the same for b: compute some b' such that there are no roots in (b',b].
+ -if [a',b'] is not empty, repeat the process with [a',(a'+b')/2] and [(a'+b')/2,b'].
+ unfortunately, extra care is needed about rounding errors, and also to avoid the repetition of roots,
+ making things tricky and unpleasant...
+*/
+//TODO: Make sure the code is "rounding-errors proof" and take care about repetition of roots!
+
+
+static int upper_level(vector<double> const &levels,double x,double tol=0.){
+ return(upper_bound(levels.begin(),levels.end(),x-tol)-levels.begin());
+}
+
+static void multi_roots_internal(SBasis const &f,
+ SBasis const &df,
+ std::vector<double> const &levels,
+ std::vector<std::vector<double> > &roots,
+ double htol,
+ double vtol,
+ double a,
+ double fa,
+ double b,
+ double fb){
+
+ if (f.size()==0){
+ int idx;
+ idx=upper_level(levels,0,vtol);
+ if (idx<(int)levels.size()&&fabs(levels.at(idx))<=vtol){
+ roots[idx].push_back(a);
+ roots[idx].push_back(b);
+ }
+ return;
+ }
+////usefull?
+// if (f.size()==1){
+// int idxa=upper_level(levels,fa);
+// int idxb=upper_level(levels,fb);
+// if (fa==fb){
+// if (fa==levels[idxa]){
+// roots[a]=idxa;
+// roots[b]=idxa;
+// }
+// return;
+// }
+// int idx_min=std::min(idxa,idxb);
+// int idx_max=std::max(idxa,idxb);
+// if (idx_max==levels.size()) idx_max-=1;
+// for(int i=idx_min;i<=idx_max; i++){
+// double t=a+(b-a)*(levels[i]-fa)/(fb-fa);
+// if(a<t&&t<b) roots[t]=i;
+// }
+// return;
+// }
+ if ((b-a)<htol){
+ //TODO: use different tol for t and f ?
+ //TODO: unsigned idx ? (remove int casts when fixed)
+ int idx=std::min(upper_level(levels,fa,vtol),upper_level(levels,fb,vtol));
+ if (idx==(int)levels.size()) idx-=1;
+ double c=levels.at(idx);
+ if((fa-c)*(fb-c)<=0||fabs(fa-c)<vtol||fabs(fb-c)<vtol){
+ roots[idx].push_back((a+b)/2);
+ }
+ return;
+ }
+
+ int idxa=upper_level(levels,fa,vtol);
+ int idxb=upper_level(levels,fb,vtol);
+
+ Interval bs = bounds_local(df,Interval(a,b));
+
+ //first times when a level (higher or lower) can be reached from a or b.
+ double ta_hi,tb_hi,ta_lo,tb_lo;
+ ta_hi=ta_lo=b+1;//default values => no root there.
+ tb_hi=tb_lo=a-1;//default values => no root there.
+
+ if (idxa<(int)levels.size() && fabs(fa-levels.at(idxa))<vtol){//a can be considered a root.
+ //ta_hi=ta_lo=a;
+ roots[idxa].push_back(a);
+ ta_hi=ta_lo=a+htol;
+ }else{
+ if (bs.max()>0 && idxa<(int)levels.size())
+ ta_hi=a+(levels.at(idxa )-fa)/bs.max();
+ if (bs.min()<0 && idxa>0)
+ ta_lo=a+(levels.at(idxa-1)-fa)/bs.min();
+ }
+ if (idxb<(int)levels.size() && fabs(fb-levels.at(idxb))<vtol){//b can be considered a root.
+ //tb_hi=tb_lo=b;
+ roots[idxb].push_back(b);
+ tb_hi=tb_lo=b-htol;
+ }else{
+ if (bs.min()<0 && idxb<(int)levels.size())
+ tb_hi=b+(levels.at(idxb )-fb)/bs.min();
+ if (bs.max()>0 && idxb>0)
+ tb_lo=b+(levels.at(idxb-1)-fb)/bs.max();
+ }
+
+ double t0,t1;
+ t0=std::min(ta_hi,ta_lo);
+ t1=std::max(tb_hi,tb_lo);
+ //hum, rounding errors frighten me! so I add this +tol...
+ if (t0>t1+htol) return;//no root here.
+
+ if (fabs(t1-t0)<htol){
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t0,f(t0),t1,f(t1));
+ }else{
+ double t,t_left,t_right,ft,ft_left,ft_right;
+ t_left =t_right =t =(t0+t1)/2;
+ ft_left=ft_right=ft=f(t);
+ int idx=upper_level(levels,ft,vtol);
+ if (idx<(int)levels.size() && fabs(ft-levels.at(idx))<vtol){//t can be considered a root.
+ roots[idx].push_back(t);
+ //we do not want to count it twice (from the left and from the right)
+ t_left =t-htol/2;
+ t_right=t+htol/2;
+ ft_left =f(t_left);
+ ft_right=f(t_right);
+ }
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t0 ,f(t0) ,t_left,ft_left);
+ multi_roots_internal(f,df,levels,roots,htol,vtol,t_right,ft_right,t1 ,f(t1) );
+ }
+}
+
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol,
+ double vtol,
+ double a,
+ double b){
+
+ std::vector<std::vector<double> > roots(levels.size(), std::vector<double>());
+
+ SBasis df=derivative(f);
+ multi_roots_internal(f,df,levels,roots,htol,vtol,a,f(a),b,f(b));
+
+ return(roots);
+}
+//-------------------------------------
+
+#if 0
+double Laguerre_internal(SBasis const & p,
+ double x0,
+ double tol,
+ bool & quad_root) {
+ double a = 2*tol;
+ double xk = x0;
+ double n = p.size();
+ quad_root = false;
+ while(a > tol) {
+ //std::cout << "xk = " << xk << std::endl;
+ Linear b = p.back();
+ Linear d(0), f(0);
+ double err = fabs(b);
+ double abx = fabs(xk);
+ for(int j = p.size()-2; j >= 0; j--) {
+ f = xk*f + d;
+ d = xk*d + b;
+ b = xk*b + p[j];
+ err = fabs(b) + abx*err;
+ }
+
+ err *= 1e-7; // magic epsilon for convergence, should be computed from tol
+
+ double px = b;
+ if(fabs(b) < err)
+ return xk;
+ //if(std::norm(px) < tol*tol)
+ // return xk;
+ double G = d / px;
+ double H = G*G - f / px;
+
+ //std::cout << "G = " << G << "H = " << H;
+ double radicand = (n - 1)*(n*H-G*G);
+ //assert(radicand.real() > 0);
+ if(radicand < 0)
+ quad_root = true;
+ //std::cout << "radicand = " << radicand << std::endl;
+ if(G.real() < 0) // here we try to maximise the denominator avoiding cancellation
+ a = - std::sqrt(radicand);
+ else
+ a = std::sqrt(radicand);
+ //std::cout << "a = " << a << std::endl;
+ a = n / (a + G);
+ //std::cout << "a = " << a << std::endl;
+ xk -= a;
+ }
+ //std::cout << "xk = " << xk << std::endl;
+ return xk;
+}
+#endif
+
+void subdiv_sbasis(SBasis const & s,
+ std::vector<double> & roots,
+ double left, double right) {
+ Interval bs = bounds_fast(s);
+ if(bs.min() > 0 || bs.max() < 0)
+ return; // no roots here
+ if(s.tailError(1) < 1e-7) {
+ double t = s[0][0] / (s[0][0] - s[0][1]);
+ roots.push_back(left*(1-t) + t*right);
+ return;
+ }
+ double middle = (left + right)/2;
+ subdiv_sbasis(compose(s, Linear(0, 0.5)), roots, left, middle);
+ subdiv_sbasis(compose(s, Linear(0.5, 1.)), roots, middle, right);
+}
+
+// It is faster to use the bernstein root finder for small degree polynomials (<100?.
+
+std::vector<double> roots(SBasis const & s) {
+ if(s.size() == 0) return std::vector<double>();
+ std::vector<double> b = sbasis_to_bezier(s), r;
+
+ find_bernstein_roots(&b[0], b.size()-1, r, 0, 0., 1.);
+ return r;
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/sbasis-to-bezier.cpp b/src/2geom/sbasis-to-bezier.cpp
new file mode 100644
index 000000000..39efc5145
--- /dev/null
+++ b/src/2geom/sbasis-to-bezier.cpp
@@ -0,0 +1,214 @@
+/* From Sanchez-Reyes 1997
+ W_{j,k} = W_{n0j, n-k} = choose(n-2k-1, j-k)choose(2k+1,k)/choose(n,j)
+ for k=0,...,q-1; j = k, ...,n-k-1
+ W_{q,q} = 1 (n even)
+
+This is wrong, it should read
+ W_{j,k} = W_{n0j, n-k} = choose(n-2k-1, j-k)/choose(n,j)
+ for k=0,...,q-1; j = k, ...,n-k-1
+ W_{q,q} = 1 (n even)
+
+*/
+#include "sbasis-to-bezier.h"
+#include "choose.h"
+#include <iostream>
+
+namespace Geom{
+
+double W(unsigned n, unsigned j, unsigned k) {
+ unsigned q = (n+1)/2;
+ if((n & 1) == 0 && j == q && k == q)
+ return 1;
+ if(k > n-k) return W(n, n-j, n-k);
+ assert(!(k < 0));
+ if(k < 0) return 0;
+ assert((k <= q));
+ if(k >= q) return 0;
+ //assert(!(j >= n-k));
+ if(j >= n-k) return 0;
+ //assert(!(j < k));
+ if(j < k) return 0;
+ return choose<double>(n-2*k-1, j-k) /
+ choose<double>(n,j);
+}
+
+// this produces a degree 2q bezier from a degree k sbasis
+std::vector<double>
+sbasis_to_bezier(SBasis const &B, unsigned q) {
+ std::vector<double> result;
+ if(q == 0) {
+ q = B.size();
+ /*if(B.back()[0] == B.back()[1]) {
+ n--;
+ }*/
+ }
+ unsigned n = q*2;
+ result.resize(n, 0);
+ if(q > B.size())
+ q = B.size();
+ n--;
+ for(unsigned k = 0; k < q; k++) {
+ for(unsigned j = 0; j <= n-k; j++) {
+ result[j] += (W(n, j, k)*B[k][0] +
+ W(n, n-j, k)*B[k][1]);
+ }
+ }
+ return result;
+}
+
+// this produces a 2q point bezier from a degree q sbasis
+std::vector<Geom::Point>
+sbasis_to_bezier(D2<SBasis> const &B, unsigned qq) {
+ std::vector<Geom::Point> result;
+ if(qq == 0) {
+ qq = sbasis_size(B);
+ }
+ unsigned n = qq * 2;
+ result.resize(n, Geom::Point(0,0));
+ n--;
+ for(unsigned dim = 0; dim < 2; dim++) {
+ unsigned q = qq;
+ if(q > B[dim].size())
+ q = B[dim].size();
+ for(unsigned k = 0; k < q; k++) {
+ for(unsigned j = 0; j <= n-k; j++) {
+ result[j][dim] += (W(n, j, k)*B[dim][k][0] +
+ W(n, n-j, k)*B[dim][k][1]);
+ }
+ }
+ }
+ return result;
+}
+/*
+template <unsigned order>
+D2<Bezier<order> > sbasis_to_bezier(D2<SBasis> const &B) {
+ return D2<Bezier<order> >(sbasis_to_bezier<order>(B[0]), sbasis_to_bezier<order>(B[1]));
+}
+*/
+
+#if 0 // using old path
+//std::vector<Geom::Point>
+// mutating
+void
+subpath_from_sbasis(Geom::OldPathSetBuilder &pb, D2<SBasis> const &B, double tol, bool initial) {
+ assert(B.is_finite());
+ if(B.tail_error(2) < tol || B.size() == 2) { // nearly cubic enough
+ if(B.size() == 1) {
+ if (initial) {
+ pb.start_subpath(Geom::Point(B[0][0][0], B[1][0][0]));
+ }
+ pb.push_line(Geom::Point(B[0][0][1], B[1][0][1]));
+ } else {
+ std::vector<Geom::Point> bez = sbasis_to_bezier(B, 2);
+ if (initial) {
+ pb.start_subpath(bez[0]);
+ }
+ pb.push_cubic(bez[1], bez[2], bez[3]);
+ }
+ } else {
+ subpath_from_sbasis(pb, compose(B, Linear(0, 0.5)), tol, initial);
+ subpath_from_sbasis(pb, compose(B, Linear(0.5, 1)), tol, false);
+ }
+}
+
+/*
+* This version works by inverting a reasonable upper bound on the error term after subdividing the
+* curve at $a$. We keep biting off pieces until there is no more curve left.
+*
+* Derivation: The tail of the power series is $a_ks^k + a_{k+1}s^{k+1} + \ldots = e$. A
+* subdivision at $a$ results in a tail error of $e*A^k, A = (1-a)a$. Let this be the desired
+* tolerance tol $= e*A^k$ and invert getting $A = e^{1/k}$ and $a = 1/2 - \sqrt{1/4 - A}$
+*/
+void
+subpath_from_sbasis_incremental(Geom::OldPathSetBuilder &pb, D2<SBasis> B, double tol, bool initial) {
+ const unsigned k = 2; // cubic bezier
+ double te = B.tail_error(k);
+ assert(B[0].is_finite());
+ assert(B[1].is_finite());
+
+ //std::cout << "tol = " << tol << std::endl;
+ while(1) {
+ double A = std::sqrt(tol/te); // pow(te, 1./k)
+ double a = A;
+ if(A < 1) {
+ A = std::min(A, 0.25);
+ a = 0.5 - std::sqrt(0.25 - A); // quadratic formula
+ if(a > 1) a = 1; // clamp to the end of the segment
+ } else
+ a = 1;
+ assert(a > 0);
+ //std::cout << "te = " << te << std::endl;
+ //std::cout << "A = " << A << "; a=" << a << std::endl;
+ D2<SBasis> Bs = compose(B, Linear(0, a));
+ assert(Bs.tail_error(k));
+ std::vector<Geom::Point> bez = sbasis_to_bezier(Bs, 2);
+ reverse(bez.begin(), bez.end());
+ if (initial) {
+ pb.start_subpath(bez[0]);
+ initial = false;
+ }
+ pb.push_cubic(bez[1], bez[2], bez[3]);
+
+// move to next piece of curve
+ if(a >= 1) break;
+ B = compose(B, Linear(a, 1));
+ te = B.tail_error(k);
+ }
+}
+
+#endif
+
+void
+path_from_sbasis(Geom::Path &pb, D2<SBasis> const &B, double tol) {
+ assert(B.isFinite());
+ if(tail_error(B, 2) < tol || sbasis_size(B) == 2) { // nearly cubic enough
+ if(B[0].size() == 0 && B[1].size() != 0) {
+ pb.append(Geom::LineSegment(Geom::Point(0, B[1][0][0]), Geom::Point(0, B[1][0][1])));
+ } else if(B[0].size() != 0 && B[1].size() == 0) {
+ pb.append(Geom::LineSegment(Geom::Point(B[0][0][0], 0), Geom::Point(B[0][0][1], 0)));
+ } else if(sbasis_size(B) == 1) {
+ pb.append(Geom::LineSegment(Geom::Point(B[0][0][0], B[1][0][0]),Geom::Point(B[0][0][1], B[1][0][1])));
+ } else {
+ std::vector<Geom::Point> bez = sbasis_to_bezier(B, 2);
+ pb.append(Geom::CubicBezier(bez[0], bez[1], bez[2], bez[3]));
+ }
+ } else {
+ path_from_sbasis(pb, compose(B, Linear(0, 0.5)), tol);
+ path_from_sbasis(pb, compose(B, Linear(0.5, 1)), tol);
+ }
+}
+
+std::vector<Geom::Path>
+path_from_piecewise(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol) {
+ std::vector<Geom::Path> ret;
+ if(B.size() == 0) return ret;
+ Geom::Path *cur = new Geom::Path();
+ unsigned i = 0;
+ while(true) {
+ path_from_sbasis(*cur, B[i], tol);
+ if(i >= B.size()-1) {
+ ret.push_back(*cur);
+ delete cur;
+ return ret;
+ }
+ i++;
+ if(B[i].at0() != B[i-1].at1()) {
+ ret.push_back(*cur);
+ delete cur;
+ cur = new Geom::Path();
+ }
+ }
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/sbasis-to-bezier.h b/src/2geom/sbasis-to-bezier.h
new file mode 100644
index 000000000..142a25026
--- /dev/null
+++ b/src/2geom/sbasis-to-bezier.h
@@ -0,0 +1,20 @@
+#ifndef _SBASIS_TO_BEZIER
+#define _SBASIS_TO_BEZIER
+
+#include "d2.h"
+#include "path.h"
+
+namespace Geom{
+// this produces a degree k bezier from a degree k sbasis
+std::vector<double>
+sbasis_to_bezier(SBasis const &B, unsigned q = 0);
+
+std::vector<Geom::Point>
+sbasis_to_bezier(D2<SBasis> const &B, unsigned q = 0);
+
+std::vector<Path> path_from_piecewise(Piecewise<D2<SBasis> > const &B, double tol);
+
+void path_from_sbasis(Path &pb, D2<SBasis> const &B, double tol);
+
+};
+#endif
diff --git a/src/2geom/sbasis.cpp b/src/2geom/sbasis.cpp
new file mode 100644
index 000000000..5bf0d2876
--- /dev/null
+++ b/src/2geom/sbasis.cpp
@@ -0,0 +1,490 @@
+/*
+ * sbasis.cpp - S-power basis function class + supporting classes
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#include <cmath>
+
+#include "sbasis.h"
+#include "isnan.h"
+
+namespace Geom{
+
+/*** At some point we should work on tighter bounds for the error. It is clear that the error is
+ * bounded by the L1 norm over the tail of the series, but this is very loose, leading to far too
+ * many cubic beziers. I've changed this to be \sum _i=tail ^\infty |hat a_i| 2^-i but I have no
+ * evidence that this is correct.
+ */
+
+/*
+double SBasis::tail_error(unsigned tail) const {
+ double err = 0, s = 1./(1<<(2*tail)); // rough
+ for(unsigned i = tail; i < size(); i++) {
+ err += (fabs((*this)[i][0]) + fabs((*this)[i][1]))*s;
+ s /= 4;
+ }
+ return err;
+}
+*/
+
+double SBasis::tailError(unsigned tail) const {
+ Interval bs = bounds_fast(*this, tail);
+ return std::max(fabs(bs.min()),fabs(bs.max()));
+}
+
+bool SBasis::isFinite() const {
+ for(unsigned i = 0; i < size(); i++) {
+ if(!(*this)[i].isFinite())
+ return false;
+ }
+ return true;
+}
+
+SBasis operator+(const SBasis& a, const SBasis& b) {
+ SBasis result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] + b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+SBasis operator-(const SBasis& a, const SBasis& b) {
+ SBasis result;
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ result.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++) {
+ result.push_back(a[i] - b[i]);
+ }
+ for(unsigned i = min_size; i < a.size(); i++)
+ result.push_back(a[i]);
+ for(unsigned i = min_size; i < b.size(); i++)
+ result.push_back(-b[i]);
+
+ assert(result.size() == out_size);
+ return result;
+}
+
+SBasis& operator+=(SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ a.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] += b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+SBasis& operator-=(SBasis& a, const SBasis& b) {
+ const unsigned out_size = std::max(a.size(), b.size());
+ const unsigned min_size = std::min(a.size(), b.size());
+ a.reserve(out_size);
+
+ for(unsigned i = 0; i < min_size; i++)
+ a[i] -= b[i];
+ for(unsigned i = min_size; i < b.size(); i++)
+ a.push_back(-b[i]);
+
+ assert(a.size() == out_size);
+ return a;
+}
+
+SBasis operator*(SBasis const &a, double k) {
+ SBasis c;
+ c.reserve(a.size());
+ for(unsigned i = 0; i < a.size(); i++)
+ c.push_back(a[i] * k);
+ return c;
+}
+
+SBasis& operator*=(SBasis& a, double b) {
+ if (a.isZero()) return a;
+ if (b == 0)
+ a.clear();
+ else
+ for(unsigned i = 0; i < a.size(); i++)
+ a[i] *= b;
+ return a;
+}
+
+SBasis shift(SBasis const &a, int sh) {
+ SBasis c = a;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, Linear(0,0));
+ } else {
+ //TODO: truncate
+ }
+ return c;
+}
+
+SBasis shift(Linear const &a, int sh) {
+ SBasis c;
+ if(sh > 0) {
+ c.insert(c.begin(), sh, Linear(0,0));
+ c.push_back(a);
+ }
+ return c;
+}
+
+SBasis multiply(SBasis const &a, SBasis const &b) {
+ // c = {a0*b0 - shift(1, a.Tri*b.Tri), a1*b1 - shift(1, a.Tri*b.Tri)}
+
+ // shift(1, a.Tri*b.Tri)
+ SBasis c;
+ if(a.isZero() || b.isZero())
+ return c;
+ c.resize(a.size() + b.size(), Linear(0,0));
+ c[0] = Linear(0,0);
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ double tri = Tri(b[j])*Tri(a[i-j]);
+ c[i+1/*shift*/] += Linear(Hat(-tri));
+ }
+ }
+ for(unsigned j = 0; j < b.size(); j++) {
+ for(unsigned i = j; i < a.size()+j; i++) {
+ for(unsigned dim = 0; dim < 2; dim++)
+ c[i][dim] += b[j][dim]*a[i-j][dim];
+ }
+ }
+ c.normalize();
+ //assert(!(0 == c.back()[0] && 0 == c.back()[1]));
+ return c;
+}
+
+SBasis integral(SBasis const &c) {
+ SBasis a;
+ a.resize(c.size() + 1, Linear(0,0));
+ a[0] = Linear(0,0);
+
+ for(unsigned k = 1; k < c.size() + 1; k++) {
+ double ahat = -Tri(c[k-1])/(2*k);
+ a[k] = Hat(ahat);
+ }
+ double aTri = 0;
+ for(int k = c.size()-1; k >= 0; k--) {
+ aTri = (Hat(c[k]).d + (k+1)*aTri/2)/(2*k+1);
+ a[k][0] -= aTri/2;
+ a[k][1] += aTri/2;
+ }
+ a.normalize();
+ return a;
+}
+
+SBasis derivative(SBasis const &a) {
+ SBasis c;
+ c.resize(a.size(), Linear(0,0));
+
+ for(unsigned k = 0; k < a.size(); k++) {
+ double d = (2*k+1)*Tri(a[k]);
+
+ for(unsigned dim = 0; dim < 2; dim++) {
+ c[k][dim] = d;
+ if(k+1 < a.size()) {
+ if(dim)
+ c[k][dim] = d - (k+1)*a[k+1][dim];
+ else
+ c[k][dim] = d + (k+1)*a[k+1][dim];
+ }
+ }
+ }
+
+ return c;
+}
+
+//TODO: convert int k to unsigned k, and remove cast
+SBasis sqrt(SBasis const &a, int k) {
+ SBasis c;
+ if(a.isZero() || k == 0)
+ return c;
+ c.resize(k, Linear(0,0));
+ c[0] = Linear(std::sqrt(a[0][0]), std::sqrt(a[0][1]));
+ SBasis r = a - multiply(c, c); // remainder
+
+ for(unsigned i = 1; i <= (unsigned)k and i<r.size(); i++) {
+ Linear ci(r[i][0]/(2*c[0][0]), r[i][1]/(2*c[0][1]));
+ SBasis cisi = shift(ci, i);
+ r -= multiply(shift((c*2 + cisi), i), SBasis(ci));
+ r.truncate(k+1);
+ c += cisi;
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+
+ return c;
+}
+
+// return a kth order approx to 1/a)
+SBasis reciprocal(Linear const &a, int k) {
+ SBasis c;
+ assert(!a.isZero());
+ c.resize(k, Linear(0,0));
+ double r_s0 = (Tri(a)*Tri(a))/(-a[0]*a[1]);
+ double r_s0k = 1;
+ for(unsigned i = 0; i < (unsigned)k; i++) {
+ c[i] = Linear(r_s0k/a[0], r_s0k/a[1]);
+ r_s0k *= r_s0;
+ }
+ return c;
+}
+
+SBasis divide(SBasis const &a, SBasis const &b, int k) {
+ SBasis c;
+ assert(!a.isZero());
+ SBasis r = a; // remainder
+
+ k++;
+ r.resize(k, Linear(0,0));
+ c.resize(k, Linear(0,0));
+
+ for(unsigned i = 0; i < (unsigned)k; i++) {
+ Linear ci(r[i][0]/b[0][0], r[i][1]/b[0][1]); //H0
+ c[i] += ci;
+ r -= shift(multiply(ci,b), i);
+ r.truncate(k+1);
+ if(r.tailError(i) == 0) // if exact
+ break;
+ }
+
+ return c;
+}
+
+// a(b)
+// return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k
+SBasis compose(SBasis const &a, SBasis const &b) {
+ SBasis s = multiply((SBasis(Linear(1,1))-b), b);
+ SBasis r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = SBasis(Linear(Hat(a[i][0]))) - b*a[i][0] + b*a[i][1] + multiply(r,s);
+ }
+ return r;
+}
+
+// a(b)
+// return a0 + s(a1 + s(a2 +... where s = (1-u)u; ak =(1 - u)a^0_k + ua^1_k
+SBasis compose(SBasis const &a, SBasis const &b, unsigned k) {
+ SBasis s = multiply((SBasis(Linear(1,1))-b), b);
+ SBasis r;
+
+ for(int i = a.size()-1; i >= 0; i--) {
+ r = SBasis(Linear(Hat(a[i][0]))) - b*a[i][0] + b*a[i][1] + multiply(r,s);
+ }
+ r.truncate(k);
+ return r;
+}
+
+/*
+Inversion algorithm. The notation is certainly very misleading. The
+pseudocode should say:
+
+c(v) := 0
+r(u) := r_0(u) := u
+for i:=0 to k do
+ c_i(v) := H_0(r_i(u)/(t_1)^i; u)
+ c(v) := c(v) + c_i(v)*t^i
+ r(u) := r(u) ? c_i(u)*(t(u))^i
+endfor
+*/
+
+//#define DEBUG_INVERSION 1
+
+SBasis inverse(SBasis a, int k) {
+ assert(a.size() > 0);
+// the function should have 'unit range'("a00 = 0 and a01 = 1") and be monotonic.
+ double a0 = a[0][0];
+ if(a0 != 0) {
+ a -= a0;
+ }
+ double a1 = a[0][1];
+ assert(a1 != 0); // not invertable.
+
+ if(a1 != 1) {
+ a /= a1;
+ }
+ SBasis c; // c(v) := 0
+ if(a.size() >= 2 && k == 2) {
+ c.push_back(Linear(0,1));
+ Linear t1(1+a[1][0], 1-a[1][1]); // t_1
+ c.push_back(Linear(-a[1][0]/t1[0], -a[1][1]/t1[1]));
+ } else if(a.size() >= 2) { // non linear
+ SBasis r = Linear(0,1); // r(u) := r_0(u) := u
+ Linear t1(1./(1+a[1][0]), 1./(1-a[1][1])); // 1./t_1
+ Linear one(1,1);
+ Linear t1i = one; // t_1^0
+ SBasis one_minus_a = SBasis(one) - a;
+ SBasis t = multiply(one_minus_a, a); // t(u)
+ SBasis ti(one); // t(u)^0
+#ifdef DEBUG_INVERSION
+ std::cout << "a=" << a << std::endl;
+ std::cout << "1-a=" << one_minus_a << std::endl;
+ std::cout << "t1=" << t1 << std::endl;
+ //assert(t1 == t[1]);
+#endif
+
+ c.resize(k+1, Linear(0,0));
+ for(unsigned i = 0; i < (unsigned)k; i++) { // for i:=0 to k do
+#ifdef DEBUG_INVERSION
+ std::cout << "-------" << i << ": ---------" <<std::endl;
+ std::cout << "r=" << r << std::endl
+ << "c=" << c << std::endl
+ << "ti=" << ti << std::endl
+ << std::endl;
+#endif
+ if(r.size() <= i) // ensure enough space in the remainder, probably not needed
+ r.resize(i+1, Linear(0,0));
+ Linear ci(r[i][0]*t1i[0], r[i][1]*t1i[1]); // c_i(v) := H_0(r_i(u)/(t_1)^i; u)
+#ifdef DEBUG_INVERSION
+ std::cout << "t1i=" << t1i << std::endl;
+ std::cout << "ci=" << ci << std::endl;
+#endif
+ for(int dim = 0; dim < 2; dim++) // t1^-i *= 1./t1
+ t1i[dim] *= t1[dim];
+ c[i] = ci; // c(v) := c(v) + c_i(v)*t^i
+ // change from v to u parameterisation
+ SBasis civ = one_minus_a*ci[0] + a*ci[1];
+ // r(u) := r(u) - c_i(u)*(t(u))^i
+ // We can truncate this to the number of final terms, as no following terms can
+ // contribute to the result.
+ r -= multiply(civ,ti);
+ r.truncate(k);
+ if(r.tailError(i) == 0)
+ break; // yay!
+ ti = multiply(ti,t);
+ }
+#ifdef DEBUG_INVERSION
+ std::cout << "##########################" << std::endl;
+#endif
+ } else
+ c = Linear(0,1); // linear
+ c -= a0; // invert the offset
+ c /= a1; // invert the slope
+ return c;
+}
+
+SBasis sin(Linear b, int k) {
+ SBasis s = Linear(std::sin(b[0]), std::sin(b[1]));
+ Tri tr(s[0]);
+ double t2 = Tri(b);
+ s.push_back(Linear(std::cos(b[0])*t2 - tr, -std::cos(b[1])*t2 + tr));
+
+ t2 *= t2;
+ for(int i = 0; i < k; i++) {
+ Linear bo(4*(i+1)*s[i+1][0] - 2*s[i+1][1],
+ -2*s[i+1][0] + 4*(i+1)*s[i+1][1]);
+ bo -= s[i]*(t2/(i+1));
+
+
+ s.push_back(bo/double(i+2));
+ }
+
+ return s;
+}
+
+SBasis cos(Linear bo, int k) {
+ return sin(Linear(bo[0] + M_PI/2,
+ bo[1] + M_PI/2),
+ k);
+}
+
+//compute fog^-1. ("zero" = double comparison threshold. *!*we might divide by "zero"*!*)
+//TODO: compute order according to tol?
+//TODO: requires g(0)=0 & g(1)=1 atm... adaptation to other cases should be obvious!
+SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double zero){
+ SBasis result; //result
+ SBasis r=f; //remainder
+ SBasis Pk=Linear(1)-g,Qk=g,sg=Pk*Qk;
+ Pk.truncate(order);
+ Qk.truncate(order);
+ Pk.resize(order,Linear(0.));
+ Qk.resize(order,Linear(0.));
+ r.resize(order,Linear(0.));
+
+ int vs= valuation(sg,zero);
+
+ for (unsigned k=0; k<order; k+=vs){
+ double p10 = Pk.at(k)[0];// we have to solve the linear system:
+ double p01 = Pk.at(k)[1];//
+ double q10 = Qk.at(k)[0];// p10*a + q10*b = r10
+ double q01 = Qk.at(k)[1];// &
+ double r10 = r.at(k)[0];// p01*a + q01*b = r01
+ double r01 = r.at(k)[1];//
+ double a,b;
+ double det = p10*q01-p01*q10;
+
+ //TODO: handle det~0!!
+ if (fabs(det)<zero){
+ det = zero;
+ a=b=0;
+ }else{
+ a=( q01*r10-q10*r01)/det;
+ b=(-p01*r10+p10*r01)/det;
+ }
+ result.push_back(Linear(a,b));
+ r=r-Pk*a-Qk*b;
+
+ Pk=Pk*sg;
+ Qk=Qk*sg;
+ Pk.truncate(order);
+ Qk.truncate(order);
+ r.truncate(order);
+ }
+ result.normalize();
+ return result;
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/sbasis.h b/src/2geom/sbasis.h
new file mode 100644
index 000000000..a27d4bfce
--- /dev/null
+++ b/src/2geom/sbasis.h
@@ -0,0 +1,301 @@
+/*
+ * sbasis.h - S-power basis function class
+ *
+ * Authors:
+ * Nathan Hurst <njh@mail.csse.monash.edu.au>
+ * Michael Sloan <mgsloan@gmail.com>
+ *
+ * Copyright (C) 2006-2007 authors
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ */
+
+#ifndef SEEN_SBASIS_H
+#define SEEN_SBASIS_H
+#include <vector>
+#include <cassert>
+#include <iostream>
+
+#include "linear.h"
+#include "interval.h"
+
+namespace Geom {
+
+/*** An empty SBasis is identically 0. */
+class SBasis : public std::vector<Linear>{
+public:
+ SBasis() {}
+ explicit SBasis(double a) {
+ push_back(Linear(a,a));
+ }
+ SBasis(SBasis const & a) :
+ std::vector<Linear>(a)
+ {}
+ SBasis(Linear const & bo) {
+ push_back(bo);
+ }
+
+ //IMPL: FragmentConcept
+ typedef double output_type;
+ inline bool isZero() const {
+ if(empty()) return true;
+ for(unsigned i = 0; i < size(); i++) {
+ if(!(*this)[i].isZero()) return false;
+ }
+ return true;
+ }
+ bool isFinite() const;
+ inline double at0() const {
+ if(empty()) return 0; else return (*this)[0][0];
+ }
+ inline double at1() const{
+ if(empty()) return 0; else return (*this)[0][1];
+ }
+
+ double valueAt(double t) const {
+ double s = t*(1-t);
+ double p0 = 0, p1 = 0;
+ double sk = 1;
+//TODO: rewrite as horner
+ for(unsigned k = 0; k < size(); k++) {
+ p0 += sk*(*this)[k][0];
+ p1 += sk*(*this)[k][1];
+ sk *= s;
+ }
+ return (1-t)*p0 + t*p1;
+ }
+ double operator()(double t) const {
+ return valueAt(t);
+ }
+ SBasis toSBasis() const { return SBasis(*this); }
+
+ double tailError(unsigned tail) const;
+
+// compute f(g)
+ SBasis operator()(SBasis const & g) const;
+
+ Linear operator[](unsigned i) const {
+ assert(i < size());
+ return std::vector<Linear>::operator[](i);
+ }
+
+//MUTATOR PRISON
+ Linear& operator[](unsigned i) { return this->at(i); }
+
+ //remove extra zeros
+ void normalize() {
+ while(!empty() && 0 == back()[0] && 0 == back()[1])
+ pop_back();
+ }
+ void truncate(unsigned k) { if(k < size()) resize(k); }
+};
+
+//TODO: figure out how to stick this in linear, while not adding an sbasis dep
+inline SBasis Linear::toSBasis() const { return SBasis(*this); }
+
+//implemented in sbasis-roots.cpp
+Interval bounds_exact(SBasis const &a);
+Interval bounds_fast(SBasis const &a, int order = 0);
+Interval bounds_local(SBasis const &a, const Interval &t, int order = 0);
+
+inline SBasis reverse(SBasis const &a) {
+ SBasis result;
+ result.reserve(a.size());
+ for(unsigned k = 0; k < a.size(); k++)
+ result.push_back(reverse(a[k]));
+ return result;
+}
+
+//IMPL: ScalableConcept
+inline SBasis operator-(const SBasis& p) {
+ if(p.isZero()) return SBasis();
+ SBasis result;
+ result.reserve(p.size());
+
+ for(unsigned i = 0; i < p.size(); i++) {
+ result.push_back(-p[i]);
+ }
+ return result;
+}
+SBasis operator*(SBasis const &a, double k);
+inline SBasis operator*(double k, SBasis const &a) { return a*k; }
+inline SBasis operator/(SBasis const &a, double k) { return a*(1./k); }
+SBasis& operator*=(SBasis& a, double b);
+inline SBasis& operator/=(SBasis& a, double b) { return (a*=(1./b)); }
+
+//IMPL: AddableConcept
+SBasis operator+(const SBasis& a, const SBasis& b);
+SBasis operator-(const SBasis& a, const SBasis& b);
+SBasis& operator+=(SBasis& a, const SBasis& b);
+SBasis& operator-=(SBasis& a, const SBasis& b);
+
+//TODO: remove?
+inline SBasis operator+(const SBasis & a, Linear const & b) {
+ if(b.isZero()) return a;
+ if(a.isZero()) return b;
+ SBasis result(a);
+ result[0] += b;
+ return result;
+}
+inline SBasis operator-(const SBasis & a, Linear const & b) {
+ if(b.isZero()) return a;
+ SBasis result(a);
+ result[0] -= b;
+ return result;
+}
+inline SBasis& operator+=(SBasis& a, const Linear& b) {
+ if(a.isZero())
+ a.push_back(b);
+ else
+ a[0] += b;
+ return a;
+}
+inline SBasis& operator-=(SBasis& a, const Linear& b) {
+ if(a.isZero())
+ a.push_back(-b);
+ else
+ a[0] -= b;
+ return a;
+}
+
+//IMPL: OffsetableConcept
+inline SBasis operator+(const SBasis & a, double b) {
+ if(a.isZero()) return Linear(b, b);
+ SBasis result(a);
+ result[0] += b;
+ return result;
+}
+inline SBasis operator-(const SBasis & a, double b) {
+ if(a.isZero()) return Linear(-b, -b);
+ SBasis result(a);
+ result[0] -= b;
+ return result;
+}
+inline SBasis& operator+=(SBasis& a, double b) {
+ if(a.isZero())
+ a.push_back(Linear(b,b));
+ else
+ a[0] += b;
+ return a;
+}
+inline SBasis& operator-=(SBasis& a, double b) {
+ if(a.isZero())
+ a.push_back(Linear(-b,-b));
+ else
+ a[0] -= b;
+ return a;
+}
+
+SBasis shift(SBasis const &a, int sh);
+SBasis shift(Linear const &a, int sh);
+
+inline SBasis truncate(SBasis const &a, unsigned terms) {
+ SBasis c;
+ c.insert(c.begin(), a.begin(), a.begin() + std::min(terms, (unsigned)a.size()));
+ return c;
+}
+
+SBasis multiply(SBasis const &a, SBasis const &b);
+
+SBasis integral(SBasis const &c);
+SBasis derivative(SBasis const &a);
+
+SBasis sqrt(SBasis const &a, int k);
+
+// return a kth order approx to 1/a)
+SBasis reciprocal(Linear const &a, int k);
+SBasis divide(SBasis const &a, SBasis const &b, int k);
+
+inline SBasis operator*(SBasis const & a, SBasis const & b) {
+ return multiply(a, b);
+}
+
+inline SBasis& operator*=(SBasis& a, SBasis const & b) {
+ a = multiply(a, b);
+ return a;
+}
+
+//valuation: degree of the first non zero coefficient.
+inline unsigned
+valuation(SBasis const &a, double tol=0){
+ unsigned val=0;
+ while( val<a.size() &&
+ fabs(a[val][0])<tol &&
+ fabs(a[val][1])<tol )
+ val++;
+ return val;
+}
+
+// a(b(t))
+SBasis compose(SBasis const &a, SBasis const &b);
+SBasis compose(SBasis const &a, SBasis const &b, unsigned k);
+SBasis inverse(SBasis a, int k);
+//compose_inverse(f,g)=compose(f,inverse(g)), but is numerically more stable in some good cases...
+//TODO: requires g(0)=0 & g(1)=1 atm. generalization should be obvious.
+SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order=2, double tol=1e-3);
+
+inline SBasis portion(const SBasis &t, double from, double to) { return compose(t, Linear(from, to)); }
+
+// compute f(g)
+inline SBasis
+SBasis::operator()(SBasis const & g) const {
+ return compose(*this, g);
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const Linear &bo) {
+ out_file << "{" << bo[0] << ", " << bo[1] << "}";
+ return out_file;
+}
+
+inline std::ostream &operator<< (std::ostream &out_file, const SBasis & p) {
+ for(unsigned i = 0; i < p.size(); i++) {
+ out_file << p[i] << "s^" << i << " + ";
+ }
+ return out_file;
+}
+
+SBasis sin(Linear bo, int k);
+SBasis cos(Linear bo, int k);
+
+std::vector<double> roots(SBasis const & s);
+std::vector<std::vector<double> > multi_roots(SBasis const &f,
+ std::vector<double> const &levels,
+ double htol=1e-7,
+ double vtol=1e-7,
+ double a=0,
+ double b=1);
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+#endif
diff --git a/src/2geom/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp
new file mode 100644
index 000000000..558c10c0f
--- /dev/null
+++ b/src/2geom/solve-bezier-one-d.cpp
@@ -0,0 +1,268 @@
+#include "solver.h"
+#include "point.h"
+#include <algorithm>
+
+/*** Find the zeros of the bernstein function. The code subdivides until it is happy with the
+ * linearity of the function. This requires an O(degree^2) subdivision for each step, even when
+ * there is only one solution.
+ */
+
+namespace Geom{
+
+template<class t>
+static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); }
+
+/*
+ * Forward declarations
+ */
+static void
+Bernstein(double const *V,
+ unsigned degree,
+ double t,
+ double *Left,
+ double *Right);
+
+static unsigned
+control_poly_flat_enough(double const *V, unsigned degree,
+ double left_t, double right_t);
+
+const unsigned MAXDEPTH = 64; /* Maximum depth for recursion */
+
+const double BEPSILON = ldexp(1.0,-MAXDEPTH-1); /*Flatness control value */
+
+/*
+ * find_bernstein_roots : Given an equation in Bernstein-Bernstein form, find all
+ * of the roots in the open interval (0, 1). Return the number of roots found.
+ */
+void
+find_bernstein_roots(double *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> &solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t, double right_t)
+{
+ unsigned n_crossings = 0; /* Number of zero-crossings */
+
+ double split = 0.5;
+ int old_sign = SGN(w[0]);
+ for (unsigned i = 1; i <= degree; i++) {
+ int sign = SGN(w[i]);
+ if (sign) {
+ if (sign != old_sign && old_sign) {
+ split = (i-0.5)/degree;
+ n_crossings++;
+ }
+ old_sign = sign;
+ }
+ }
+
+ switch (n_crossings) {
+ case 0: /* No solutions here */
+ return;
+
+ case 1:
+ /* Unique solution */
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth >= MAXDEPTH) {
+ solutions.push_back((left_t + right_t) / 2.0);
+ return;
+ }
+
+ // I thought secant method would be faster here, but it'aint. -- njh
+
+ if (control_poly_flat_enough(w, degree, left_t, right_t)) {
+ const double Ax = right_t - left_t;
+ const double Ay = w[degree] - w[0];
+
+ solutions.push_back(left_t + Ax*w[0] / Ay);
+ return;
+ }
+ break;
+ }
+
+ /* Otherwise, solve recursively after subdividing control polygon */
+
+
+ /* This bit is very clever (if I say so myself) - rather than arbitrarily subdividing at the t = 0.5 point, we subdivide at the most likely point of change of direction. This buys us a factor of 10 speed up. We also avoid lots of stack frames by avoiding tail recursion. */
+ double Left[degree+1], /* New left and right */
+ Right[degree+1]; /* control polygons */
+ Bernstein(w, degree, split, Left, Right);
+
+ double mid_t = left_t*(1-split) + right_t*split;
+
+ find_bernstein_roots(Left, degree, solutions, depth+1, left_t, mid_t);
+
+ /* Solution is exactly on the subdivision point. */
+ if (Right[0] == 0)
+ solutions.push_back(mid_t);
+
+ find_bernstein_roots(Right, degree, solutions, depth+1, mid_t, right_t);
+}
+
+/*
+ * find_bernstein_roots : Given an equation in Bernstein-Bernstein form, find all
+ * of the roots in the interval [0, 1]. Return the number of roots found.
+ */
+void
+find_bernstein_roots_buggy(double *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> &solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t, double right_t)
+{
+ unsigned n_crossings = 0; /* Number of zero-crossings */
+
+ int old_sign = SGN(w[0]);
+ for (unsigned i = 1; i <= degree; i++) {
+ int sign = SGN(w[i]);
+ if (sign) {
+ if (sign != old_sign && old_sign)
+ n_crossings++;
+ old_sign = sign;
+ }
+ }
+
+ switch (n_crossings) {
+ case 0: /* No solutions here */
+ return;
+
+ case 1:
+ /* Unique solution */
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth >= MAXDEPTH) {
+ solutions.push_back((left_t + right_t) / 2.0);
+ return;
+ }
+
+ // I thought secant method would be faster here, but it'aint. -- njh
+
+ if (control_poly_flat_enough(w, degree, left_t, right_t)) {
+ const double Ax = right_t - left_t;
+ const double Ay = w[degree] - w[0];
+
+ solutions.push_back(left_t + Ax*w[0] / Ay);
+ return;
+ }
+ break;
+ }
+
+ /* Otherwise, solve recursively after subdividing control polygon */
+
+ double split = 0.5;
+
+ double Left[degree+1], /* New left and right */
+ Right[degree+1]; /* control polygons */
+ Bernstein(w, degree, split, Left, Right);
+
+ double mid_t = left_t*(1-split) + right_t*split;
+
+ find_bernstein_roots_buggy(Left, degree, solutions, depth+1, left_t, mid_t);
+
+ /* Solution is exactly on the subdivision point. */
+ if (Right[0] == 0)
+ solutions.push_back(mid_t);
+
+ find_bernstein_roots_buggy(Right, degree, solutions, depth+1, mid_t, right_t);
+}
+
+/*
+ * control_poly_flat_enough :
+ * Check if the control polygon of a Bernstein curve is flat enough
+ * for recursive subdivision to bottom out.
+ *
+ */
+static unsigned
+control_poly_flat_enough(double const *V, /* Control points */
+ unsigned degree,
+ double left_t, double right_t) /* Degree of polynomial */
+{
+ /* Find the perpendicular distance from each interior control point to line connecting V[0] and
+ * V[degree] */
+
+ /* Derive the implicit equation for line connecting first */
+ /* and last control points */
+ const double a = V[0] - V[degree];
+ const double b = right_t - left_t;
+ const double c = left_t * V[degree] - right_t * V[0] + a * left_t;
+
+ double max_distance_above = 0.0;
+ double max_distance_below = 0.0;
+ double ii = 0, dii = 1./degree;
+ for (unsigned i = 1; i < degree; i++) {
+ ii += dii;
+ /* Compute distance from each of the points to that line */
+ const double d = (a + V[i]) * ii*b + c;
+ double dist = d*d;
+ // Find the largest distance
+ if (d < 0.0)
+ max_distance_below = std::min(max_distance_below, -dist);
+ else
+ max_distance_above = std::max(max_distance_above, dist);
+ }
+
+ const double abSquared = (a * a) + (b * b);
+
+ const double intercept_1 = -(c + max_distance_above / abSquared);
+ const double intercept_2 = -(c + max_distance_below / abSquared);
+
+ /* Compute bounding interval*/
+ const double left_intercept = std::min(intercept_1, intercept_2);
+ const double right_intercept = std::max(intercept_1, intercept_2);
+
+ const double error = 0.5 * (right_intercept - left_intercept);
+
+ if (error < BEPSILON * a)
+ return 1;
+
+ return 0;
+}
+
+
+
+/*
+ * Bernstein :
+ * Evaluate a Bernstein function at a particular parameter value
+ * Fill in control points for resulting sub-curves.
+ *
+ */
+static void
+Bernstein(double const *V, /* Control pts */
+ unsigned degree, /* Degree of bernstein curve */
+ double t, /* Parameter value */
+ double *Left, /* RETURN left half ctl pts */
+ double *Right) /* RETURN right half ctl pts */
+{
+ double Vtemp[degree+1][degree+1];
+
+ /* Copy control points */
+ std::copy(V, V+degree+1, Vtemp[0]);
+
+ /* Triangle computation */
+ const double omt = (1-t);
+ Left[0] = Vtemp[0][0];
+ Right[degree] = Vtemp[0][degree];
+ for (unsigned i = 1; i <= degree; i++) {
+ for (unsigned j = 0; j <= degree - i; j++) {
+ Vtemp[i][j] = omt*Vtemp[i-1][j] + t*Vtemp[i-1][j+1];
+ }
+ Left[i] = Vtemp[i][0];
+ Right[degree-i] = Vtemp[i][degree-i];
+ }
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/solve-bezier-parametric.cpp b/src/2geom/solve-bezier-parametric.cpp
new file mode 100644
index 000000000..576ac34ff
--- /dev/null
+++ b/src/2geom/solve-bezier-parametric.cpp
@@ -0,0 +1,227 @@
+#include "solver.h"
+#include "point.h"
+#include <algorithm>
+
+namespace Geom{
+
+/*** Find the zeros of the parametric function in 2d defined by two beziers X(t), Y(t). The code subdivides until it happy with the linearity of the bezier. This requires an n^2 subdivision for each step, even when there is only one solution.
+ *
+ * Perhaps it would be better to subdivide particularly around nodes with changing sign, rather than simply cutting in half.
+ */
+
+#define SGN(a) (((a)<0) ? -1 : 1)
+
+/*
+ * Forward declarations
+ */
+static Geom::Point
+Bezier(Geom::Point const *V,
+ unsigned degree,
+ double t,
+ Geom::Point *Left,
+ Geom::Point *Right);
+
+unsigned
+crossing_count(Geom::Point const *V, unsigned degree);
+static unsigned
+control_poly_flat_enough(Geom::Point const *V, unsigned degree);
+static double
+compute_x_intercept(Geom::Point const *V, unsigned degree);
+
+const unsigned MAXDEPTH = 64; /* Maximum depth for recursion */
+
+const double BEPSILON = ldexp(1.0,-MAXDEPTH-1); /*Flatness control value */
+
+unsigned total_steps, total_subs;
+
+/*
+ * find_bezier_roots : Given an equation in Bernstein-Bezier form, find all
+ * of the roots in the interval [0, 1]. Return the number of roots found.
+ */
+void
+find_parametric_bezier_roots(Geom::Point const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> &solutions, /* RETURN candidate t-values */
+ unsigned depth) /* The depth of the recursion */
+{
+ total_steps++;
+ const unsigned max_crossings = crossing_count(w, degree);
+ switch (max_crossings) {
+ case 0: /* No solutions here */
+ return;
+
+ case 1:
+ /* Unique solution */
+ /* Stop recursion when the tree is deep enough */
+ /* if deep enough, return 1 solution at midpoint */
+ if (depth >= MAXDEPTH) {
+ solutions.push_back((w[0][Geom::X] + w[degree][Geom::X]) / 2.0);
+ return;
+ }
+
+ // I thought secant method would be faster here, but it'aint. -- njh
+
+ if (control_poly_flat_enough(w, degree)) {
+ solutions.push_back(compute_x_intercept(w, degree));
+ return;
+ }
+ break;
+ }
+
+ /* Otherwise, solve recursively after subdividing control polygon */
+ Geom::Point Left[degree+1], /* New left and right */
+ Right[degree+1]; /* control polygons */
+ Bezier(w, degree, 0.5, Left, Right);
+ total_subs ++;
+ find_parametric_bezier_roots(Left, degree, solutions, depth+1);
+ find_parametric_bezier_roots(Right, degree, solutions, depth+1);
+}
+
+
+/*
+ * crossing_count:
+ * Count the number of times a Bezier control polygon
+ * crosses the 0-axis. This number is >= the number of roots.
+ *
+ */
+unsigned
+crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */
+ unsigned degree) /* Degree of Bezier curve */
+{
+ unsigned n_crossings = 0; /* Number of zero-crossings */
+
+ int old_sign = SGN(V[0][Geom::Y]);
+ for (unsigned i = 1; i <= degree; i++) {
+ int sign = SGN(V[i][Geom::Y]);
+ if (sign != old_sign)
+ n_crossings++;
+ old_sign = sign;
+ }
+ return n_crossings;
+}
+
+
+
+/*
+ * control_poly_flat_enough :
+ * Check if the control polygon of a Bezier curve is flat enough
+ * for recursive subdivision to bottom out.
+ *
+ */
+static unsigned
+control_poly_flat_enough(Geom::Point const *V, /* Control points */
+ unsigned degree) /* Degree of polynomial */
+{
+ /* Find the perpendicular distance from each interior control point to line connecting V[0] and
+ * V[degree] */
+
+ /* Derive the implicit equation for line connecting first */
+ /* and last control points */
+ const double a = V[0][Geom::Y] - V[degree][Geom::Y];
+ const double b = V[degree][Geom::X] - V[0][Geom::X];
+ const double c = V[0][Geom::X] * V[degree][Geom::Y] - V[degree][Geom::X] * V[0][Geom::Y];
+
+ const double abSquared = (a * a) + (b * b);
+
+ double distance[degree]; /* Distances from pts to line */
+ for (unsigned i = 1; i < degree; i++) {
+ /* Compute distance from each of the points to that line */
+ double & dist(distance[i-1]);
+ const double d = a * V[i][Geom::X] + b * V[i][Geom::Y] + c;
+ dist = d*d / abSquared;
+ if (d < 0.0)
+ dist = -dist;
+ }
+
+
+ // Find the largest distance
+ double max_distance_above = 0.0;
+ double max_distance_below = 0.0;
+ for (unsigned i = 0; i < degree-1; i++) {
+ const double d = distance[i];
+ if (d < 0.0)
+ max_distance_below = std::min(max_distance_below, d);
+ if (d > 0.0)
+ max_distance_above = std::max(max_distance_above, d);
+ }
+
+ const double intercept_1 = (c + max_distance_above) / -a;
+ const double intercept_2 = (c + max_distance_below) / -a;
+
+ /* Compute bounding interval*/
+ const double left_intercept = std::min(intercept_1, intercept_2);
+ const double right_intercept = std::max(intercept_1, intercept_2);
+
+ const double error = 0.5 * (right_intercept - left_intercept);
+
+ if (error < BEPSILON)
+ return 1;
+
+ return 0;
+}
+
+
+
+/*
+ * compute_x_intercept :
+ * Compute intersection of chord from first control point to last
+ * with 0-axis.
+ *
+ */
+static double
+compute_x_intercept(Geom::Point const *V, /* Control points */
+ unsigned degree) /* Degree of curve */
+{
+ const Geom::Point A = V[degree] - V[0];
+
+ return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y];
+}
+
+
+/*
+ * Bezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ * Fill in control points for resulting sub-curves.
+ *
+ */
+static Geom::Point
+Bezier(Geom::Point const *V, /* Control pts */
+ unsigned degree, /* Degree of bezier curve */
+ double t, /* Parameter value */
+ Geom::Point *Left, /* RETURN left half ctl pts */
+ Geom::Point *Right) /* RETURN right half ctl pts */
+{
+ Geom::Point Vtemp[degree+1][degree+1];
+
+ /* Copy control points */
+ std::copy(V, V+degree+1, Vtemp[0]);
+
+ /* Triangle computation */
+ for (unsigned i = 1; i <= degree; i++) {
+ for (unsigned j = 0; j <= degree - i; j++) {
+ Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]);
+ }
+ }
+
+ for (unsigned j = 0; j <= degree; j++)
+ Left[j] = Vtemp[j][0];
+ for (unsigned j = 0; j <= degree; j++)
+ Right[j] = Vtemp[degree-j][j];
+
+ return (Vtemp[degree][0]);
+}
+
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(substatement-open . 0))
+ indent-tabs-mode:nil
+ c-brace-offset:0
+ fill-column:99
+ End:
+ vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+*/
+
diff --git a/src/2geom/solver.h b/src/2geom/solver.h
new file mode 100644
index 000000000..c2e290251
--- /dev/null
+++ b/src/2geom/solver.h
@@ -0,0 +1,38 @@
+#ifndef _SOLVE_SBASIS_H
+#define _SOLVE_SBASIS_H
+#include "point.h"
+#include "sbasis.h"
+
+namespace Geom{
+
+unsigned
+crossing_count(Geom::Point const *V, /* Control pts of Bezier curve */
+ unsigned degree); /* Degree of Bezier curve */
+void
+find_parametric_bezier_roots(
+ Geom::Point const *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> & solutions, /* RETURN candidate t-values */
+ unsigned depth); /* The depth of the recursion */
+
+unsigned
+crossing_count(double const *V, /* Control pts of Bezier curve */
+ unsigned degree, /* Degree of Bezier curve */
+ double left_t, double right_t);
+void
+find_bernstein_roots(
+ double *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> & solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t=0, double right_t=1);
+void
+find_bernstein_roots_buggy(
+ double *w, /* The control points */
+ unsigned degree, /* The degree of the polynomial */
+ std::vector<double> & solutions, /* RETURN candidate t-values */
+ unsigned depth, /* The depth of the recursion */
+ double left_t=0, double right_t=1);
+
+};
+#endif
diff --git a/src/2geom/sturm.h b/src/2geom/sturm.h
new file mode 100644
index 000000000..4a9fcc6e2
--- /dev/null
+++ b/src/2geom/sturm.h
@@ -0,0 +1,62 @@
+#include "poly.h"
+#include "utils.h"
+
+class sturm : public std::vector<Poly>{
+public:
+ sturm(Poly const &X) {
+ push_back(X);
+ push_back(derivative(X));
+ Poly Xi = back();
+ Poly Xim1 = X;
+ std::cout << "sturm:\n" << Xim1 << std::endl;
+ std::cout << Xi << std::endl;
+ while(Xi.size() > 1) {
+ Poly r;
+ divide(Xim1, Xi, r);
+ std::cout << r << std::endl;
+ assert(r.size() < Xi.size());
+ Xim1 = Xi;
+ Xi = -r;
+ assert(Xim1.size() > Xi.size());
+ push_back(Xi);
+ }
+ }
+
+ unsigned count_signs(double t) {
+ unsigned n_signs = 0;/* Number of sign-changes */
+ const double big = 1e20; // a number such that practical polys would overflow on evaluation
+ if(t >= big) {
+ int old_sign = sgn((*this)[0].back());
+ for (unsigned i = 1; i < size(); i++) {
+ int sign = sgn((*this)[i].back());
+ if (sign != old_sign)
+ n_signs++;
+ old_sign = sign;
+ }
+ } else {
+ int old_sign = sgn((*this)[0].eval(t));
+ for (unsigned i = 1; i < size(); i++) {
+ int sign = sgn((*this)[i].eval(t));
+ if (sign != old_sign)
+ n_signs++;
+ old_sign = sign;
+ }
+ }
+ return n_signs;
+ }
+
+ unsigned n_roots_between(double l, double r) {
+ return count_signs(l) - count_signs(r);
+ }
+};
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp
new file mode 100644
index 000000000..0063fcfe9
--- /dev/null
+++ b/src/2geom/svg-path-parser.cpp
@@ -0,0 +1,1603 @@
+#line 1 "/home/michael/2geom/src/svg-path-parser.rl"
+/*
+ * parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 Aaron Spike <aaron@ekips.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+
+#include <cmath>
+#include <vector>
+#include <glib.h>
+
+#include "point.h"
+
+#include "svg-path-parser.h"
+
+namespace Geom {
+
+namespace {
+
+class Parser {
+public:
+ Parser(SVGPathSink &sink) : _sink(sink) {}
+
+ void parse(char const *str) throw(SVGPathParseError);
+
+private:
+ bool _absolute;
+ Point _current;
+ Point _initial;
+ Point _cubic_tangent;
+ Point _quad_tangent;
+ std::vector<double> _params;
+ SVGPathSink &_sink;
+
+ void _reset() {
+ _absolute = false;
+ _current = _initial = Point(0, 0);
+ _quad_tangent = _cubic_tangent = Point(0, 0);
+ _params.clear();
+ }
+
+ void _push(double value) {
+ _params.push_back(value);
+ }
+
+ double _pop() {
+ double value = _params.back();
+ _params.pop_back();
+ return value;
+ }
+
+ bool _pop_flag() {
+ return _pop() != 0.0;
+ }
+
+ double _pop_coord(Geom::Dim2 axis) {
+ if (_absolute) {
+ return _pop();
+ } else {
+ return _pop() + _current[axis];
+ }
+ }
+
+ Point _pop_point() {
+ double y = _pop_coord(Geom::Y);
+ double x = _pop_coord(Geom::X);
+ return Point(x, y);
+ }
+
+ void _moveTo(Point p) {
+ _quad_tangent = _cubic_tangent = _current = _initial = p;
+ _sink.moveTo(p);
+ }
+
+ void _lineTo(Point p) {
+ _quad_tangent = _cubic_tangent = _current = p;
+ _sink.lineTo(p);
+ }
+
+ void _curveTo(Point c0, Point c1, Point p) {
+ _quad_tangent = _current = p;
+ _cubic_tangent = p + ( p - c1 );
+ _sink.curveTo(c0, c1, p);
+ }
+
+ void _quadTo(Point c, Point p) {
+ _cubic_tangent = _current = p;
+ _quad_tangent = p + ( p - c );
+ _sink.quadTo(c, p);
+ }
+
+ void _arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point p)
+ {
+ _quad_tangent = _cubic_tangent = _current = p;
+ _sink.arcTo(rx, ry, angle, large_arc, sweep, p);
+ }
+
+ void _closePath() {
+ _quad_tangent = _cubic_tangent = _current = _initial;
+ _sink.closePath();
+ }
+};
+
+
+#line 133 "/home/michael/2geom/src/svg-path-parser.cpp"
+static const char _svg_path_actions[] = {
+ 0, 1, 0, 1, 1, 1, 2, 1,
+ 3, 1, 4, 1, 5, 1, 15, 1,
+ 16, 2, 1, 0, 2, 1, 2, 2,
+ 1, 3, 2, 1, 6, 2, 1, 7,
+ 2, 1, 8, 2, 1, 9, 2, 1,
+ 10, 2, 1, 11, 2, 1, 12, 2,
+ 1, 13, 2, 1, 14, 2, 2, 1,
+ 2, 3, 1, 2, 4, 0, 2, 5,
+ 0, 2, 15, 16, 3, 1, 6, 0,
+ 3, 1, 6, 16, 3, 1, 7, 0,
+ 3, 1, 7, 16, 3, 1, 8, 0,
+ 3, 1, 8, 16, 3, 1, 9, 0,
+ 3, 1, 9, 16, 3, 1, 10, 0,
+ 3, 1, 10, 16, 3, 1, 11, 0,
+ 3, 1, 11, 16, 3, 1, 12, 0,
+ 3, 1, 12, 16, 3, 1, 13, 0,
+ 3, 1, 13, 16, 3, 1, 14, 0,
+ 3, 1, 14, 16
+};
+
+static const short _svg_path_key_offsets[] = {
+ 0, 7, 7, 16, 25, 28, 30, 42,
+ 52, 55, 57, 90, 121, 124, 126, 138,
+ 148, 151, 153, 186, 195, 207, 216, 249,
+ 256, 263, 265, 275, 283, 290, 292, 304,
+ 314, 317, 319, 328, 335, 341, 346, 353,
+ 359, 364, 374, 377, 379, 391, 401, 404,
+ 406, 437, 466, 476, 488, 498, 507, 509,
+ 521, 533, 544, 554, 561, 567, 572, 584,
+ 595, 607, 617, 620, 622, 655, 664, 695,
+ 704, 713, 716, 718, 730, 740, 743, 745,
+ 757, 767, 770, 772, 784, 794, 797, 799,
+ 811, 821, 824, 826, 838, 848, 851, 853,
+ 886, 917, 929, 938, 950, 959, 971, 980,
+ 992, 1001, 1013, 1022, 1055, 1059, 1061, 1092,
+ 1101, 1110, 1113, 1115, 1148, 1179, 1182, 1184,
+ 1217, 1226, 1259, 1263, 1265, 1296, 1305, 1314,
+ 1323, 1326, 1328, 1340, 1350, 1353, 1355, 1367,
+ 1377, 1380, 1382, 1394, 1404, 1407, 1409, 1442,
+ 1473, 1485, 1494, 1506, 1515, 1527, 1536, 1569,
+ 1573, 1575, 1606, 1615, 1624, 1627, 1629, 1641,
+ 1651, 1654, 1656, 1668, 1678, 1681, 1683, 1695,
+ 1705, 1708, 1710, 1743, 1774, 1786, 1795, 1807,
+ 1816, 1828, 1837, 1870, 1874, 1876, 1907, 1916,
+ 1925, 1928, 1930, 1942, 1952, 1955, 1957, 1990,
+ 2021, 2033, 2042, 2075, 2079, 2081, 2112, 2121,
+ 2130, 2133, 2135, 2168, 2199, 2202, 2204, 2237,
+ 2246, 2279, 2283, 2285, 2316, 2341, 2366, 2373,
+ 2382, 2391, 2424, 2428, 2430, 2461, 2494, 2503,
+ 2512, 2524, 2533, 2566, 2570, 2572, 2603, 2612,
+ 2621, 2630, 2639, 2672, 2676, 2678, 2709, 2742,
+ 2746, 2748, 2758, 2791, 2795, 2797, 2807, 2811,
+ 2813, 2823, 2827, 2829, 2839, 2843, 2845, 2855,
+ 2859, 2861, 2871, 2875, 2877, 2887, 2891, 2893,
+ 2903, 2936, 2940, 2942, 2952, 2956, 2958, 2968,
+ 2972, 2974, 2984, 2988, 2990, 3000, 3004, 3006,
+ 3016, 3020, 3022, 3051, 3055, 3057, 3088, 3121,
+ 3130, 3142, 3146, 3148, 3158, 3170, 3175, 3185,
+ 3189, 3191, 3198, 3208, 3212, 3214, 3224, 3236,
+ 3248, 3260, 3264, 3266, 3276, 3288, 3292, 3294,
+ 3304, 3313, 3317, 3319, 3327, 3336, 3341, 3346,
+ 3358, 3362, 3364, 3395, 3399, 3401, 3411
+};
+
+static const char _svg_path_trans_keys[] = {
+ 0, 13, 32, 77, 109, 9, 10, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 0, 13, 32, 44, 46, 65,
+ 67, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 69,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 101, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 13, 32, 46, 9, 10, 48, 57,
+ 13, 32, 46, 9, 10, 48, 57, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 48, 57, 13, 32, 44, 46, 9,
+ 10, 48, 57, 13, 32, 46, 9, 10,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 69, 101, 9, 10, 48, 57,
+ 13, 32, 44, 48, 49, 9, 10, 13,
+ 32, 48, 49, 9, 10, 13, 32, 44,
+ 9, 10, 13, 32, 44, 48, 49, 9,
+ 10, 13, 32, 48, 49, 9, 10, 13,
+ 32, 44, 9, 10, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 48, 57, 13, 32, 44,
+ 46, 69, 101, 9, 10, 43, 45, 48,
+ 57, 13, 32, 44, 46, 48, 49, 9,
+ 10, 43, 45, 50, 57, 13, 32, 43,
+ 45, 46, 48, 49, 9, 10, 50, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 48, 57, 13, 32, 44, 48, 49, 9,
+ 10, 13, 32, 48, 49, 9, 10, 13,
+ 32, 44, 9, 10, 13, 32, 44, 46,
+ 48, 49, 9, 10, 43, 45, 50, 57,
+ 13, 32, 43, 45, 46, 48, 49, 9,
+ 10, 50, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 69,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 101, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 46,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 9, 10, 43, 45,
+ 48, 57, 46, 48, 57, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 44, 46, 9,
+ 10, 43, 45, 48, 57, 46, 48, 57,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 46, 48, 57, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 69, 101, 9, 10, 43, 45, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 44, 46, 69, 101, 9, 10, 43,
+ 45, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 46, 48,
+ 57, 48, 57, 0, 13, 32, 44, 46,
+ 65, 67, 69, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 101, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 0, 13, 32, 44,
+ 46, 65, 67, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 46, 48, 57, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 69,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 101, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 0, 13, 32, 44, 46, 65,
+ 67, 69, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 101, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 43, 45, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 46, 48, 57, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 46, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 69, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 101, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 43, 45,
+ 48, 57, 0, 13, 32, 44, 46, 65,
+ 67, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 13, 32, 44, 46, 69, 101, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 69,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 101, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 43, 45, 48, 57, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 46, 48, 57, 48, 57, 13, 32, 44,
+ 46, 69, 101, 9, 10, 43, 45, 48,
+ 57, 13, 32, 44, 46, 9, 10, 43,
+ 45, 48, 57, 46, 48, 57, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 46, 48,
+ 57, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 44, 46, 9, 10, 43, 45, 48,
+ 57, 46, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 43, 45, 46, 9,
+ 10, 48, 57, 13, 32, 44, 46, 69,
+ 101, 9, 10, 43, 45, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 43, 45,
+ 48, 57, 48, 57, 0, 13, 32, 44,
+ 46, 65, 67, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 46, 48, 57,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 46, 48, 57, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 43, 45, 48, 57, 13, 32, 44,
+ 46, 69, 101, 9, 10, 43, 45, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 0, 13, 32, 44, 46, 65,
+ 67, 69, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 101, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 43, 45, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 46, 48, 57, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 46,
+ 48, 57, 48, 57, 0, 13, 32, 44,
+ 46, 65, 67, 69, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 101, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 43, 45, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 72, 76, 77, 81,
+ 83, 84, 86, 90, 97, 99, 104, 108,
+ 109, 113, 115, 116, 118, 122, 9, 10,
+ 43, 45, 48, 57, 0, 13, 32, 65,
+ 67, 72, 76, 77, 81, 83, 84, 86,
+ 90, 97, 99, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 0, 13, 32,
+ 65, 67, 72, 76, 77, 81, 83, 84,
+ 86, 90, 97, 99, 104, 108, 109, 113,
+ 115, 116, 118, 122, 9, 10, 13, 32,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 43, 45, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 43, 45,
+ 48, 57, 48, 57, 0, 13, 32, 44,
+ 46, 65, 67, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 13, 32, 43, 45, 46,
+ 9, 10, 48, 57, 13, 32, 43, 45,
+ 46, 9, 10, 48, 57, 13, 32, 43,
+ 45, 46, 9, 10, 48, 57, 13, 32,
+ 43, 45, 46, 9, 10, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 43, 45, 48, 57, 0, 13, 32,
+ 44, 46, 65, 67, 69, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 101,
+ 104, 108, 109, 113, 115, 116, 118, 122,
+ 9, 10, 43, 45, 48, 57, 43, 45,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 69, 72, 76,
+ 77, 81, 83, 84, 86, 90, 97, 99,
+ 101, 104, 108, 109, 113, 115, 116, 118,
+ 122, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 43,
+ 45, 48, 57, 48, 57, 13, 32, 44,
+ 46, 9, 10, 43, 45, 48, 57, 0,
+ 13, 32, 44, 46, 65, 67, 69, 72,
+ 76, 77, 81, 83, 84, 86, 90, 97,
+ 99, 101, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 0, 13,
+ 32, 44, 46, 65, 67, 72, 76, 77,
+ 81, 83, 84, 86, 90, 97, 99, 104,
+ 108, 109, 113, 115, 116, 118, 122, 9,
+ 10, 48, 57, 43, 45, 48, 57, 48,
+ 57, 0, 13, 32, 44, 46, 65, 67,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 104, 108, 109, 113, 115, 116,
+ 118, 122, 9, 10, 43, 45, 48, 57,
+ 0, 13, 32, 44, 46, 65, 67, 69,
+ 72, 76, 77, 81, 83, 84, 86, 90,
+ 97, 99, 101, 104, 108, 109, 113, 115,
+ 116, 118, 122, 9, 10, 43, 45, 48,
+ 57, 13, 32, 43, 45, 46, 9, 10,
+ 48, 57, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 43, 45,
+ 48, 57, 48, 57, 13, 32, 44, 46,
+ 9, 10, 43, 45, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 43, 45,
+ 48, 57, 13, 32, 44, 9, 10, 13,
+ 32, 44, 46, 69, 101, 9, 10, 48,
+ 57, 43, 45, 48, 57, 48, 57, 13,
+ 32, 44, 9, 10, 48, 57, 13, 32,
+ 44, 46, 69, 101, 9, 10, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 13, 32, 44, 46, 69, 101, 9, 10,
+ 43, 45, 48, 57, 43, 45, 48, 57,
+ 48, 57, 13, 32, 44, 46, 9, 10,
+ 43, 45, 48, 57, 13, 32, 44, 46,
+ 69, 101, 9, 10, 43, 45, 48, 57,
+ 43, 45, 48, 57, 48, 57, 13, 32,
+ 44, 46, 9, 10, 43, 45, 48, 57,
+ 13, 32, 43, 45, 46, 9, 10, 48,
+ 57, 43, 45, 48, 57, 48, 57, 13,
+ 32, 44, 46, 9, 10, 48, 57, 13,
+ 32, 43, 45, 46, 9, 10, 48, 57,
+ 13, 32, 44, 9, 10, 13, 32, 44,
+ 9, 10, 13, 32, 44, 46, 69, 101,
+ 9, 10, 43, 45, 48, 57, 43, 45,
+ 48, 57, 48, 57, 0, 13, 32, 44,
+ 46, 65, 67, 72, 76, 77, 81, 83,
+ 84, 86, 90, 97, 99, 104, 108, 109,
+ 113, 115, 116, 118, 122, 9, 10, 43,
+ 45, 48, 57, 43, 45, 48, 57, 48,
+ 57, 13, 32, 44, 46, 9, 10, 43,
+ 45, 48, 57, 0
+};
+
+static const char _svg_path_single_lengths[] = {
+ 5, 0, 5, 5, 1, 0, 6, 4,
+ 1, 0, 27, 25, 1, 0, 6, 4,
+ 1, 0, 27, 5, 6, 5, 27, 3,
+ 3, 0, 6, 4, 3, 0, 6, 4,
+ 1, 0, 5, 5, 4, 3, 5, 4,
+ 3, 4, 1, 0, 6, 4, 1, 0,
+ 27, 25, 6, 6, 4, 5, 0, 6,
+ 6, 7, 6, 5, 4, 3, 6, 7,
+ 6, 4, 1, 0, 27, 5, 27, 5,
+ 5, 1, 0, 6, 4, 1, 0, 6,
+ 4, 1, 0, 6, 4, 1, 0, 6,
+ 4, 1, 0, 6, 4, 1, 0, 27,
+ 25, 6, 5, 6, 5, 6, 5, 6,
+ 5, 6, 5, 27, 2, 0, 25, 5,
+ 5, 1, 0, 27, 25, 1, 0, 27,
+ 5, 27, 2, 0, 25, 5, 5, 5,
+ 1, 0, 6, 4, 1, 0, 6, 4,
+ 1, 0, 6, 4, 1, 0, 27, 25,
+ 6, 5, 6, 5, 6, 5, 27, 2,
+ 0, 25, 5, 5, 1, 0, 6, 4,
+ 1, 0, 6, 4, 1, 0, 6, 4,
+ 1, 0, 27, 25, 6, 5, 6, 5,
+ 6, 5, 27, 2, 0, 25, 5, 5,
+ 1, 0, 6, 4, 1, 0, 27, 25,
+ 6, 5, 27, 2, 0, 25, 5, 5,
+ 1, 0, 27, 25, 1, 0, 27, 5,
+ 27, 2, 0, 25, 23, 23, 3, 5,
+ 5, 27, 2, 0, 25, 27, 5, 5,
+ 6, 5, 27, 2, 0, 25, 5, 5,
+ 5, 5, 27, 2, 0, 25, 27, 2,
+ 0, 4, 27, 2, 0, 4, 2, 0,
+ 4, 2, 0, 4, 2, 0, 4, 2,
+ 0, 4, 2, 0, 4, 2, 0, 4,
+ 27, 2, 0, 4, 2, 0, 4, 2,
+ 0, 4, 2, 0, 4, 2, 0, 4,
+ 2, 0, 25, 2, 0, 25, 27, 5,
+ 6, 2, 0, 4, 6, 3, 6, 2,
+ 0, 3, 6, 2, 0, 4, 6, 6,
+ 6, 2, 0, 4, 6, 2, 0, 4,
+ 5, 2, 0, 4, 5, 3, 3, 6,
+ 2, 0, 25, 2, 0, 4, 0
+};
+
+static const char _svg_path_range_lengths[] = {
+ 1, 0, 2, 2, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 1, 1, 3, 2, 3, 2, 3, 2,
+ 2, 1, 2, 2, 2, 1, 3, 3,
+ 1, 1, 2, 1, 1, 1, 1, 1,
+ 1, 3, 1, 1, 3, 3, 1, 1,
+ 2, 2, 2, 3, 3, 2, 1, 3,
+ 3, 2, 2, 1, 1, 1, 3, 2,
+ 3, 3, 1, 1, 3, 2, 2, 2,
+ 2, 1, 1, 3, 3, 1, 1, 3,
+ 3, 1, 1, 3, 3, 1, 1, 3,
+ 3, 1, 1, 3, 3, 1, 1, 3,
+ 3, 3, 2, 3, 2, 3, 2, 3,
+ 2, 3, 2, 3, 1, 1, 3, 2,
+ 2, 1, 1, 3, 3, 1, 1, 3,
+ 2, 3, 1, 1, 3, 2, 2, 2,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 3, 2, 3, 2, 3, 2, 3, 1,
+ 1, 3, 2, 2, 1, 1, 3, 3,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 1, 1, 3, 3, 3, 2, 3, 2,
+ 3, 2, 3, 1, 1, 3, 2, 2,
+ 1, 1, 3, 3, 1, 1, 3, 3,
+ 3, 2, 3, 1, 1, 3, 2, 2,
+ 1, 1, 3, 3, 1, 1, 3, 2,
+ 3, 1, 1, 3, 1, 1, 2, 2,
+ 2, 3, 1, 1, 3, 3, 2, 2,
+ 3, 2, 3, 1, 1, 3, 2, 2,
+ 2, 2, 3, 1, 1, 3, 3, 1,
+ 1, 3, 3, 1, 1, 3, 1, 1,
+ 3, 1, 1, 3, 1, 1, 3, 1,
+ 1, 3, 1, 1, 3, 1, 1, 3,
+ 3, 1, 1, 3, 1, 1, 3, 1,
+ 1, 3, 1, 1, 3, 1, 1, 3,
+ 1, 1, 2, 1, 1, 3, 3, 2,
+ 3, 1, 1, 3, 3, 1, 2, 1,
+ 1, 2, 2, 1, 1, 3, 3, 3,
+ 3, 1, 1, 3, 3, 1, 1, 3,
+ 2, 1, 1, 2, 2, 1, 1, 3,
+ 1, 1, 3, 1, 1, 3, 0
+};
+
+static const short _svg_path_index_offsets[] = {
+ 0, 7, 7, 15, 23, 26, 28, 38,
+ 46, 49, 51, 82, 111, 114, 116, 126,
+ 134, 137, 139, 170, 178, 188, 196, 227,
+ 233, 239, 241, 250, 257, 263, 265, 275,
+ 283, 286, 288, 296, 303, 309, 314, 321,
+ 327, 332, 340, 343, 345, 355, 363, 366,
+ 368, 398, 426, 435, 445, 453, 461, 463,
+ 473, 483, 493, 502, 509, 515, 520, 530,
+ 540, 550, 558, 561, 563, 594, 602, 632,
+ 640, 648, 651, 653, 663, 671, 674, 676,
+ 686, 694, 697, 699, 709, 717, 720, 722,
+ 732, 740, 743, 745, 755, 763, 766, 768,
+ 799, 828, 838, 846, 856, 864, 874, 882,
+ 892, 900, 910, 918, 949, 953, 955, 984,
+ 992, 1000, 1003, 1005, 1036, 1065, 1068, 1070,
+ 1101, 1109, 1140, 1144, 1146, 1175, 1183, 1191,
+ 1199, 1202, 1204, 1214, 1222, 1225, 1227, 1237,
+ 1245, 1248, 1250, 1260, 1268, 1271, 1273, 1304,
+ 1333, 1343, 1351, 1361, 1369, 1379, 1387, 1418,
+ 1422, 1424, 1453, 1461, 1469, 1472, 1474, 1484,
+ 1492, 1495, 1497, 1507, 1515, 1518, 1520, 1530,
+ 1538, 1541, 1543, 1574, 1603, 1613, 1621, 1631,
+ 1639, 1649, 1657, 1688, 1692, 1694, 1723, 1731,
+ 1739, 1742, 1744, 1754, 1762, 1765, 1767, 1798,
+ 1827, 1837, 1845, 1876, 1880, 1882, 1911, 1919,
+ 1927, 1930, 1932, 1963, 1992, 1995, 1997, 2028,
+ 2036, 2067, 2071, 2073, 2102, 2127, 2152, 2158,
+ 2166, 2174, 2205, 2209, 2211, 2240, 2271, 2279,
+ 2287, 2297, 2305, 2336, 2340, 2342, 2371, 2379,
+ 2387, 2395, 2403, 2434, 2438, 2440, 2469, 2500,
+ 2504, 2506, 2514, 2545, 2549, 2551, 2559, 2563,
+ 2565, 2573, 2577, 2579, 2587, 2591, 2593, 2601,
+ 2605, 2607, 2615, 2619, 2621, 2629, 2633, 2635,
+ 2643, 2674, 2678, 2680, 2688, 2692, 2694, 2702,
+ 2706, 2708, 2716, 2720, 2722, 2730, 2734, 2736,
+ 2744, 2748, 2750, 2778, 2782, 2784, 2813, 2844,
+ 2852, 2862, 2866, 2868, 2876, 2886, 2891, 2900,
+ 2904, 2906, 2912, 2921, 2925, 2927, 2935, 2945,
+ 2955, 2965, 2969, 2971, 2979, 2989, 2993, 2995,
+ 3003, 3011, 3015, 3017, 3024, 3032, 3037, 3042,
+ 3052, 3056, 3058, 3087, 3091, 3093, 3101
+};
+
+static const short _svg_path_indicies[] = {
+ 73, 74, 74, 75, 76, 74, 0, 571,
+ 571, 572, 572, 573, 571, 574, 0, 631,
+ 631, 632, 632, 633, 631, 634, 0, 670,
+ 513, 0, 512, 0, 509, 509, 511, 548,
+ 514, 514, 509, 510, 512, 0, 490, 490,
+ 434, 436, 490, 435, 437, 0, 393, 127,
+ 0, 126, 0, 122, 123, 123, 125, 148,
+ 128, 129, 130, 131, 132, 133, 134, 135,
+ 136, 137, 138, 139, 140, 130, 141, 142,
+ 143, 144, 145, 146, 147, 138, 123, 124,
+ 126, 0, 73, 611, 611, 613, 614, 78,
+ 79, 80, 81, 75, 82, 83, 84, 85,
+ 86, 87, 88, 89, 90, 76, 91, 92,
+ 93, 94, 86, 611, 612, 615, 0, 671,
+ 519, 0, 518, 0, 515, 515, 517, 550,
+ 520, 520, 515, 516, 518, 0, 491, 491,
+ 438, 440, 491, 439, 441, 0, 394, 155,
+ 0, 154, 0, 150, 151, 151, 153, 176,
+ 156, 157, 158, 159, 160, 161, 162, 163,
+ 164, 165, 166, 167, 168, 158, 169, 170,
+ 171, 172, 173, 174, 175, 166, 151, 152,
+ 154, 0, 613, 613, 612, 612, 614, 613,
+ 615, 0, 515, 515, 517, 518, 520, 520,
+ 515, 516, 519, 0, 438, 438, 439, 439,
+ 440, 438, 441, 0, 150, 151, 151, 153,
+ 154, 156, 157, 158, 159, 160, 161, 162,
+ 163, 164, 165, 166, 167, 168, 158, 169,
+ 170, 171, 172, 173, 174, 175, 166, 151,
+ 152, 155, 0, 47, 47, 48, 47, 49,
+ 0, 8, 8, 9, 8, 10, 0, 385,
+ 0, 25, 25, 26, 27, 31, 31, 25,
+ 498, 0, 12, 12, 13, 14, 12, 15,
+ 0, 13, 13, 14, 13, 15, 0, 35,
+ 0, 32, 32, 34, 38, 37, 37, 32,
+ 33, 35, 0, 20, 20, 16, 18, 20,
+ 17, 19, 0, 2, 3, 0, 23, 0,
+ 21, 21, 22, 24, 24, 21, 23, 0,
+ 11, 11, 5, 6, 7, 11, 0, 5,
+ 5, 6, 7, 5, 0, 45, 45, 46,
+ 45, 0, 722, 722, 716, 717, 718, 722,
+ 0, 716, 716, 717, 718, 716, 0, 567,
+ 567, 568, 567, 0, 651, 651, 647, 430,
+ 651, 429, 433, 0, 676, 69, 0, 68,
+ 0, 351, 351, 352, 375, 70, 70, 351,
+ 66, 68, 0, 497, 497, 478, 480, 497,
+ 479, 481, 0, 406, 380, 0, 379, 0,
+ 350, 377, 377, 378, 382, 355, 356, 381,
+ 358, 359, 360, 361, 362, 363, 364, 365,
+ 366, 367, 381, 368, 369, 370, 371, 372,
+ 373, 374, 365, 377, 379, 0, 73, 95,
+ 95, 8, 9, 78, 79, 80, 81, 75,
+ 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 76, 91, 92, 93, 94, 86, 95,
+ 10, 0, 25, 25, 26, 29, 31, 31,
+ 25, 30, 0, 40, 40, 41, 562, 561,
+ 561, 40, 33, 30, 0, 483, 483, 425,
+ 426, 483, 17, 427, 0, 425, 425, 17,
+ 17, 426, 425, 427, 0, 501, 0, 499,
+ 499, 500, 38, 503, 503, 499, 33, 501,
+ 0, 482, 482, 422, 18, 423, 424, 482,
+ 17, 19, 0, 422, 422, 17, 17, 18,
+ 423, 424, 422, 19, 0, 63, 63, 64,
+ 23, 24, 24, 63, 3, 0, 723, 723,
+ 719, 720, 721, 723, 0, 719, 719, 720,
+ 721, 719, 0, 569, 569, 570, 569, 0,
+ 489, 489, 428, 430, 431, 432, 489, 429,
+ 433, 0, 428, 428, 429, 429, 430, 431,
+ 432, 428, 433, 0, 71, 71, 72, 68,
+ 70, 70, 71, 66, 69, 0, 496, 496,
+ 474, 476, 496, 475, 477, 0, 405, 354,
+ 0, 353, 0, 350, 351, 351, 352, 375,
+ 355, 356, 357, 358, 359, 360, 361, 362,
+ 363, 364, 365, 366, 367, 357, 368, 369,
+ 370, 371, 372, 373, 374, 365, 351, 66,
+ 353, 0, 478, 478, 479, 479, 480, 478,
+ 481, 0, 350, 377, 377, 378, 379, 355,
+ 356, 381, 358, 359, 360, 361, 362, 363,
+ 364, 365, 366, 367, 381, 368, 369, 370,
+ 371, 372, 373, 374, 365, 377, 380, 0,
+ 50, 50, 51, 51, 52, 50, 53, 0,
+ 98, 98, 97, 97, 99, 98, 100, 0,
+ 391, 392, 0, 507, 0, 504, 504, 506,
+ 546, 508, 508, 504, 505, 507, 0, 484,
+ 484, 486, 487, 484, 485, 488, 0, 790,
+ 788, 0, 755, 0, 751, 751, 753, 754,
+ 756, 756, 751, 752, 755, 0, 787, 787,
+ 773, 775, 787, 774, 776, 0, 748, 739,
+ 0, 710, 0, 689, 689, 691, 692, 711,
+ 711, 689, 690, 710, 0, 736, 736, 724,
+ 726, 736, 725, 727, 0, 677, 656, 0,
+ 655, 0, 652, 652, 654, 704, 657, 657,
+ 652, 653, 655, 0, 648, 648, 635, 637,
+ 648, 636, 638, 0, 672, 525, 0, 524,
+ 0, 521, 521, 523, 552, 526, 526, 521,
+ 522, 524, 0, 492, 492, 458, 460, 492,
+ 459, 461, 0, 401, 243, 0, 242, 0,
+ 238, 239, 239, 241, 264, 244, 245, 246,
+ 247, 248, 249, 250, 251, 252, 253, 254,
+ 255, 256, 246, 257, 258, 259, 260, 261,
+ 262, 263, 254, 239, 240, 242, 0, 73,
+ 96, 96, 98, 99, 78, 79, 80, 81,
+ 75, 82, 83, 84, 85, 86, 87, 88,
+ 89, 90, 76, 91, 92, 93, 94, 86,
+ 96, 97, 100, 0, 504, 504, 506, 507,
+ 508, 508, 504, 505, 392, 0, 486, 486,
+ 485, 485, 487, 486, 488, 0, 751, 751,
+ 753, 755, 756, 756, 751, 752, 788, 0,
+ 773, 773, 774, 774, 775, 773, 776, 0,
+ 689, 689, 691, 710, 711, 711, 689, 690,
+ 739, 0, 724, 724, 725, 725, 726, 724,
+ 727, 0, 652, 652, 654, 655, 657, 657,
+ 652, 653, 656, 0, 635, 635, 636, 636,
+ 637, 635, 638, 0, 521, 521, 523, 524,
+ 526, 526, 521, 522, 525, 0, 458, 458,
+ 459, 459, 460, 458, 461, 0, 238, 239,
+ 239, 241, 242, 244, 245, 246, 247, 248,
+ 249, 250, 251, 252, 253, 254, 255, 256,
+ 246, 257, 258, 259, 260, 261, 262, 263,
+ 254, 239, 240, 243, 0, 416, 416, 265,
+ 0, 265, 0, 238, 239, 239, 241, 264,
+ 244, 245, 247, 248, 249, 250, 251, 252,
+ 253, 254, 255, 256, 257, 258, 259, 260,
+ 261, 262, 263, 254, 239, 240, 265, 0,
+ 583, 583, 584, 584, 585, 583, 586, 0,
+ 446, 446, 447, 447, 448, 446, 449, 0,
+ 396, 397, 0, 388, 0, 178, 179, 179,
+ 181, 204, 184, 185, 624, 187, 188, 189,
+ 190, 191, 192, 193, 194, 195, 196, 624,
+ 197, 198, 199, 200, 201, 202, 203, 194,
+ 179, 180, 623, 0, 73, 621, 621, 442,
+ 444, 78, 79, 80, 81, 75, 82, 83,
+ 84, 85, 86, 87, 88, 89, 90, 76,
+ 91, 92, 93, 94, 86, 621, 443, 445,
+ 0, 395, 183, 0, 182, 0, 178, 179,
+ 179, 181, 204, 184, 185, 186, 187, 188,
+ 189, 190, 191, 192, 193, 194, 195, 196,
+ 186, 197, 198, 199, 200, 201, 202, 203,
+ 194, 179, 180, 182, 0, 442, 442, 443,
+ 443, 444, 442, 445, 0, 178, 179, 179,
+ 181, 182, 184, 185, 186, 187, 188, 189,
+ 190, 191, 192, 193, 194, 195, 196, 186,
+ 197, 198, 199, 200, 201, 202, 203, 194,
+ 179, 180, 183, 0, 412, 412, 205, 0,
+ 205, 0, 178, 179, 179, 181, 204, 184,
+ 185, 187, 188, 189, 190, 191, 192, 193,
+ 194, 195, 196, 197, 198, 199, 200, 201,
+ 202, 203, 194, 179, 180, 205, 0, 575,
+ 575, 576, 576, 577, 575, 578, 0, 761,
+ 761, 762, 762, 763, 761, 764, 0, 781,
+ 781, 782, 782, 783, 781, 784, 0, 750,
+ 741, 0, 714, 0, 699, 699, 701, 702,
+ 715, 715, 699, 700, 714, 0, 738, 738,
+ 732, 734, 738, 733, 735, 0, 679, 668,
+ 0, 667, 0, 664, 664, 666, 708, 669,
+ 669, 664, 665, 667, 0, 650, 650, 643,
+ 645, 650, 644, 646, 0, 674, 537, 0,
+ 536, 0, 533, 533, 535, 556, 538, 538,
+ 533, 534, 536, 0, 494, 494, 466, 468,
+ 494, 467, 469, 0, 403, 299, 0, 298,
+ 0, 294, 295, 295, 297, 320, 300, 301,
+ 302, 303, 304, 305, 306, 307, 308, 309,
+ 310, 311, 312, 302, 313, 314, 315, 316,
+ 317, 318, 319, 310, 295, 296, 298, 0,
+ 73, 786, 786, 781, 783, 78, 79, 80,
+ 81, 75, 82, 83, 84, 85, 86, 87,
+ 88, 89, 90, 76, 91, 92, 93, 94,
+ 86, 786, 782, 784, 0, 699, 699, 701,
+ 714, 715, 715, 699, 700, 741, 0, 732,
+ 732, 733, 733, 734, 732, 735, 0, 664,
+ 664, 666, 667, 669, 669, 664, 665, 668,
+ 0, 643, 643, 644, 644, 645, 643, 646,
+ 0, 533, 533, 535, 536, 538, 538, 533,
+ 534, 537, 0, 466, 466, 467, 467, 468,
+ 466, 469, 0, 294, 295, 295, 297, 298,
+ 300, 301, 302, 303, 304, 305, 306, 307,
+ 308, 309, 310, 311, 312, 302, 313, 314,
+ 315, 316, 317, 318, 319, 310, 295, 296,
+ 299, 0, 418, 418, 321, 0, 321, 0,
+ 294, 295, 295, 297, 320, 300, 301, 303,
+ 304, 305, 306, 307, 308, 309, 310, 311,
+ 312, 313, 314, 315, 316, 317, 318, 319,
+ 310, 295, 296, 321, 0, 757, 757, 758,
+ 758, 759, 757, 760, 0, 777, 777, 778,
+ 778, 779, 777, 780, 0, 749, 740, 0,
+ 712, 0, 694, 694, 696, 697, 713, 713,
+ 694, 695, 712, 0, 737, 737, 728, 730,
+ 737, 729, 731, 0, 678, 662, 0, 661,
+ 0, 658, 658, 660, 706, 663, 663, 658,
+ 659, 661, 0, 649, 649, 639, 641, 649,
+ 640, 642, 0, 673, 531, 0, 530, 0,
+ 527, 527, 529, 554, 532, 532, 527, 528,
+ 530, 0, 493, 493, 462, 464, 493, 463,
+ 465, 0, 402, 271, 0, 270, 0, 266,
+ 267, 267, 269, 292, 272, 273, 274, 275,
+ 276, 277, 278, 279, 280, 281, 282, 283,
+ 284, 274, 285, 286, 287, 288, 289, 290,
+ 291, 282, 267, 268, 270, 0, 73, 785,
+ 785, 777, 779, 78, 79, 80, 81, 75,
+ 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 76, 91, 92, 93, 94, 86, 785,
+ 778, 780, 0, 694, 694, 696, 712, 713,
+ 713, 694, 695, 740, 0, 728, 728, 729,
+ 729, 730, 728, 731, 0, 658, 658, 660,
+ 661, 663, 663, 658, 659, 662, 0, 639,
+ 639, 640, 640, 641, 639, 642, 0, 527,
+ 527, 529, 530, 532, 532, 527, 528, 531,
+ 0, 462, 462, 463, 463, 464, 462, 465,
+ 0, 266, 267, 267, 269, 270, 272, 273,
+ 274, 275, 276, 277, 278, 279, 280, 281,
+ 282, 283, 284, 274, 285, 286, 287, 288,
+ 289, 290, 291, 282, 267, 268, 271, 0,
+ 417, 417, 293, 0, 293, 0, 266, 267,
+ 267, 269, 292, 272, 273, 275, 276, 277,
+ 278, 279, 280, 281, 282, 283, 284, 285,
+ 286, 287, 288, 289, 290, 291, 282, 267,
+ 268, 293, 0, 579, 579, 580, 580, 581,
+ 579, 582, 0, 618, 618, 617, 617, 619,
+ 618, 620, 0, 675, 543, 0, 542, 0,
+ 539, 539, 541, 558, 544, 544, 539, 540,
+ 542, 0, 495, 495, 470, 472, 495, 471,
+ 473, 0, 404, 327, 0, 326, 0, 322,
+ 323, 323, 325, 348, 328, 329, 330, 331,
+ 332, 333, 334, 335, 336, 337, 338, 339,
+ 340, 330, 341, 342, 343, 344, 345, 346,
+ 347, 338, 323, 324, 326, 0, 73, 616,
+ 616, 618, 619, 78, 79, 80, 81, 75,
+ 82, 83, 84, 85, 86, 87, 88, 89,
+ 90, 76, 91, 92, 93, 94, 86, 616,
+ 617, 620, 0, 539, 539, 541, 542, 544,
+ 544, 539, 540, 543, 0, 470, 470, 471,
+ 471, 472, 470, 473, 0, 322, 323, 323,
+ 325, 326, 328, 329, 330, 331, 332, 333,
+ 334, 335, 336, 337, 338, 339, 340, 330,
+ 341, 342, 343, 344, 345, 346, 347, 338,
+ 323, 324, 327, 0, 419, 419, 349, 0,
+ 349, 0, 322, 323, 323, 325, 348, 328,
+ 329, 331, 332, 333, 334, 335, 336, 337,
+ 338, 339, 340, 341, 342, 343, 344, 345,
+ 346, 347, 338, 323, 324, 349, 0, 587,
+ 587, 588, 588, 589, 587, 590, 0, 454,
+ 454, 455, 455, 456, 454, 457, 0, 399,
+ 400, 0, 390, 0, 208, 209, 209, 211,
+ 234, 214, 215, 628, 217, 218, 219, 220,
+ 221, 222, 223, 224, 225, 226, 628, 227,
+ 228, 229, 230, 231, 232, 233, 224, 209,
+ 210, 627, 0, 73, 622, 622, 450, 452,
+ 78, 79, 80, 81, 75, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 76, 91,
+ 92, 93, 94, 86, 622, 451, 453, 0,
+ 398, 213, 0, 212, 0, 208, 209, 209,
+ 211, 234, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 216,
+ 227, 228, 229, 230, 231, 232, 233, 224,
+ 209, 210, 212, 0, 450, 450, 451, 451,
+ 452, 450, 453, 0, 208, 209, 209, 211,
+ 212, 214, 215, 216, 217, 218, 219, 220,
+ 221, 222, 223, 224, 225, 226, 216, 227,
+ 228, 229, 230, 231, 232, 233, 224, 209,
+ 210, 213, 0, 414, 414, 235, 0, 235,
+ 0, 208, 209, 209, 211, 234, 214, 215,
+ 217, 218, 219, 220, 221, 222, 223, 224,
+ 225, 226, 227, 228, 229, 230, 231, 232,
+ 233, 224, 209, 210, 235, 0, 101, 102,
+ 102, 103, 104, 105, 106, 107, 108, 109,
+ 110, 111, 112, 113, 114, 115, 116, 117,
+ 118, 119, 120, 121, 112, 102, 0, 73,
+ 77, 77, 78, 79, 80, 81, 75, 82,
+ 83, 84, 85, 86, 87, 88, 89, 90,
+ 76, 91, 92, 93, 94, 86, 77, 0,
+ 54, 54, 55, 54, 56, 0, 57, 57,
+ 58, 58, 59, 57, 60, 0, 603, 603,
+ 604, 604, 605, 603, 606, 0, 178, 179,
+ 179, 181, 625, 184, 185, 624, 187, 188,
+ 189, 190, 191, 192, 193, 194, 195, 196,
+ 624, 197, 198, 199, 200, 201, 202, 203,
+ 194, 179, 180, 626, 0, 413, 413, 387,
+ 0, 387, 0, 178, 179, 179, 181, 204,
+ 184, 185, 187, 188, 189, 190, 191, 192,
+ 193, 194, 195, 196, 197, 198, 199, 200,
+ 201, 202, 203, 194, 179, 180, 206, 0,
+ 178, 179, 179, 181, 207, 184, 185, 186,
+ 187, 188, 189, 190, 191, 192, 193, 194,
+ 195, 196, 186, 197, 198, 199, 200, 201,
+ 202, 203, 194, 179, 180, 206, 0, 595,
+ 595, 596, 596, 597, 595, 598, 0, 591,
+ 591, 592, 592, 593, 591, 594, 0, 509,
+ 509, 511, 512, 514, 514, 509, 510, 513,
+ 0, 434, 434, 435, 435, 436, 434, 437,
+ 0, 122, 123, 123, 125, 126, 128, 129,
+ 130, 131, 132, 133, 134, 135, 136, 137,
+ 138, 139, 140, 130, 141, 142, 143, 144,
+ 145, 146, 147, 138, 123, 124, 127, 0,
+ 410, 410, 149, 0, 149, 0, 122, 123,
+ 123, 125, 148, 128, 129, 131, 132, 133,
+ 134, 135, 136, 137, 138, 139, 140, 141,
+ 142, 143, 144, 145, 146, 147, 138, 123,
+ 124, 149, 0, 769, 769, 770, 770, 771,
+ 769, 772, 0, 765, 765, 766, 766, 767,
+ 765, 768, 0, 599, 599, 600, 600, 601,
+ 599, 602, 0, 607, 607, 608, 608, 609,
+ 607, 610, 0, 208, 209, 209, 211, 629,
+ 214, 215, 628, 217, 218, 219, 220, 221,
+ 222, 223, 224, 225, 226, 628, 227, 228,
+ 229, 230, 231, 232, 233, 224, 209, 210,
+ 630, 0, 415, 415, 389, 0, 389, 0,
+ 208, 209, 209, 211, 234, 214, 215, 217,
+ 218, 219, 220, 221, 222, 223, 224, 225,
+ 226, 227, 228, 229, 230, 231, 232, 233,
+ 224, 209, 210, 236, 0, 208, 209, 209,
+ 211, 237, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 216,
+ 227, 228, 229, 230, 231, 232, 233, 224,
+ 209, 210, 236, 0, 682, 682, 549, 0,
+ 549, 0, 509, 509, 511, 548, 509, 510,
+ 549, 0, 208, 209, 209, 211, 237, 214,
+ 215, 628, 217, 218, 219, 220, 221, 222,
+ 223, 224, 225, 226, 628, 227, 228, 229,
+ 230, 231, 232, 233, 224, 209, 210, 627,
+ 0, 687, 687, 559, 0, 559, 0, 539,
+ 539, 541, 558, 539, 540, 559, 0, 685,
+ 685, 555, 0, 555, 0, 527, 527, 529,
+ 554, 527, 528, 555, 0, 746, 746, 707,
+ 0, 707, 0, 658, 658, 660, 706, 658,
+ 659, 707, 0, 743, 743, 698, 0, 698,
+ 0, 694, 694, 696, 697, 694, 695, 698,
+ 0, 686, 686, 557, 0, 557, 0, 533,
+ 533, 535, 556, 533, 534, 557, 0, 747,
+ 747, 709, 0, 709, 0, 664, 664, 666,
+ 708, 664, 665, 709, 0, 744, 744, 703,
+ 0, 703, 0, 699, 699, 701, 702, 699,
+ 700, 703, 0, 178, 179, 179, 181, 207,
+ 184, 185, 624, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 624, 197, 198,
+ 199, 200, 201, 202, 203, 194, 179, 180,
+ 623, 0, 684, 684, 553, 0, 553, 0,
+ 521, 521, 523, 552, 521, 522, 553, 0,
+ 745, 745, 705, 0, 705, 0, 652, 652,
+ 654, 704, 652, 653, 705, 0, 742, 742,
+ 693, 0, 693, 0, 689, 689, 691, 692,
+ 689, 690, 693, 0, 791, 791, 789, 0,
+ 789, 0, 751, 751, 753, 754, 751, 752,
+ 789, 0, 681, 681, 547, 0, 547, 0,
+ 504, 504, 506, 546, 504, 505, 547, 0,
+ 421, 421, 383, 0, 383, 0, 350, 377,
+ 377, 378, 382, 355, 356, 358, 359, 360,
+ 361, 362, 363, 364, 365, 366, 367, 368,
+ 369, 370, 371, 372, 373, 374, 365, 377,
+ 383, 0, 420, 420, 376, 0, 376, 0,
+ 350, 351, 351, 352, 375, 355, 356, 358,
+ 359, 360, 361, 362, 363, 364, 365, 366,
+ 367, 368, 369, 370, 371, 372, 373, 374,
+ 365, 351, 66, 376, 0, 350, 351, 351,
+ 352, 353, 355, 356, 357, 358, 359, 360,
+ 361, 362, 363, 364, 365, 366, 367, 357,
+ 368, 369, 370, 371, 372, 373, 374, 365,
+ 351, 66, 354, 0, 474, 474, 475, 475,
+ 476, 474, 477, 0, 351, 351, 352, 68,
+ 70, 70, 351, 66, 69, 0, 688, 688,
+ 560, 0, 560, 0, 351, 351, 352, 375,
+ 351, 66, 560, 0, 65, 65, 67, 68,
+ 70, 70, 65, 66, 69, 0, 565, 565,
+ 566, 565, 0, 21, 21, 22, 23, 24,
+ 24, 21, 3, 0, 4, 4, 1, 0,
+ 1, 0, 21, 21, 22, 21, 1, 0,
+ 61, 61, 62, 23, 24, 24, 61, 3,
+ 0, 680, 680, 545, 0, 545, 0, 499,
+ 499, 500, 38, 499, 33, 545, 0, 499,
+ 499, 500, 501, 503, 503, 499, 33, 502,
+ 0, 40, 40, 41, 27, 561, 561, 40,
+ 33, 498, 0, 40, 40, 41, 42, 561,
+ 561, 40, 33, 498, 0, 409, 409, 386,
+ 0, 386, 0, 40, 40, 41, 27, 40,
+ 33, 28, 0, 40, 40, 41, 42, 37,
+ 37, 40, 33, 28, 0, 408, 408, 39,
+ 0, 39, 0, 32, 32, 34, 38, 32,
+ 33, 39, 0, 16, 16, 17, 17, 18,
+ 16, 19, 0, 407, 407, 384, 0, 384,
+ 0, 25, 25, 26, 27, 25, 28, 0,
+ 647, 647, 429, 429, 430, 647, 433, 0,
+ 563, 563, 564, 563, 0, 43, 43, 44,
+ 43, 0, 32, 32, 34, 35, 37, 37,
+ 32, 33, 36, 0, 411, 411, 177, 0,
+ 177, 0, 150, 151, 151, 153, 176, 156,
+ 157, 159, 160, 161, 162, 163, 164, 165,
+ 166, 167, 168, 169, 170, 171, 172, 173,
+ 174, 175, 166, 151, 152, 177, 0, 683,
+ 683, 551, 0, 551, 0, 515, 515, 517,
+ 550, 515, 516, 551, 0, 0, 0
+};
+
+static const short _svg_path_trans_targs_wi[] = {
+ 1, 297, 33, 294, 296, 36, 37, 318,
+ 24, 25, 50, 35, 27, 28, 29, 319,
+ 312, 32, 33, 294, 31, 35, 36, 34,
+ 295, 27, 28, 29, 308, 26, 51, 313,
+ 31, 32, 312, 30, 319, 309, 33, 311,
+ 52, 53, 30, 38, 39, 38, 39, 24,
+ 25, 50, 72, 73, 74, 97, 24, 25,
+ 50, 72, 73, 74, 97, 59, 60, 59,
+ 60, 65, 46, 287, 44, 288, 289, 65,
+ 287, 326, 0, 2, 223, 213, 23, 71,
+ 111, 125, 126, 154, 182, 198, 212, 214,
+ 215, 216, 222, 230, 231, 232, 233, 49,
+ 96, 73, 72, 74, 97, 326, 213, 23,
+ 71, 111, 125, 2, 126, 154, 182, 198,
+ 212, 214, 215, 216, 222, 223, 230, 231,
+ 232, 233, 326, 11, 12, 19, 10, 226,
+ 23, 71, 227, 111, 125, 2, 126, 154,
+ 182, 198, 212, 214, 215, 216, 222, 223,
+ 230, 231, 232, 233, 13, 229, 326, 11,
+ 12, 19, 18, 22, 23, 71, 320, 111,
+ 125, 2, 126, 154, 182, 198, 212, 214,
+ 215, 216, 222, 223, 230, 231, 232, 233,
+ 13, 322, 326, 116, 117, 120, 119, 121,
+ 23, 71, 122, 111, 125, 2, 126, 154,
+ 182, 198, 212, 214, 215, 216, 222, 223,
+ 230, 231, 232, 233, 118, 124, 221, 119,
+ 326, 203, 204, 207, 206, 208, 23, 71,
+ 209, 111, 125, 2, 126, 154, 182, 198,
+ 212, 214, 215, 216, 222, 223, 230, 231,
+ 232, 233, 205, 211, 238, 206, 326, 96,
+ 73, 72, 95, 107, 23, 71, 108, 111,
+ 125, 2, 126, 154, 182, 198, 212, 214,
+ 215, 216, 222, 223, 230, 231, 232, 233,
+ 74, 110, 326, 171, 156, 155, 170, 178,
+ 23, 71, 179, 111, 125, 2, 126, 154,
+ 182, 198, 212, 214, 215, 216, 222, 223,
+ 230, 231, 232, 233, 157, 181, 326, 143,
+ 128, 127, 142, 150, 23, 71, 151, 111,
+ 125, 2, 126, 154, 182, 198, 212, 214,
+ 215, 216, 222, 223, 230, 231, 232, 233,
+ 129, 153, 326, 191, 184, 183, 190, 194,
+ 23, 71, 195, 111, 125, 2, 126, 154,
+ 182, 198, 212, 214, 215, 216, 222, 223,
+ 230, 231, 232, 233, 185, 197, 326, 45,
+ 69, 68, 286, 23, 71, 283, 111, 125,
+ 2, 126, 154, 182, 198, 212, 214, 215,
+ 216, 222, 223, 230, 231, 232, 233, 47,
+ 285, 49, 24, 48, 70, 280, 25, 282,
+ 315, 26, 307, 220, 115, 237, 202, 74,
+ 97, 9, 17, 118, 114, 217, 205, 201,
+ 234, 94, 169, 141, 189, 67, 47, 314,
+ 310, 306, 228, 321, 123, 219, 210, 236,
+ 109, 180, 152, 196, 284, 281, 57, 58,
+ 298, 53, 54, 302, 63, 42, 43, 64,
+ 292, 288, 225, 8, 9, 226, 21, 16,
+ 17, 22, 120, 117, 118, 121, 112, 113,
+ 114, 217, 207, 204, 205, 208, 199, 200,
+ 201, 234, 106, 93, 94, 107, 177, 168,
+ 169, 178, 149, 140, 141, 150, 193, 188,
+ 189, 194, 287, 66, 67, 286, 69, 46,
+ 47, 70, 56, 52, 76, 77, 98, 78,
+ 99, 62, 7, 15, 92, 167, 139, 187,
+ 65, 45, 304, 56, 57, 55, 302, 299,
+ 76, 77, 98, 75, 277, 7, 8, 225,
+ 6, 224, 239, 15, 16, 21, 14, 20,
+ 323, 92, 93, 106, 91, 105, 265, 167,
+ 168, 177, 166, 176, 246, 139, 140, 149,
+ 138, 148, 255, 187, 188, 193, 186, 192,
+ 243, 301, 78, 279, 9, 241, 17, 325,
+ 94, 267, 169, 248, 141, 257, 189, 245,
+ 291, 305, 303, 41, 316, 62, 63, 41,
+ 316, 62, 63, 3, 4, 5, 224, 19,
+ 12, 13, 20, 183, 184, 185, 192, 112,
+ 113, 114, 217, 199, 200, 201, 234, 3,
+ 4, 5, 224, 19, 12, 13, 20, 183,
+ 184, 185, 192, 112, 113, 114, 217, 199,
+ 200, 201, 234, 11, 12, 19, 13, 20,
+ 191, 184, 183, 185, 192, 116, 203, 264,
+ 218, 115, 217, 242, 235, 202, 234, 3,
+ 4, 5, 224, 104, 89, 90, 105, 175,
+ 164, 165, 176, 147, 136, 137, 148, 316,
+ 88, 163, 135, 41, 88, 89, 104, 87,
+ 103, 268, 163, 164, 175, 162, 174, 249,
+ 135, 136, 147, 134, 146, 258, 5, 13,
+ 90, 165, 137, 185, 43, 86, 161, 133,
+ 300, 278, 240, 324, 266, 247, 256, 244,
+ 290, 84, 85, 102, 86, 273, 159, 160,
+ 173, 161, 254, 131, 132, 145, 133, 263,
+ 90, 270, 165, 251, 137, 260, 83, 271,
+ 158, 252, 130, 261, 39, 40, 317, 60,
+ 61, 293, 38, 59, 102, 85, 86, 103,
+ 173, 160, 161, 174, 145, 132, 133, 146,
+ 84, 159, 131, 101, 172, 144, 272, 253,
+ 262, 269, 250, 259, 82, 157, 129, 80,
+ 81, 100, 82, 79, 274, 155, 156, 157,
+ 172, 127, 128, 129, 144, 155, 156, 157,
+ 172, 127, 128, 129, 144, 100, 81, 82,
+ 101, 155, 156, 157, 172, 127, 128, 129,
+ 144, 171, 143, 80, 99, 276, 78, 275
+};
+
+static const unsigned char _svg_path_trans_actions_wi[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 1, 1,
+ 0, 1, 1, 1, 0, 3, 3, 0,
+ 0, 3, 3, 17, 17, 17, 17, 0,
+ 3, 17, 3, 0, 0, 0, 17, 0,
+ 3, 3, 17, 5, 5, 7, 7, 9,
+ 59, 59, 9, 59, 59, 59, 11, 62,
+ 62, 11, 62, 62, 62, 20, 20, 23,
+ 23, 53, 17, 53, 0, 0, 0, 56,
+ 56, 15, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 1, 1, 65, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 72, 26, 68, 26, 0, 0,
+ 26, 26, 0, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 26, 26, 26, 26,
+ 26, 26, 26, 26, 68, 0, 80, 29,
+ 76, 29, 0, 0, 29, 29, 0, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29,
+ 29, 29, 29, 29, 29, 29, 29, 29,
+ 76, 0, 88, 32, 84, 32, 0, 0,
+ 32, 32, 0, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 84, 0, 84, 84,
+ 96, 35, 92, 35, 0, 0, 35, 35,
+ 0, 35, 35, 35, 35, 35, 35, 35,
+ 35, 35, 35, 35, 35, 35, 35, 35,
+ 35, 35, 92, 0, 92, 92, 104, 38,
+ 100, 38, 0, 0, 38, 38, 0, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38,
+ 100, 0, 112, 41, 108, 41, 0, 0,
+ 41, 41, 0, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 41, 41, 41, 41,
+ 41, 41, 41, 41, 108, 0, 120, 44,
+ 116, 44, 0, 0, 44, 44, 0, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 44, 44, 44, 44, 44, 44,
+ 116, 0, 128, 47, 124, 47, 0, 0,
+ 47, 47, 0, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 47, 47, 47, 47,
+ 47, 47, 47, 47, 124, 0, 136, 3,
+ 3, 0, 0, 50, 50, 0, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 50,
+ 50, 50, 50, 50, 50, 50, 50, 17,
+ 0, 50, 50, 0, 0, 0, 132, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 1, 1, 0, 1, 1, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 1, 1, 1, 0, 1,
+ 1, 1, 0, 0, 0, 1, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 17, 3, 3, 0, 0, 0,
+ 3, 17, 3, 0, 0, 3, 17, 3,
+ 0, 0, 0, 3, 17, 3, 0, 0,
+ 0, 3, 17, 3, 0, 0, 0, 3,
+ 17, 3, 0, 0, 0, 3, 17, 3,
+ 0, 0, 0, 3, 17, 3, 0, 0,
+ 0, 0, 17, 0, 17, 0, 17, 0,
+ 17, 0, 17, 0, 17, 0, 17, 0,
+ 0, 0, 17, 5, 5, 5, 5, 7,
+ 7, 7, 7, 9, 59, 59, 59, 9,
+ 59, 59, 59, 9, 59, 59, 59, 9,
+ 59, 59, 59, 9, 59, 59, 59, 11,
+ 62, 62, 62, 11, 62, 62, 62, 11,
+ 62, 62, 62, 11, 62, 62, 62, 11,
+ 62, 62, 62, 0, 1, 0, 1, 1,
+ 0, 1, 0, 1, 1, 0, 0, 84,
+ 0, 84, 84, 92, 0, 92, 92, 0,
+ 1, 1, 1, 0, 1, 1, 1, 0,
+ 1, 1, 1, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 3, 17, 3, 0,
+ 0, 0, 3, 17, 3, 0, 0, 0,
+ 3, 17, 3, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 3, 17, 3, 17, 0, 3, 17,
+ 3, 17, 0, 3, 17, 3, 17, 0,
+ 17, 0, 17, 0, 17, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 1, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 3,
+ 17, 3, 17, 0, 0, 9, 59, 59,
+ 59, 9, 59, 59, 59, 11, 62, 62,
+ 62, 11, 62, 62, 62, 0, 1, 1,
+ 1, 0, 1, 1, 1, 0, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const int svg_path_start = 0;
+
+static const int svg_path_first_final = 326;
+
+#line 133 "/home/michael/2geom/src/svg-path-parser.rl"
+
+
+void Parser::parse(char const *str)
+throw(SVGPathParseError)
+{
+ char const *p = str;
+ char const *start = NULL;
+ int cs;
+
+ _reset();
+
+
+#line 1373 "/home/michael/2geom/src/svg-path-parser.cpp"
+ {
+ cs = svg_path_start;
+ }
+ {
+ int _klen;
+ unsigned int _trans;
+ const char *_acts;
+ unsigned int _nacts;
+ const char *_keys;
+
+_resume:
+ if ( cs == 1 )
+ goto _out;
+ _keys = _svg_path_trans_keys + _svg_path_key_offsets[cs];
+ _trans = _svg_path_index_offsets[cs];
+
+ _klen = _svg_path_single_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + _klen - 1;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + ((_upper-_lower) >> 1);
+ if ( (*p) < *_mid )
+ _upper = _mid - 1;
+ else if ( (*p) > *_mid )
+ _lower = _mid + 1;
+ else {
+ _trans += (_mid - _keys);
+ goto _match;
+ }
+ }
+ _keys += _klen;
+ _trans += _klen;
+ }
+
+ _klen = _svg_path_range_lengths[cs];
+ if ( _klen > 0 ) {
+ const char *_lower = _keys;
+ const char *_mid;
+ const char *_upper = _keys + (_klen<<1) - 2;
+ while (1) {
+ if ( _upper < _lower )
+ break;
+
+ _mid = _lower + (((_upper-_lower) >> 1) & ~1);
+ if ( (*p) < _mid[0] )
+ _upper = _mid - 2;
+ else if ( (*p) > _mid[1] )
+ _lower = _mid + 2;
+ else {
+ _trans += ((_mid - _keys)>>1);
+ goto _match;
+ }
+ }
+ _trans += _klen;
+ }
+
+_match:
+ _trans = _svg_path_indicies[_trans];
+ cs = _svg_path_trans_targs_wi[_trans];
+
+ if ( _svg_path_trans_actions_wi[_trans] == 0 )
+ goto _again;
+
+ _acts = _svg_path_actions + _svg_path_trans_actions_wi[_trans];
+ _nacts = (unsigned int) *_acts++;
+ while ( _nacts-- > 0 )
+ {
+ switch ( *_acts++ )
+ {
+ case 0:
+#line 145 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ start = p;
+ }
+ break;
+ case 1:
+#line 149 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ char const *end=p;
+ std::string buf(start, end);
+ _push(g_ascii_strtod(buf.c_str(), NULL));
+ start = NULL;
+ }
+ break;
+ case 2:
+#line 156 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _push(1.0);
+ }
+ break;
+ case 3:
+#line 160 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _push(0.0);
+ }
+ break;
+ case 4:
+#line 164 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _absolute = true;
+ }
+ break;
+ case 5:
+#line 168 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _absolute = false;
+ }
+ break;
+ case 6:
+#line 172 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _moveTo(_pop_point());
+ }
+ break;
+ case 7:
+#line 176 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _lineTo(_pop_point());
+ }
+ break;
+ case 8:
+#line 180 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _lineTo(Point(_pop_coord(X), _current[Y]));
+ }
+ break;
+ case 9:
+#line 184 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _lineTo(Point(_current[X], _pop_coord(Y)));
+ }
+ break;
+ case 10:
+#line 188 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ Point c0 = _pop_point();
+ _curveTo(c0, c1, p);
+ }
+ break;
+ case 11:
+#line 195 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c1 = _pop_point();
+ _curveTo(_cubic_tangent, c1, p);
+ }
+ break;
+ case 12:
+#line 201 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ Point c = _pop_point();
+ _quadTo(c, p);
+ }
+ break;
+ case 13:
+#line 207 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ Point p = _pop_point();
+ _quadTo(_quad_tangent, p);
+ }
+ break;
+ case 14:
+#line 212 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ Point point = _pop_point();
+ bool sweep = _pop_flag();
+ bool large_arc = _pop_flag();
+ double angle = _pop();
+ double ry = _pop();
+ double rx = _pop();
+
+ _arcTo(rx, ry, angle, large_arc, sweep, point);
+ }
+ break;
+ case 15:
+#line 223 "/home/michael/2geom/src/svg-path-parser.rl"
+ {
+ _closePath();
+ }
+ break;
+ case 16:
+#line 360 "/home/michael/2geom/src/svg-path-parser.rl"
+ {goto _out;}
+ break;
+#line 1566 "/home/michael/2geom/src/svg-path-parser.cpp"
+ }
+ }
+
+_again:
+ p += 1;
+ goto _resume;
+ _out: {}
+ }
+#line 370 "/home/michael/2geom/src/svg-path-parser.rl"
+
+
+ if ( cs < svg_path_first_final ) {
+ throw SVGPathParseError();
+ }
+}
+
+}
+
+void parse_svg_path(char const *str, SVGPathSink &sink)
+throw(SVGPathParseError)
+{
+ Parser parser(sink);
+ parser.parse(str);
+ sink.finish();
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path-parser.h b/src/2geom/svg-path-parser.h
new file mode 100644
index 000000000..4017df458
--- /dev/null
+++ b/src/2geom/svg-path-parser.h
@@ -0,0 +1,81 @@
+/*
+ * parse SVG path specifications
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ * Copyright 2007 Aaron Spike <aaron@ekips.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_SVG_PATH_PARSER_H
+#define SEEN_SVG_PATH_PARSER_H
+
+#include <vector>
+#include <iterator>
+#include <exception>
+#include "point.h"
+#include "svg-path.h"
+
+namespace Geom {
+
+struct SVGPathParseError : public std::exception {
+ char const *what() const throw() { return "parse error"; }
+};
+
+void parse_svg_path(char const *str, SVGPathSink &sink) throw(SVGPathParseError);
+
+inline std::vector<Path> parse_svg_path(char const *str) throw(SVGPathParseError) {
+ /*PathBuilder b;
+ parse_svg_path(str, b);
+ return b.peek();*/
+ std::vector<Path> subpaths;
+ std::back_insert_iterator<std::vector<Path> > iter(subpaths);
+ SVGPathGenerator<std::back_insert_iterator<std::vector<Path> > > generator(iter);
+ parse_svg_path(str, generator);
+ return subpaths;
+}
+
+inline std::vector<Path> read_svgd(char const * name) throw(SVGPathParseError) {
+ FILE* fi = fopen(name, "r");
+ if(fi == NULL) throw(std::runtime_error("Error opening file"));
+ char input[1024 * 10];
+ fgets(input, 1024 * 10, fi);
+ fclose(fi);
+ return parse_svg_path(input);
+}
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path.cpp b/src/2geom/svg-path.cpp
new file mode 100644
index 000000000..141ddbcf3
--- /dev/null
+++ b/src/2geom/svg-path.cpp
@@ -0,0 +1,97 @@
+/*
+ * callback interface for SVG path data
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, output to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include "sbasis-to-bezier.h"
+#include "svg-path.h"
+
+namespace Geom {
+
+void output(Curve const &curve, SVGPathSink &sink) {
+ std::vector<Point> pts = sbasis_to_bezier(curve.sbasis(), 2); //TODO: use something better!
+ sink.curveTo(pts[0], pts[1], pts[2]);
+}
+
+void output(LineSegment const &curve, SVGPathSink &sink) {
+ sink.lineTo(curve[1]);
+}
+
+void output(CubicBezier const &curve, SVGPathSink &sink) {
+ sink.curveTo(curve[1], curve[2], curve[3]);
+}
+
+void output(QuadraticBezier const &curve, SVGPathSink &sink) {
+ sink.quadTo(curve[1], curve[2]);
+}
+
+void output(SVGEllipticalArc const &curve, SVGPathSink &sink) {
+ // FIXME
+}
+
+template <typename T>
+bool output_as(Curve const &curve, SVGPathSink &sink) {
+ T const *t = dynamic_cast<T const *>(&curve);
+ if (t) {
+ output(*t, sink);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void output_svg_path(Path &path, SVGPathSink &sink) {
+ sink.moveTo(path.front().initialPoint());
+
+ Path::iterator iter;
+ for ( iter = path.begin() ; iter != path.end() ; ++iter ) {
+ output_as<LineSegment>(*iter, sink) ||
+ output_as<CubicBezier>(*iter, sink) ||
+ output_as<QuadraticBezier>(*iter, sink) ||
+ output_as<SVGEllipticalArc>(*iter, sink) ||
+ output_as<Curve>(*iter, sink);
+ }
+
+ if (path.closed()) {
+ sink.closePath();
+ }
+ sink.finish();
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/svg-path.h b/src/2geom/svg-path.h
new file mode 100644
index 000000000..d09002220
--- /dev/null
+++ b/src/2geom/svg-path.h
@@ -0,0 +1,126 @@
+/*
+ * callback interface for SVG path data
+ *
+ * Copyright 2007 MenTaLguY <mental@rydia.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#ifndef SEEN_SVG_PATH_H
+#define SEEN_SVG_PATH_H
+
+#include "path.h"
+#include <iterator>
+
+namespace Geom {
+
+class SVGPathSink {
+public:
+ virtual void moveTo(Point p) = 0;
+ virtual void lineTo(Point p) = 0;
+ virtual void curveTo(Point c0, Point c1, Point p) = 0;
+ virtual void quadTo(Point c, Point p) = 0;
+ virtual void arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point p) = 0;
+ virtual void closePath() = 0;
+ virtual void finish() = 0;
+};
+
+void output_svg_path(Path &path, SVGPathSink &sink);
+
+template <typename OutputIterator>
+class SVGPathGenerator : public SVGPathSink {
+public:
+ explicit SVGPathGenerator(OutputIterator out)
+ : _in_path(false), _out(out) {}
+
+ void moveTo(Point p) {
+ finish();
+ _path.start(p);
+ _in_path = true;
+ }
+//TODO: what if _in_path = false?
+ void lineTo(Point p) {
+ _path.appendNew<LineSegment>(p);
+ }
+
+ void curveTo(Point c0, Point c1, Point p) {
+ _path.appendNew<CubicBezier>(c0, c1, p);
+ }
+
+ void quadTo(Point c, Point p) {
+ _path.appendNew<QuadraticBezier>(c, p);
+ }
+
+ void arcTo(double rx, double ry, double angle,
+ bool large_arc, bool sweep, Point p)
+ {
+ _path.appendNew<SVGEllipticalArc>(rx, ry, angle,
+ large_arc, sweep, p);
+ }
+
+ void closePath() {
+ _path.close();
+ finish();
+ }
+
+ void finish() {
+ if (_in_path) {
+ _in_path = false;
+ *_out = _path;
+ _path.clear();
+ _path.close(false);
+ }
+ }
+
+protected:
+ bool _in_path;
+ OutputIterator _out;
+ Path _path;
+};
+
+typedef std::back_insert_iterator<std::vector<Path> > iter;
+
+class PathBuilder : public SVGPathGenerator<iter> {
+private:
+ std::vector<Path> _pathset;
+public:
+ PathBuilder() : SVGPathGenerator<iter>(iter(_pathset)) {}
+ std::vector<Path> const &peek() const {return _pathset;}
+};
+
+}
+
+#endif
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/transforms.cpp b/src/2geom/transforms.cpp
new file mode 100644
index 000000000..8c0164338
--- /dev/null
+++ b/src/2geom/transforms.cpp
@@ -0,0 +1,48 @@
+#include "transforms.h"
+
+namespace Geom {
+
+Matrix operator*(Translate const &t, Scale const &s) {
+ Matrix ret(s);
+ ret[4] = t[X] * s[X];
+ ret[5] = t[Y] * s[Y];
+ return ret;
+}
+
+Matrix operator*(Translate const &t, Rotate const &r) {
+ Matrix ret(r);
+ ret.setTranslation(t.vec * ret);
+ return ret;
+}
+
+Matrix operator*(Scale const &s, Translate const &t) {
+ return Matrix(s[0], 0,
+ 0 , s[1],
+ t[0], t[1]);
+}
+
+Matrix operator*(Scale const &s, Matrix const &m) {
+ Matrix ret(m);
+ ret[0] *= s[X];
+ ret[1] *= s[X];
+ ret[2] *= s[Y];
+ ret[3] *= s[Y];
+ return ret;
+}
+
+Matrix operator*(Matrix const &m, Translate const &t) {
+ Matrix ret(m);
+ ret[4] += t[X];
+ ret[5] += t[Y];
+ return ret;
+}
+
+Matrix operator*(Matrix const &m, Scale const &s) {
+ Matrix ret(m);
+ ret[0] *= s[X]; ret[1] *= s[Y];
+ ret[2] *= s[X]; ret[3] *= s[Y];
+ ret[4] *= s[X]; ret[5] *= s[Y];
+ return ret;
+}
+
+}
diff --git a/src/2geom/transforms.h b/src/2geom/transforms.h
new file mode 100644
index 000000000..1cd1d3e5f
--- /dev/null
+++ b/src/2geom/transforms.h
@@ -0,0 +1,131 @@
+#ifndef SEEN_Geom_TRANSFORMS_H
+#define SEEN_Geom_TRANSFORMS_H
+
+#include "matrix.h"
+#include <cmath>
+
+namespace Geom {
+
+template <typename T>
+struct TransformConcept {
+ T t;
+ Matrix m;
+ Point p;
+ void constraints() {
+ m = t; //implicit conversion
+ t = t.inverse();
+ p = p * t;
+ t = t * t;
+ }
+};
+
+
+class Rotate;
+class Translate {
+ private:
+ Translate();
+ Point vec;
+ public:
+ explicit Translate(Point const &p) : vec(p) {}
+ explicit Translate(Coord const x, Coord const y) : vec(x, y) {}
+ inline operator Matrix() const { return Matrix(0, 0, 0, 0, vec[X], vec[Y]); }
+
+ inline Coord operator[](Dim2 const dim) const { return vec[dim]; }
+ inline Coord operator[](unsigned const dim) const { return vec[dim]; }
+ inline bool operator==(Translate const &o) const { return vec == o.vec; }
+ inline bool operator!=(Translate const &o) const { return vec != o.vec; }
+
+ inline Translate inverse() const { return Translate(-vec); }
+
+ friend Point operator*(Point const &v, Translate const &t);
+ inline Translate operator*(Translate const &b) const { return Translate(vec + b.vec); }
+
+ friend Matrix operator*(Translate const &t, Rotate const &r);
+};
+
+inline Point operator*(Point const &v, Translate const &t) { return v + t.vec; }
+
+class Scale {
+ private:
+ Point vec;
+ Scale();
+ public:
+ explicit Scale(Point const &p) : vec(p) {}
+ Scale(Coord const x, Coord const y) : vec(x, y) {}
+ explicit Scale(Coord const s) : vec(s, s) {}
+ inline operator Matrix() const { return Matrix(vec[X], 0, 0, vec[Y], 0, 0); }
+
+ inline Coord operator[](Dim2 const d) const { return vec[d]; }
+ inline Coord operator[](unsigned const d) const { return vec[d]; }
+ //TODO: should we keep these mutators? add them to the other transforms?
+ inline Coord &operator[](Dim2 const d) { return vec[d]; }
+ inline Coord &operator[](unsigned const d) { return vec[d]; }
+ inline bool operator==(Scale const &o) const { return vec == o.vec; }
+ inline bool operator!=(Scale const &o) const { return vec != o.vec; }
+
+ inline Scale inverse() const { return Scale(1./vec[0], 1./vec[1]); }
+
+ friend Point operator*(Point const &v, Translate const &t);
+ inline Scale operator*(Scale const &b) const { return Scale(vec[X]*b[X], vec[Y]*b[Y]); }
+};
+
+inline Point operator*(Point const &p, Scale const &s) { return Point(p[X] * s[X], p[Y] * s[Y]); }
+
+/** Notionally an Geom::Matrix corresponding to rotation about the origin.
+ Behaves like Geom::Matrix for multiplication.
+**/
+class Rotate {
+ private:
+ Rotate();
+ Point vec;
+ public:
+ explicit Rotate(Coord theta) : vec(std::cos(theta), std::sin(theta)) {}
+ Rotate(Point const &p) {Point v = p; v.normalize(); vec = v;} //TODO: UGLY!
+ explicit Rotate(Coord x, Coord y) { Rotate(Point(x, y)); }
+ inline operator Matrix() const { return Matrix(vec[X], vec[Y], vec[Y], -vec[X], 0, 0); }
+
+ inline Coord operator[](Dim2 const dim) const { return vec[dim]; }
+ inline Coord operator[](unsigned const dim) const { return vec[dim]; }
+ inline bool operator==(Rotate const &o) const { return vec == o.vec; }
+ inline bool operator!=(Rotate const &o) const { return vec != o.vec; }
+
+ Rotate inverse() const { return Rotate( Point(vec[X], -vec[Y]) ); }
+ static Rotate from_degrees(Coord deg) {
+ Coord rad = (deg / 180.0) * M_PI;
+ return Rotate(rad);
+ }
+
+ friend Point operator*(Point const &v, Rotate const &r);
+ inline Rotate operator*(Rotate const &b) const { return Rotate(vec * b); }
+};
+
+inline Point operator*(Point const &v, Rotate const &r) { return v ^ r.vec; }
+
+Matrix operator*(Translate const &t, Scale const &s);
+Matrix operator*(Translate const &t, Rotate const &r);
+
+Matrix operator*(Scale const &s, Translate const &t);
+Matrix operator*(Scale const &s, Matrix const &m);
+
+Matrix operator*(Matrix const &m, Translate const &t);
+Matrix operator*(Matrix const &m, Scale const &s);
+Matrix operator*(Matrix const &m, Rotate const &r);
+Matrix operator*(Matrix const &m1, Matrix const &m2);
+
+//TODO: matrix to trans/scale/rotate
+
+} /* namespace Geom */
+
+
+#endif /* !SEEN_Geom_TRANSFORMS_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/2geom/utils.h b/src/2geom/utils.h
new file mode 100644
index 000000000..a2f906ff4
--- /dev/null
+++ b/src/2geom/utils.h
@@ -0,0 +1,79 @@
+#ifndef MATH_UTILS_HEADER
+#define MATH_UTILS_HEADER
+
+/** Various utility functions.
+ *
+ * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ */
+
+#include <cmath>
+#include <stdexcept>
+
+class NotImplemented : public std::logic_error {
+public:
+ NotImplemented() : std::logic_error("method not implemented") {}
+};
+
+/** Sign function - indicates the sign of a numeric type. -1 indicates negative, 1 indicates
+ * positive, and 0 indicates, well, 0. Mathsy people will know this is basically the derivative
+ * of abs, except for the fact that it is defined on 0.
+ */
+template <class T> inline int sgn(const T& x) {return (x < 0 ? -1 : (x > 0 ? 1 : 0) );}
+
+template <class T> inline T sqr(const T& x) {return x * x;}
+template <class T> inline T cube(const T& x) {return x * x * x;}
+
+/** Between function - returns true if a number x is within a range. The values delimiting the
+ * range and the number must have the same type.
+ */
+template <class T> inline const T& between (const T& min, const T& max, const T& x)
+ { return min < x && max > x; }
+
+/** Returns x rounded to the nearest integer. It is unspecified what happens
+ * if x is half way between two integers: we may in future use rint/round
+ * on platforms that have them.
+ */
+inline double round(double const x) { return std::floor(x + .5); }
+
+/** Returns x rounded to the nearest \a places decimal places.
+
+ Implemented in terms of round, i.e. we make no guarantees as to what happens if x is
+ half way between two rounded numbers.
+
+ Note: places is the number of decimal places without using scientific (e) notation, not the
+ number of significant figures. This function may not be suitable for values of x whose
+ magnitude is so far from 1 that one would want to use scientific (e) notation.
+
+ places may be negative: e.g. places = -2 means rounding to a multiple of .01
+**/
+inline double decimal_round(double const x, int const places) {
+ //TODO: possibly implement with modulus instead?
+ double const multiplier = std::pow(10.0, places);
+ return round( x * multiplier ) / multiplier;
+}
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f33ae5f4..260280a40 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,6 +47,8 @@ include libnr/Makefile_insert
include libnrtype/Makefile_insert
include libavoid/Makefile_insert
include livarot/Makefile_insert
+include live_effects/Makefile_insert
+include live_effects/parameter/Makefile_insert
include libvpsc/Makefile_insert
include libcola/Makefile_insert
include removeoverlap/Makefile_insert
@@ -65,6 +67,7 @@ include ui/view/Makefile_insert
include ui/widget/Makefile_insert
include util/Makefile_insert
include trace/Makefile_insert
+include 2geom/Makefile_insert
bin_PROGRAMS = inkscape inkview
@@ -84,6 +87,8 @@ noinst_LIBRARIES = \
helper/libspchelp.a \
io/libio.a \
libcroco/libcroco.a \
+ live_effects/liblive_effects.a \
+ live_effects/parameter/liblpeparam.a \
ui/libui.a \
ui/cache/libuicache.a \
ui/dialog/libuidialog.a \
@@ -104,6 +109,7 @@ noinst_LIBRARIES = \
widgets/libspwidgets.a \
trace/libtrace.a \
xml/libspxml.a \
+ 2geom/lib2geom.a \
libinkpost.a
check_LIBRARIES = \
@@ -143,6 +149,8 @@ EXTRA_DIST = \
libnrtype/makefile.in \
libavoid/makefile.in \
livarot/makefile.in \
+ live_effects/makefile.in \
+ live_effects/parameter/makefile.in \
removeoverlap/makefile.in \
svg/makefile.in \
trace/makefile.in \
@@ -156,6 +164,7 @@ EXTRA_DIST = \
util/makefile.in \
widgets/makefile.in \
xml/makefile.in \
+ 2geom/makefile.in \
extension/internal/gnome.cpp \
extension/internal/gnome.h \
extension/internal/win32.cpp \
diff --git a/src/Makefile_insert b/src/Makefile_insert
index 89fa9cb11..ebba357ef 100644
--- a/src/Makefile_insert
+++ b/src/Makefile_insert
@@ -334,6 +334,8 @@ inkscape_private_libs = \
libnr/libnr.a \
libavoid/libavoid.a \
livarot/libvarot.a \
+ live_effects/liblive_effects.a \
+ live_effects/parameter/liblpeparam.a \
ui/view/libuiview.a \
ui/libui.a \
ui/widget/libuiwidget.a \
@@ -348,6 +350,7 @@ inkscape_private_libs = \
extension/script/libscript.a \
dom/libdom.a \
xml/libspxml.a \
+ 2geom/lib2geom.a \
util/libinkutil.a \
io/libio.a \
$(inkjar_libs) \
diff --git a/src/attributes.cpp b/src/attributes.cpp
index 3810cb0f7..9541ff01e 100644
--- a/src/attributes.cpp
+++ b/src/attributes.cpp
@@ -39,6 +39,7 @@ static SPStyleProp const props[] = {
{SP_ATTR_STYLE, "style"},
{SP_ATTR_TRANSFORM_CENTER_X, "inkscape:transform-center-x"},
{SP_ATTR_TRANSFORM_CENTER_Y, "inkscape:transform-center-y"},
+ {SP_ATTR_INKSCAPE_PATH_EFFECT, "inkscape:path-effect"},
/* SPAnchor */
{SP_ATTR_XLINK_HREF, "xlink:href"},
{SP_ATTR_XLINK_TYPE, "xlink:type"},
@@ -102,6 +103,7 @@ static SPStyleProp const props[] = {
{SP_ATTR_Y, "y"},
/* SPPath */
{SP_ATTR_D, "d"},
+ {SP_ATTR_INKSCAPE_ORIGINAL_D, "inkscape:original-d"},
/* (Note: XML representation of connectors may change in future.) */
{SP_ATTR_CONNECTOR_TYPE, "inkscape:connector-type"},
{SP_ATTR_CONNECTION_START, "inkscape:connection-start"},
@@ -385,6 +387,8 @@ static SPStyleProp const props[] = {
{SP_PROP_SYSTEM_LANGUAGE, "systemLanguage"},
{SP_PROP_REQUIRED_FEATURES, "requiredFeatures"},
{SP_PROP_REQUIRED_EXTENSIONS, "requiredExtensions"},
+ /* LivePathEffect */
+ {SP_PROP_PATH_EFFECT, "effect"},
};
#define n_attrs (sizeof(props) / sizeof(props[0]))
diff --git a/src/attributes.h b/src/attributes.h
index 752500e24..847266ff3 100644
--- a/src/attributes.h
+++ b/src/attributes.h
@@ -38,7 +38,8 @@ enum SPAttributeEnum {
SP_ATTR_CONNECTOR_AVOID,
SP_ATTR_STYLE,
SP_ATTR_TRANSFORM_CENTER_X,
- SP_ATTR_TRANSFORM_CENTER_Y,
+ SP_ATTR_TRANSFORM_CENTER_Y,
+ SP_ATTR_INKSCAPE_PATH_EFFECT,
/* SPAnchor */
SP_ATTR_XLINK_HREF,
SP_ATTR_XLINK_TYPE,
@@ -103,6 +104,7 @@ enum SPAttributeEnum {
SP_ATTR_Y,
/* SPPath */
SP_ATTR_D,
+ SP_ATTR_INKSCAPE_ORIGINAL_D,
SP_ATTR_CONNECTOR_TYPE,
SP_ATTR_CONNECTION_START,
SP_ATTR_CONNECTION_END,
@@ -386,6 +388,8 @@ enum SPAttributeEnum {
SP_PROP_SYSTEM_LANGUAGE,
SP_PROP_REQUIRED_FEATURES,
SP_PROP_REQUIRED_EXTENSIONS,
+ /* LivePathEffect */
+ SP_PROP_PATH_EFFECT,
};
#endif
diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert
new file mode 100644
index 000000000..f285416e2
--- /dev/null
+++ b/src/live_effects/Makefile_insert
@@ -0,0 +1,27 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+live_effects/all: live_effects/liblive_effects.a live_effects/parameter/all
+
+live_effects/clean:
+ rm -f live_effects/liblive_effects.a $(live_effects_liblive_effects_a_OBJECTS)
+
+live_effects_liblive_effects_a_SOURCES = \
+ live_effects/effect.cpp \
+ live_effects/effect.h \
+ live_effects/lpeobject.cpp \
+ live_effects/lpeobject.h \
+ live_effects/lpeobject-reference.cpp \
+ live_effects/lpeobject-reference.h \
+ live_effects/n-art-bpath-2geom.cpp \
+ live_effects/n-art-bpath-2geom.h \
+ live_effects/lpe-skeletalstrokes.cpp \
+ live_effects/lpe-skeletalstrokes.h \
+ live_effects/lpe-gears.cpp \
+ live_effects/lpe-gears.h \
+ live_effects/lpe-test-doEffect-stack.cpp \
+ live_effects/lpe-test-doEffect-stack.h \
+ live_effects/lpe-slant.cpp \
+ live_effects/lpe-slant.h
+
+
+
diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp
new file mode 100644
index 000000000..a0a8f3db9
--- /dev/null
+++ b/src/live_effects/effect.cpp
@@ -0,0 +1,232 @@
+#define INKSCAPE_LIVEPATHEFFECT_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "display/display-forward.h"
+#include "xml/node-event-vector.h"
+#include "sp-object.h"
+#include "attributes.h"
+
+#include "desktop.h"
+
+#include "document.h"
+#include <glibmm/i18n.h>
+
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/parameter/parameter.h"
+#include <glibmm/ustring.h>
+#include "live_effects/n-art-bpath-2geom.h"
+#include "display/curve.h"
+#include <2geom/sbasis-to-bezier.h>
+#include <gtkmm.h>
+
+// include effects:
+#include "live_effects/lpe-skeletalstrokes.h"
+#include "live_effects/lpe-slant.h"
+#include "live_effects/lpe-test-doEffect-stack.h"
+#include "live_effects/lpe-gears.h"
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+const Util::EnumData<EffectType> LPETypeData[ENDTYPE_LPE] = {
+ {INVALID_LPE, _("Invalid effect"), "invalid"},
+ {SKELETAL_STROKES, _("Skeletal Strokes"), "skeletal"},
+ {SLANT, _("Slant"), "slant"},
+ {DOEFFECTSTACK_TEST, _("doEffect stack test"), "doeffectstacktest"},
+ {GEARS, _("Gears"), "gears"}
+};
+const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, ENDTYPE_LPE);
+
+Effect*
+Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
+{
+ switch (lpenr) {
+ case INVALID_LPE:
+ g_warning("LivePathEffect::Effect::New called with invalid patheffect type");
+ return NULL;
+ case SKELETAL_STROKES:
+ return (Effect*) new LPESkeletalStrokes(lpeobj);
+ case SLANT:
+ return (Effect*) new LPESlant(lpeobj);
+ case DOEFFECTSTACK_TEST:
+ return (Effect*) new LPEdoEffectStackTest(lpeobj);
+ case GEARS:
+ return (Effect*) new LPEGears(lpeobj);
+ case ENDTYPE_LPE:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+Effect::Effect(LivePathEffectObject *lpeobject)
+{
+ vbox = NULL;
+ tooltips = NULL;
+ lpeobj = lpeobject;
+}
+
+Effect::~Effect()
+{
+ if (tooltips) {
+ delete tooltips;
+ }
+}
+
+Glib::ustring
+Effect::getName()
+{
+ return Glib::ustring( LPETypeConverter.get_label(lpeobj->effecttype) );
+}
+
+/*
+ * Here be the doEffect function chain:
+ */
+void
+Effect::doEffect (SPCurve * curve)
+{
+ NArtBpath *new_bpath = doEffect(SP_CURVE_BPATH(curve));
+
+ if (new_bpath) { // FIXME, add function to SPCurve to change bpath? or a copy function?
+ if (curve->_bpath) {
+ g_free(curve->_bpath); //delete old bpath
+ }
+ curve->_bpath = new_bpath;
+ }
+}
+
+NArtBpath *
+Effect::doEffect (NArtBpath * path_in)
+{
+ std::vector<Geom::Path> orig_pathv = BPath_to_2GeomPath(path_in);
+
+ std::vector<Geom::Path> result_pathv = doEffect(orig_pathv);
+
+ NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv);
+
+ return new_bpath;
+}
+
+std::vector<Geom::Path>
+Effect::doEffect (std::vector<Geom::Path> & path_in)
+{
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in;
+ // FIXME: find standard function to convert std::vector<Geom::Path> ==> Piecewise< D2<SBasis> >
+ for (unsigned int i=0; i < path_in.size(); i++) {
+ pwd2_in.concat( path_in[i].toPwSb() );
+ }
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect(pwd2_in);
+
+ std::vector<Geom::Path> path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE);
+
+ return path_out;
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+Effect::doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+{
+ g_warning("Effect has no doEffect implementation");
+ return pwd2_in;
+}
+
+void
+Effect::readallParameters(Inkscape::XML::Node * repr)
+{
+ param_map_type::iterator it = param_map.begin();
+ while (it != param_map.end()) {
+ const gchar * key = (*it).first.c_str();
+ const gchar * value = repr->attribute(key);
+ if(value) {
+ setParameter(repr, key, NULL, value);
+ }
+ it++;
+ }
+}
+
+void
+Effect::setParameter(Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value)
+{
+ Glib::ustring stringkey(key);
+
+ param_map_type::iterator it = param_map.find(stringkey);
+ if (it != param_map.end()) {
+ bool accepted = it->second->param_readSVGValue(new_value);
+ /* think: can this backfire and create infinite loop when started with unacceptable old_value?
+ if (!accepted) { // change was not accepted, so change it back.
+ repr->setAttribute(key, old_value);
+ } */
+ }
+}
+
+void
+Effect::registerParameter(Parameter * param)
+{
+ param_map[param->param_key] = param; // inserts or updates
+}
+
+Gtk::Widget *
+Effect::getWidget()
+{
+ if (!vbox) {
+ vbox = Gtk::manage( new Gtk::VBox() ); // use manage here, because after deletion of Effect object, others might still be pointing to this widget.
+ //if (!tooltips)
+ tooltips = new Gtk::Tooltips();
+
+ vbox->set_border_width(5);
+
+ param_map_type::iterator it = param_map.begin();
+ while (it != param_map.end()) {
+ Parameter * param = it->second;
+ Gtk::Widget * widg = param->param_getWidget();
+ Glib::ustring * tip = param->param_getTooltip();
+ if (widg) {
+ vbox->pack_start(*widg, true, true, 2);
+ if (tip != NULL) {
+ tooltips->set_tip(*widg, *tip);
+ }
+ }
+
+ it++;
+ }
+ }
+
+ return dynamic_cast<Gtk::Widget *>(vbox);
+}
+
+
+Inkscape::XML::Node *
+Effect::getRepr()
+{
+ return SP_OBJECT_REPR(lpeobj);
+}
+
+SPDocument *
+Effect::getSPDoc()
+{
+ if (SP_OBJECT_DOCUMENT(lpeobj) == NULL) g_message("oh crap");
+ return SP_OBJECT_DOCUMENT(lpeobj);
+}
+
+
+}; /* namespace LivePathEffect */
+
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h
new file mode 100644
index 000000000..0ebd5d5a5
--- /dev/null
+++ b/src/live_effects/effect.h
@@ -0,0 +1,108 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_H
+#define INKSCAPE_LIVEPATHEFFECT_H
+
+/*
+ * Inkscape::LivePathEffect
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include "display/display-forward.h"
+#include <map>
+#include <glibmm/ustring.h>
+#include <2geom/path.h>
+#include "ui/widget/registry.h"
+#include "util/enums.h"
+
+#define LPE_CONVERSION_TOLERANCE 0.01 // FIXME: find good solution for this.
+
+struct SPShape;
+struct SPDocument;
+class NArtBpath;
+struct LivePathEffectObject;
+
+namespace Gtk {
+ class Widget;
+ class VBox;
+ class Tooltips;
+}
+
+namespace Inkscape {
+
+namespace XML {
+ class Node;
+};
+
+namespace LivePathEffect {
+
+enum EffectType {
+ INVALID_LPE = 0,
+ SKELETAL_STROKES,
+ SLANT,
+ DOEFFECTSTACK_TEST,
+ GEARS,
+ ENDTYPE_LPE // This must be last
+};
+
+extern const Util::EnumData<EffectType> LPETypeData[ENDTYPE_LPE];
+extern const Util::EnumDataConverter<EffectType> LPETypeConverter;
+
+class Parameter;
+
+class Effect {
+public:
+ virtual ~Effect();
+
+ Glib::ustring getName();
+
+ virtual void doEffect (SPCurve * curve);
+
+ static Effect* New(EffectType lpenr, LivePathEffectObject *lpeobj);
+
+ virtual Gtk::Widget * getWidget();
+
+ Inkscape::XML::Node * getRepr();
+ SPDocument * getSPDoc();
+
+ void readallParameters(Inkscape::XML::Node * repr);
+ void setParameter(Inkscape::XML::Node * repr, const gchar * key, const gchar * old_value, const gchar * new_value);
+
+protected:
+ Effect(LivePathEffectObject *lpeobject);
+
+ // provide a set of doEffect functions so the developer has a choice
+ // of what kind of input/output parameters he desires.
+ // the order in which they appear is the order in which they are
+ // called by this base class. (i.e. doEffect(SPCurve * curve) defaults to calling
+ // doEffect(std::vector<Geom::Path> )
+ virtual NArtBpath *
+ doEffect (NArtBpath * path_in);
+ virtual std::vector<Geom::Path>
+ doEffect (std::vector<Geom::Path> & path_in);
+ virtual Geom::Piecewise<Geom::D2<Geom::SBasis> >
+ doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in);
+
+ void registerParameter(Parameter * param);
+
+ typedef std::map<Glib::ustring, Parameter *> param_map_type;
+ param_map_type param_map;
+
+ Inkscape::UI::Widget::Registry wr;
+ Gtk::VBox * vbox;
+ Gtk::Tooltips * tooltips;
+
+ LivePathEffectObject *lpeobj;
+
+private:
+ Effect(const Effect&);
+ Effect& operator=(const Effect&);
+};
+
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpe-gears.cpp b/src/live_effects/lpe-gears.cpp
new file mode 100644
index 000000000..8da819ef6
--- /dev/null
+++ b/src/live_effects/lpe-gears.cpp
@@ -0,0 +1,266 @@
+#define INKSCAPE_LPE_DOEFFECT_STACK_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-gears.h"
+
+#include <vector>
+#include <2geom/d2.h>
+#include <2geom/sbasis.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/path.h>
+
+using std::vector;
+using namespace Geom;
+
+class Gear {
+public:
+ // pitch circles touch on two properly meshed gears
+ // all measurements are taken from the pitch circle
+ double pitch_diameter() {return (_number_of_teeth * _module) / M_PI;}
+ double pitch_radius() {return pitch_diameter() / 2.0;}
+ void pitch_radius(double R) {_module = (2 * M_PI * R) / _number_of_teeth;}
+
+ // base circle serves as the basis for the involute toothe profile
+ double base_diameter() {return pitch_diameter() * cos(_pressure_angle);}
+ double base_radius() {return base_diameter() / 2.0;}
+
+ // diametrical pitch
+ double diametrical_pitch() {return _number_of_teeth / pitch_diameter();}
+
+ // height of the tooth above the pitch circle
+ double addendum() {return 1.0 / diametrical_pitch();}
+ // depth of the tooth below the pitch circle
+ double dedendum() {return addendum() + _clearance;}
+
+ // root circle specifies the bottom of the fillet between teeth
+ double root_radius() {return pitch_radius() - dedendum();}
+ double root_diameter() {return root_radius() * 2.0;}
+
+ // outer circle is the outside diameter of the gear
+ double outer_radius() {return pitch_radius() + addendum();}
+ double outer_diameter() {return outer_radius() * 2.0;}
+
+ // angle covered by the tooth on the pitch circle
+ double tooth_thickness_angle() {return M_PI / _number_of_teeth;}
+
+ Geom::Point centre() {return _centre;}
+ void centre(Geom::Point c) {_centre = c;}
+
+ double angle() {return _angle;}
+ void angle(double a) {_angle = a;}
+
+ int number_of_teeth() {return _number_of_teeth;}
+
+ Geom::Path path();
+ Gear spawn(Geom::Point p);
+
+ Gear(int n, double m, double phi) {
+ _number_of_teeth = n;
+ _module = m;
+ _pressure_angle = phi;
+ _clearance = 0.0;
+ _angle = 0.0;
+ _centre = Geom::Point(0.0,0.0);
+ }
+private:
+ int _number_of_teeth;
+ double _pressure_angle;
+ double _module;
+ double _clearance;
+ double _angle;
+ Geom::Point _centre;
+ D2<SBasis> _involute(double start, double stop) {
+ D2<SBasis> B;
+ D2<SBasis> I;
+ Linear bo = Linear(start,stop);
+
+ B[0] = cos(bo,2);
+ B[1] = sin(bo,2);
+
+ I = B - Linear(0,1) * derivative(B);
+ I = I*base_radius() + _centre;
+ return I;
+ }
+ D2<SBasis> _arc(double start, double stop, double R) {
+ D2<SBasis> B;
+ Linear bo = Linear(start,stop);
+
+ B[0] = cos(bo,2);
+ B[1] = sin(bo,2);
+
+ B = B*R + _centre;
+ return B;
+ }
+ // angle of the base circle used to create the involute to a certain radius
+ double involute_swath_angle(double R) {
+ if (R <= base_radius()) return 0.0;
+ return sqrt(R*R - base_radius()*base_radius())/base_radius();
+ }
+
+ // angle of the base circle between the origin of the involute and the intersection on another radius
+ double involute_intersect_angle(double R) {
+ if (R <= base_radius()) return 0.0;
+ return (sqrt(R*R - base_radius()*base_radius())/base_radius()) - acos(base_radius()/R);
+ }
+};
+
+void makeContinuous(D2<SBasis> &a, Point const b) {
+ for(unsigned d=0;d<2;d++)
+ a[d][0][0] = b[d];
+}
+
+Geom::Path Gear::path() {
+ Geom::Path pb;
+
+ // angle covered by a full tooth and fillet
+ double tooth_rotation = 2.0 * tooth_thickness_angle();
+ // angle covered by an involute
+ double involute_advance = involute_intersect_angle(outer_radius()) - involute_intersect_angle(root_radius());
+ // angle covered by the tooth tip
+ double tip_advance = tooth_thickness_angle() - (2 * (involute_intersect_angle(outer_radius()) - involute_intersect_angle(pitch_radius())));
+ // angle covered by the toothe root
+ double root_advance = (tooth_rotation - tip_advance) - (2.0 * involute_advance);
+ // begin drawing the involute at t if the root circle is larger than the base circle
+ double involute_t = involute_swath_angle(root_radius())/involute_swath_angle(outer_radius());
+
+ //rewind angle to start drawing from the leading edge of the tooth
+ double first_tooth_angle = _angle - ((0.5 * tip_advance) + involute_advance);
+
+ Geom::Point prev;
+ for (int i=0; i < _number_of_teeth; i++)
+ {
+ double cursor = first_tooth_angle + (i * tooth_rotation);
+
+ D2<SBasis> leading_I = compose(_involute(cursor, cursor + involute_swath_angle(outer_radius())), Linear(involute_t,1));
+ if(i != 0) makeContinuous(leading_I, prev);
+ pb.append(SBasisCurve(leading_I));
+ cursor += involute_advance;
+ prev = leading_I.at1();
+
+ D2<SBasis> tip = _arc(cursor, cursor+tip_advance, outer_radius());
+ makeContinuous(tip, prev);
+ pb.append(SBasisCurve(tip));
+ cursor += tip_advance;
+ prev = tip.at1();
+
+ cursor += involute_advance;
+ D2<SBasis> trailing_I = compose(_involute(cursor, cursor - involute_swath_angle(outer_radius())), Linear(1,involute_t));
+ makeContinuous(trailing_I, prev);
+ pb.append(SBasisCurve(trailing_I));
+ prev = trailing_I.at1();
+
+ if (base_radius() > root_radius()) {
+ Geom::Point leading_start = trailing_I.at1();
+ Geom::Point leading_end = (root_radius() * unit_vector(leading_start - _centre)) + _centre;
+ prev = leading_end;
+ pb.appendNew<LineSegment>(leading_end);
+ }
+
+ D2<SBasis> root = _arc(cursor, cursor+root_advance, root_radius());
+ makeContinuous(root, prev);
+ pb.append(SBasisCurve(root));
+ cursor += root_advance;
+ prev = root.at1();
+
+ if (base_radius() > root_radius()) {
+ Geom::Point trailing_start = root.at1();
+ Geom::Point trailing_end = (base_radius() * unit_vector(trailing_start - _centre)) + _centre;
+ pb.appendNew<LineSegment>(trailing_end);
+ prev = trailing_end;
+ }
+ }
+
+ return pb;
+}
+
+Gear Gear::spawn(Geom::Point p) {
+ double radius = Geom::distance(this->centre(), p) - this->pitch_radius();
+ int N = (int) floor( (radius / this->pitch_radius()) * this->number_of_teeth() );
+
+ Gear gear(N, _module, _pressure_angle);
+ gear.centre(p);
+
+ double a = atan2(p - this->centre());
+ double new_angle = 0.0;
+ if (gear.number_of_teeth() % 2 == 0)
+ new_angle -= gear.tooth_thickness_angle();
+ new_angle -= (_angle) * (pitch_radius() / gear.pitch_radius());
+ new_angle += (a) * (pitch_radius() / gear.pitch_radius());
+ gear.angle(new_angle + a);
+ return gear;
+}
+
+
+
+// #################################################################
+
+
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+
+LPEGears::LPEGears(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ teeth(_("Teeth"), _("The number of teeth"), "teeth", &wr, this, 10),
+ phi(_("Phi"), _("???"), "phi", &wr, this, 5)
+{
+ registerParameter( dynamic_cast<Parameter *>(&teeth) );
+ registerParameter( dynamic_cast<Parameter *>(&phi) );
+}
+
+LPEGears::~LPEGears()
+{
+
+}
+
+std::vector<Geom::Path>
+LPEGears::doEffect (std::vector<Geom::Path> & path_in)
+{
+ std::vector<Geom::Path> path_out;
+ Geom::Path gearpath = path_in[0];
+
+ Geom::Path::iterator it(gearpath.begin());
+ if ( it == gearpath.end() ) return path_out;
+
+ Gear * gear = new Gear(teeth, 200.0, phi * M_PI / 180);
+ Geom::Point gear_centre = (*it).finalPoint();
+ gear->centre(gear_centre);
+ gear->angle(atan2((*it).initialPoint() - gear_centre));
+
+ it++; if ( it == gearpath.end() ) return path_out;
+ gear->pitch_radius(Geom::distance(gear_centre, (*it).finalPoint()));
+
+ path_out.push_back( gear->path());
+
+ for (it++ ; it != gearpath.end() ; it++) {
+ // iterate through Geom::Curve in path_in
+ Gear* gearnew = new Gear(gear->spawn( (*it).finalPoint() ));
+ path_out.push_back( gearnew->path() );
+ delete gear;
+ gear = gearnew;
+ }
+ delete gear;
+
+ return path_out;
+}
+
+
+}; // namespace LivePathEffect
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpe-gears.h b/src/live_effects/lpe-gears.h
new file mode 100644
index 000000000..81a4ef058
--- /dev/null
+++ b/src/live_effects/lpe-gears.h
@@ -0,0 +1,38 @@
+#ifndef INKSCAPE_LPE_GEARS_H
+#define INKSCAPE_LPE_GEARS_H
+
+/*
+ * Inkscape::LPEGears
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+*
+*
+ */
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/parameter.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPEGears : public Effect {
+public:
+ LPEGears(LivePathEffectObject *lpeobject);
+ ~LPEGears();
+
+ std::vector<Geom::Path> doEffect (std::vector<Geom::Path> & path_in);
+
+private:
+ RealParam teeth;
+ RealParam phi;
+
+ LPEGears(const LPEGears&);
+ LPEGears& operator=(const LPEGears&);
+};
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpe-skeletalstrokes.cpp b/src/live_effects/lpe-skeletalstrokes.cpp
new file mode 100644
index 000000000..47c73a56b
--- /dev/null
+++ b/src/live_effects/lpe-skeletalstrokes.cpp
@@ -0,0 +1,161 @@
+#define INKSCAPE_LPE_SKELETAL_STROKES_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-skeletalstrokes.h"
+#include "sp-shape.h"
+#include "display/curve.h"
+#include <libnr/n-art-bpath.h>
+#include "live_effects/n-art-bpath-2geom.h"
+#include "svg/svg.h"
+
+#include <2geom/sbasis.h>
+#include <2geom/sbasis-geometric.h>
+#include <2geom/bezier-to-sbasis.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+#include <2geom/piecewise.h>
+
+#include <algorithm>
+using std::vector;
+
+
+/* Theory in e-mail from J.F. Barraud
+Let B be the skeleton path, and P the pattern (the path to be deformed).
+
+P is a map t --> P(t) = ( x(t), y(t) ).
+B is a map t --> B(t) = ( a(t), b(t) ).
+
+The first step is to re-parametrize B by its arc length: this is the parametrization in which a point p on B is located by its distance s from start. One obtains a new map s --> U(s) = (a'(s),b'(s)), that still describes the same path B, but where the distance along B from start to
+U(s) is s itself.
+
+We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90°. Call this normal vector N(s).
+
+The basic deformation associated to B is then given by:
+
+ (x,y) --> U(x)+y*N(x)
+
+(i.e. we go for distance x along the path, and then for distance y along the normal)
+
+Of course this formula needs some minor adaptations (as is it depends on the absolute position of P for instance, so a little translation is needed
+first) but I think we can first forget about them.
+*/
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+static const Util::EnumData<SkelCopyType> SkelCopyTypeData[SSCT_END] = {
+ {SSCT_SINGLE, N_("Single"), "single"},
+ {SSCT_SINGLE_STRETCHED, N_("Single, stretched"), "single_stretched"},
+ {SSCT_REPEATED, N_("Repeated"), "repeated"},
+ {SSCT_REPEATED_STRETCHED, N_("Repeated, stretched"), "repeated_stretched"}
+};
+static const Util::EnumDataConverter<SkelCopyType> SkelCopyTypeConverter(SkelCopyTypeData, SSCT_END);
+
+LPESkeletalStrokes::LPESkeletalStrokes(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ pattern(_("Pattern"), _("Pattern to put along path"), "pattern", &wr, this),
+ origin(_("Origin"), _("Origin of ?"), "origin", &wr, this),
+ copytype(_("Copytype"), _("tooltip"), "copytype", SkelCopyTypeConverter, &wr, this)
+{
+ registerParameter( dynamic_cast<Parameter *>(&origin) );
+ registerParameter( dynamic_cast<Parameter *>(&pattern) );
+ registerParameter( dynamic_cast<Parameter *>(&copytype) );
+
+ pattern.signal_path_pasted.connect(sigc::mem_fun(*this, &LPESkeletalStrokes::on_pattern_pasted));
+}
+
+LPESkeletalStrokes::~LPESkeletalStrokes()
+{
+
+}
+
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+LPESkeletalStrokes::doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+{
+ using namespace Geom;
+
+/* LOTS OF CODE COPIED FROM 2geom/src/toys/path-along-path.cpp
+ * All credits should go to jfb and mgsloan of lib2geom development! */
+
+ const Util::EnumData<SkelCopyType> * data = copytype.get_selected_data();
+ if (!data)
+ return pwd2_in;
+ SkelCopyType type = data->id;
+
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(Piecewise<D2<SBasis> >(pwd2_in),2,.1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independant(pattern);
+ Piecewise<SBasis> x=Piecewise<SBasis>(patternd2[0]-origin[0]);
+ Piecewise<SBasis> y=Piecewise<SBasis>(patternd2[1]-origin[1]);
+ Interval pattBnds = bounds_exact(x);
+
+ int nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
+ double scaling = 1;
+
+ switch(type) {
+ case SSCT_REPEATED:
+ break;
+
+ case SSCT_SINGLE:
+ nbCopies = (nbCopies > 0) ? 1 : 0;
+ break;
+
+ case SSCT_SINGLE_STRETCHED:
+ nbCopies = 1;
+ scaling = uskeleton.cuts.back()/pattBnds.extent();
+ break;
+
+ case SSCT_REPEATED_STRETCHED:
+ scaling = uskeleton.cuts.back()/(((double)nbCopies)*pattBnds.extent());
+ break;
+
+ default:
+ return pwd2_in;
+ };
+
+ double pattWidth = pattBnds.extent() * scaling;
+
+ x-=pattBnds.min();
+ if (scaling != 1)
+ x*=scaling;
+
+ double offs = 0;
+ Piecewise<D2<SBasis> >output;
+ for (int i=0; i<nbCopies; i++){
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ offs+=pattWidth;
+ }
+ return output;
+}
+
+void
+LPESkeletalStrokes::on_pattern_pasted()
+{
+ // a new pattern was pasted through the dialog. overwrite the origin thingie to the first point of the path
+ origin.param_setValue(pattern.valueAt(0));
+}
+
+
+
+}; // namespace LivePathEffect
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpe-skeletalstrokes.h b/src/live_effects/lpe-skeletalstrokes.h
new file mode 100644
index 000000000..159a82f57
--- /dev/null
+++ b/src/live_effects/lpe-skeletalstrokes.h
@@ -0,0 +1,49 @@
+#ifndef INKSCAPE_LPE_SKELETAL_STROKES_H
+#define INKSCAPE_LPE_SKELETAL_STROKES_H
+
+/*
+ * Inkscape::LPESkeletalStrokes
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/path.h"
+#include "live_effects/parameter/point.h"
+#include "live_effects/parameter/enum.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+enum SkelCopyType {
+ SSCT_SINGLE = 0,
+ SSCT_SINGLE_STRETCHED,
+ SSCT_REPEATED,
+ SSCT_REPEATED_STRETCHED,
+ SSCT_END // This must be last
+};
+
+class LPESkeletalStrokes : public Effect {
+public:
+ LPESkeletalStrokes(LivePathEffectObject *lpeobject);
+ ~LPESkeletalStrokes();
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in);
+
+private:
+ PathParam pattern;
+ PointParam origin;
+ EnumParam<SkelCopyType> copytype;
+
+ void on_pattern_pasted();
+
+ LPESkeletalStrokes(const LPESkeletalStrokes&);
+ LPESkeletalStrokes& operator=(const LPESkeletalStrokes&);
+};
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpe-slant.cpp b/src/live_effects/lpe-slant.cpp
new file mode 100644
index 000000000..93107f81a
--- /dev/null
+++ b/src/live_effects/lpe-slant.cpp
@@ -0,0 +1,62 @@
+#define INKSCAPE_LPE_SLANT_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-slant.h"
+#include "sp-shape.h"
+#include "display/curve.h"
+#include <libnr/n-art-bpath.h>
+#include "live_effects/n-art-bpath-2geom.h"
+
+#include <2geom/path.h>
+#include <2geom/transforms.h>
+
+#include "svg/svg.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+LPESlant::LPESlant(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ factor(_("Slant factor"), _("y = y + x*(slant factor)"), "factor", &wr, this),
+ center(_("Center"), _("The x-coord of this point is around which the slant will happen"), "center", &wr, this)
+{
+ registerParameter( dynamic_cast<Parameter *>(&factor) );
+ registerParameter( dynamic_cast<Parameter *>(&center) );
+}
+
+LPESlant::~LPESlant()
+{
+}
+
+void
+LPESlant::doEffect(SPCurve * curve)
+{
+ NArtBpath *bpath = curve->_bpath;
+ int i = 0;
+ while(bpath[i].code != NR_END) {
+ bpath[i].y1 += (bpath[i].x1-center[Geom::X]) * factor;
+ bpath[i].y2 += (bpath[i].x2-center[Geom::X]) * factor;
+ bpath[i].y3 += (bpath[i].x3-center[Geom::X]) * factor;
+ i++;
+ }
+
+}
+
+}; //namespace LivePathEffect
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpe-slant.h b/src/live_effects/lpe-slant.h
new file mode 100644
index 000000000..95d4420fa
--- /dev/null
+++ b/src/live_effects/lpe-slant.h
@@ -0,0 +1,40 @@
+#ifndef INKSCAPE_LPE_SLANT_H
+#define INKSCAPE_LPE_SLANT_H
+
+/*
+ * Inkscape::LPESlant
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/parameter.h"
+#include "live_effects/parameter/point.h"
+#include "ui/widget/registered-widget.h"
+
+
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPESlant : public Effect {
+public:
+ LPESlant(LivePathEffectObject *lpeobject);
+ ~LPESlant();
+
+ void doEffect(SPCurve * curve);
+
+private:
+ RealParam factor;
+ PointParam center;
+
+ LPESlant(const LPESlant&);
+ LPESlant& operator=(const LPESlant&);
+};
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpe-test-doEffect-stack.cpp b/src/live_effects/lpe-test-doEffect-stack.cpp
new file mode 100644
index 000000000..2e3d12725
--- /dev/null
+++ b/src/live_effects/lpe-test-doEffect-stack.cpp
@@ -0,0 +1,96 @@
+#define INKSCAPE_LPE_DOEFFECT_STACK_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-test-doEffect-stack.h"
+
+#include <2geom/piecewise.h>
+#include <vector>
+#include <libnr/n-art-bpath.h>
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+
+LPEdoEffectStackTest::LPEdoEffectStackTest(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ step(_("Stack step"), _(""), "step", &wr, this)
+{
+ registerParameter( dynamic_cast<Parameter *>(&step) );
+}
+
+LPEdoEffectStackTest::~LPEdoEffectStackTest()
+{
+
+}
+
+void
+LPEdoEffectStackTest::doEffect (SPCurve * curve)
+{
+ if (step >= 1) {
+ Effect::doEffect(curve);
+ } else {
+ // return here
+ return;
+ }
+}
+
+NArtBpath *
+LPEdoEffectStackTest::doEffect (NArtBpath * path_in)
+{
+ if (step >= 2) {
+ return Effect::doEffect(path_in);
+ } else {
+ // return here
+ NArtBpath *path_out;
+
+ unsigned ret = 0;
+ while ( path_in[ret].code != NR_END ) {
+ ++ret;
+ }
+ unsigned len = ++ret;
+
+ path_out = g_new(NArtBpath, len);
+ memcpy(path_out, path_in, len * sizeof(NArtBpath));
+ return path_out;
+ }
+}
+
+std::vector<Geom::Path>
+LPEdoEffectStackTest::doEffect (std::vector<Geom::Path> & path_in)
+{
+ if (step >= 3) {
+ return Effect::doEffect(path_in);
+ } else {
+ // return here
+ std::vector<Geom::Path> path_out = path_in;
+ return path_out;
+ }
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> >
+LPEdoEffectStackTest::doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in)
+{
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > output = pwd2_in;
+
+ return output;
+}
+
+
+}; // namespace LivePathEffect
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpe-test-doEffect-stack.h b/src/live_effects/lpe-test-doEffect-stack.h
new file mode 100644
index 000000000..57748f503
--- /dev/null
+++ b/src/live_effects/lpe-test-doEffect-stack.h
@@ -0,0 +1,42 @@
+#ifndef INKSCAPE_LPE_DOEFFECT_STACK_H
+#define INKSCAPE_LPE_DOEFFECT_STACK_H
+
+/*
+ * Inkscape::LPEdoEffectStackTest
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+*
+* This effect is to test whether running up and down the doEffect stack does not change the original-d too much.
+* i.e. for this effect, the output should match more or less exactly with the input.
+*
+ */
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/parameter.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPEdoEffectStackTest : public Effect {
+public:
+ LPEdoEffectStackTest(LivePathEffectObject *lpeobject);
+ ~LPEdoEffectStackTest();
+
+ void doEffect (SPCurve * curve);
+ NArtBpath * doEffect (NArtBpath * path_in);
+ std::vector<Geom::Path> doEffect (std::vector<Geom::Path> & path_in);
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect (Geom::Piecewise<Geom::D2<Geom::SBasis> > & pwd2_in);
+
+private:
+ RealParam step;
+
+ LPEdoEffectStackTest(const LPEdoEffectStackTest&);
+ LPEdoEffectStackTest& operator=(const LPEdoEffectStackTest&);
+};
+
+}; //namespace LivePathEffect
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpeobject-reference.cpp b/src/live_effects/lpeobject-reference.cpp
new file mode 100644
index 000000000..a5392ef90
--- /dev/null
+++ b/src/live_effects/lpeobject-reference.cpp
@@ -0,0 +1,161 @@
+/*
+ * The reference corresponding to the inkscape:live-effect attribute
+ *
+ * Copyright (C) 2007 Johan Engelen
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information.
+ */
+
+#include "enums.h"
+#include "live_effects/lpeobject-reference.h"
+#include "live_effects/lpeobject.h"
+
+#include "prefs-utils.h"
+#include "uri.h"
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+static void lpeobjectreference_href_changed(SPObject *old_ref, SPObject *ref, LPEObjectReference *lpeobjref);
+static void lpeobjectreference_delete_self(SPObject *deleted, LPEObjectReference *lpeobjref);
+static void lpeobjectreference_source_modified(SPObject *iSource, guint flags, LPEObjectReference *lpeobjref);
+
+LPEObjectReference::LPEObjectReference(SPObject* i_owner) : URIReference(i_owner)
+{
+ owner=i_owner;
+ lpeobject_href = NULL;
+ lpeobject_repr = NULL;
+ lpeobject = NULL;
+ _changed_connection = changedSignal().connect(sigc::bind(sigc::ptr_fun(lpeobjectreference_href_changed), this)); // listening to myself, this should be virtual instead
+
+ user_unlink = NULL;
+}
+
+LPEObjectReference::~LPEObjectReference(void)
+{
+ _changed_connection.disconnect(); // to do before unlinking
+
+ quit_listening();
+ unlink();
+}
+
+bool LPEObjectReference::_acceptObject(SPObject * const obj) const
+{
+ if (IS_LIVEPATHEFFECT(obj)) {
+ SPObject * const owner = getOwner();
+ /* Refuse references to us or to an ancestor. */
+ for ( SPObject *iter = owner ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
+ if ( iter == obj ) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void
+LPEObjectReference::link(char *to)
+{
+ if ( to == NULL ) {
+ quit_listening();
+ unlink();
+ } else {
+ if ( !lpeobject_href || ( strcmp(to, lpeobject_href) != 0 ) ) {
+ g_free(lpeobject_href);
+ lpeobject_href = g_strdup(to);
+ try {
+ attach(Inkscape::URI(to));
+ } catch (Inkscape::BadURIException &e) {
+ /* TODO: Proper error handling as per
+ * http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing.
+ */
+ g_warning("%s", e.what());
+ detach();
+ }
+ }
+ }
+}
+
+void
+LPEObjectReference::unlink(void)
+{
+ g_free(lpeobject_href);
+ lpeobject_href = NULL;
+ detach();
+}
+
+void
+LPEObjectReference::start_listening(LivePathEffectObject* to)
+{
+ if ( to == NULL ) {
+ return;
+ }
+ lpeobject = to;
+ lpeobject_repr = SP_OBJECT_REPR(to);
+ _delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&lpeobjectreference_delete_self), this));
+ _modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&lpeobjectreference_source_modified), this));
+}
+
+void
+LPEObjectReference::quit_listening(void)
+{
+ if ( lpeobject == NULL ) {
+ return;
+ }
+ _modified_connection.disconnect();
+ _delete_connection.disconnect();
+ lpeobject_repr = NULL;
+ lpeobject = NULL;
+}
+
+static void
+lpeobjectreference_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, LPEObjectReference *lpeobjref)
+{
+ lpeobjref->quit_listening();
+ LivePathEffectObject *refobj = LIVEPATHEFFECT( lpeobjref->getObject() );
+ if ( refobj ) {
+ lpeobjref->start_listening(refobj);
+ }
+
+ lpeobjref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+static void
+lpeobjectreference_delete_self(SPObject */*deleted*/, LPEObjectReference *lpeobjref)
+{
+ guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK);
+
+ if (mode == SP_CLONE_ORPHANS_UNLINK) {
+ // leave it be. just forget about the source
+ lpeobjref->quit_listening();
+ lpeobjref->unlink();
+ if (lpeobjref->user_unlink)
+ lpeobjref->user_unlink(lpeobjref->owner);
+ } else if (mode == SP_CLONE_ORPHANS_DELETE) {
+ lpeobjref->owner->deleteObject();
+ }
+}
+
+static void
+lpeobjectreference_source_modified(SPObject *iSource, guint flags, LPEObjectReference *lpeobjref)
+{
+ lpeobjref->owner->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+}; //namespace LivePathEffect
+
+}; // namespace inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpeobject-reference.h b/src/live_effects/lpeobject-reference.h
new file mode 100644
index 000000000..7aaa5f691
--- /dev/null
+++ b/src/live_effects/lpeobject-reference.h
@@ -0,0 +1,71 @@
+#ifndef SEEN_LPEOBJECT_REFERENCE_H
+#define SEEN_LPEOBJECT_REFERENCE_H
+
+/*
+ * The reference corresponding to the inkscape:live-effect attribute
+ *
+ * Copyright (C) 2007 Johan Engelen
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information.
+ */
+
+#include <forward.h>
+#include <uri-references.h>
+#include <sigc++/sigc++.h>
+
+namespace Inkscape {
+namespace XML {
+struct Node;
+}
+}
+
+struct LivePathEffectObject;
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+class LPEObjectReference : public Inkscape::URIReference {
+public:
+ LPEObjectReference(SPObject *owner);
+ ~LPEObjectReference();
+
+ SPObject *owner;
+
+ // concerning the LPEObject that is refered to:
+ gchar *lpeobject_href;
+ Inkscape::XML::Node *lpeobject_repr;
+ LivePathEffectObject *lpeobject;
+
+ sigc::connection _modified_connection;
+ sigc::connection _delete_connection;
+ sigc::connection _changed_connection;
+
+ void link(char* to);
+ void unlink(void);
+ void start_listening(LivePathEffectObject* to);
+ void quit_listening(void);
+
+ void (*user_unlink) (SPObject *user);
+
+protected:
+ bool _acceptObject(SPObject * const obj) const;
+
+};
+
+}; //namespace LivePathEffect
+
+}; // namespace inkscape
+
+#endif /* !SEEN_LPEOBJECT_REFERENCE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpeobject.cpp b/src/live_effects/lpeobject.cpp
new file mode 100644
index 000000000..0ce2ce283
--- /dev/null
+++ b/src/live_effects/lpeobject.cpp
@@ -0,0 +1,260 @@
+#define INKSCAPE_LIVEPATHEFFECT_OBJECT_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "xml/repr.h"
+#include "xml/node-event-vector.h"
+#include "sp-object.h"
+#include "attributes.h"
+
+#include "document.h"
+#include <glibmm/i18n.h>
+
+#include "live_effects/lpeobject.h"
+#include "live_effects/effect.h"
+
+//#define LIVEPATHEFFECT_VERBOSE
+
+static void livepatheffect_class_init(LivePathEffectObjectClass *klass);
+static void livepatheffect_init(LivePathEffectObject *stop);
+
+static void livepatheffect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
+static void livepatheffect_release(SPObject *object);
+
+static void livepatheffect_set(SPObject *object, unsigned key, gchar const *value);
+static Inkscape::XML::Node *livepatheffect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
+
+static void livepatheffect_on_repr_attr_changed (Inkscape::XML::Node * repr, const gchar *key, const gchar *oldval, const gchar *newval, bool is_interactive, void * data);
+
+static SPObjectClass *livepatheffect_parent_class;
+/**
+ * Registers the LivePathEffect class with Gdk and returns its type number.
+ */
+GType
+livepatheffect_get_type ()
+{
+ static GType livepatheffect_type = 0;
+
+ if (!livepatheffect_type) {
+ GTypeInfo livepatheffect_info = {
+ sizeof (LivePathEffectObjectClass),
+ NULL, NULL,
+ (GClassInitFunc) livepatheffect_class_init,
+ NULL, NULL,
+ sizeof (LivePathEffectObject),
+ 16,
+ (GInstanceInitFunc) livepatheffect_init,
+ NULL,
+ };
+ livepatheffect_type = g_type_register_static (SP_TYPE_OBJECT, "LivePathEffectObject", &livepatheffect_info, (GTypeFlags)0);
+ }
+ return livepatheffect_type;
+}
+
+static Inkscape::XML::NodeEventVector const livepatheffect_repr_events = {
+ NULL, /* child_added */
+ NULL, /* child_removed */
+ livepatheffect_on_repr_attr_changed,
+ NULL, /* content_changed */
+ NULL /* order_changed */
+};
+
+
+/**
+ * Callback to initialize livepatheffect vtable.
+ */
+static void
+livepatheffect_class_init(LivePathEffectObjectClass *klass)
+{
+ SPObjectClass *sp_object_class = (SPObjectClass *) klass;
+
+ livepatheffect_parent_class = (SPObjectClass *) g_type_class_ref(SP_TYPE_OBJECT);
+
+ sp_object_class->build = livepatheffect_build;
+ sp_object_class->release = livepatheffect_release;
+
+ sp_object_class->set = livepatheffect_set;
+ sp_object_class->write = livepatheffect_write;
+}
+
+/**
+ * Callback to initialize livepatheffect object.
+ */
+static void
+livepatheffect_init(LivePathEffectObject *lpeobj)
+{
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_message("Init livepatheffectobject");
+#endif
+ lpeobj->effecttype = Inkscape::LivePathEffect::INVALID_LPE;
+ lpeobj->lpe = NULL;
+
+ lpeobj->effecttype_set = false;
+}
+
+/**
+ * Virtual build: set livepatheffect attributes from its associated XML node.
+ */
+static void
+livepatheffect_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
+{
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_message("Build livepatheffect");
+#endif
+
+ if (((SPObjectClass *) livepatheffect_parent_class)->build)
+ (* ((SPObjectClass *) livepatheffect_parent_class)->build)(object, document, repr);
+
+ sp_object_read_attr(object, "effect");
+
+ if (repr) {
+ repr->addListener (&livepatheffect_repr_events, object);
+ }
+
+ /* Register ourselves */
+// sp_document_add_resource(document, "path-effect", object);
+}
+
+/**
+ * Virtual release of livepatheffect members before destruction.
+ */
+static void
+livepatheffect_release(SPObject *object)
+{
+ LivePathEffectObject *lpeobj = LIVEPATHEFFECT(object);
+
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_print("Releasing livepatheffect");
+#endif
+
+/*
+ if (SP_OBJECT_DOCUMENT(object)) {
+ // Unregister ourselves
+ sp_document_remove_resource(SP_OBJECT_DOCUMENT(object), "livepatheffect", SP_OBJECT(object));
+ }
+
+ if (gradient->ref) {
+ gradient->modified_connection.disconnect();
+ gradient->ref->detach();
+ delete gradient->ref;
+ gradient->ref = NULL;
+ }
+
+ gradient->modified_connection.~connection();
+
+*/
+
+ if (lpeobj->lpe) {
+ delete lpeobj->lpe;
+ lpeobj->lpe = NULL;
+ }
+ lpeobj->effecttype = Inkscape::LivePathEffect::INVALID_LPE;
+
+ if (((SPObjectClass *) livepatheffect_parent_class)->release)
+ ((SPObjectClass *) livepatheffect_parent_class)->release(object);
+}
+
+/**
+ * Virtual set: set attribute to value.
+ */
+static void
+livepatheffect_set(SPObject *object, unsigned key, gchar const *value)
+{
+ LivePathEffectObject *lpeobj = LIVEPATHEFFECT(object);
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_print("Set livepatheffect");
+#endif
+ switch (key) {
+ case SP_PROP_PATH_EFFECT:
+ if (lpeobj->lpe) {
+ delete lpeobj->lpe;
+ lpeobj->lpe = NULL;
+ }
+
+ if (value) {
+ lpeobj->effecttype = Inkscape::LivePathEffect::LPETypeConverter.get_id_from_key(value);
+ if (lpeobj->effecttype != Inkscape::LivePathEffect::INVALID_LPE) {
+ lpeobj->lpe = Inkscape::LivePathEffect::Effect::New(lpeobj->effecttype, lpeobj);
+ lpeobj->lpe->readallParameters(SP_OBJECT_REPR(object));
+ }
+ lpeobj->effecttype_set = true;
+ } else {
+ lpeobj->effecttype = Inkscape::LivePathEffect::INVALID_LPE;
+ lpeobj->effecttype_set = false;
+ }
+ object->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ }
+
+ if (((SPObjectClass *) livepatheffect_parent_class)->set) {
+ ((SPObjectClass *) livepatheffect_parent_class)->set(object, key, value);
+ }
+}
+
+/**
+ * Virtual write: write object attributes to repr.
+ */
+static Inkscape::XML::Node *
+livepatheffect_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
+{
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_print("Write livepatheffect");
+#endif
+
+ LivePathEffectObject *lpeobj = LIVEPATHEFFECT(object);
+
+ if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
+ Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
+ repr = xml_doc->createElement("inkscape:path-effect");
+ }
+
+ if ((flags & SP_OBJECT_WRITE_ALL) || lpeobj->effecttype_set)
+ repr->setAttribute("effect", Inkscape::LivePathEffect::LPETypeConverter.get_key(lpeobj->effecttype).c_str());
+
+// lpeobj->lpe->write(repr); something like this.
+
+ if (((SPObjectClass *) livepatheffect_parent_class)->write)
+ (* ((SPObjectClass *) livepatheffect_parent_class)->write)(object, repr, flags);
+
+ return repr;
+}
+
+static void
+livepatheffect_on_repr_attr_changed ( Inkscape::XML::Node * repr,
+ const gchar *key,
+ const gchar *oldval,
+ const gchar *newval,
+ bool is_interactive,
+ void * data )
+{
+#ifdef LIVEPATHEFFECT_VERBOSE
+ g_print("livepatheffect_on_repr_attr_changed");
+#endif
+
+ if (!data)
+ return;
+
+ LivePathEffectObject *lpeobj = (LivePathEffectObject*) data;
+ if (!lpeobj->lpe)
+ return;
+
+ lpeobj->lpe->setParameter(repr, key, oldval, newval);
+
+ lpeobj->requestModified(SP_OBJECT_MODIFIED_FLAG);
+}
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/lpeobject.h b/src/live_effects/lpeobject.h
new file mode 100644
index 000000000..fff43cdcf
--- /dev/null
+++ b/src/live_effects/lpeobject.h
@@ -0,0 +1,52 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_OBJECT_H
+#define INKSCAPE_LIVEPATHEFFECT_OBJECT_H
+
+/*
+ * Inkscape::LivePathEffect
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "sp-object.h"
+#include "effect.h"
+
+#define TYPE_LIVEPATHEFFECT (livepatheffect_get_type())
+#define LIVEPATHEFFECT(o) (G_TYPE_CHECK_INSTANCE_CAST((o), TYPE_LIVEPATHEFFECT, LivePathEffectObject))
+#define IS_LIVEPATHEFFECT(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), TYPE_LIVEPATHEFFECT))
+
+/*
+namespace Inkscape {
+namespace LivePathEffect {
+ class Effect;
+};
+};
+*/
+
+struct LivePathEffectObject : public SPObject {
+ Inkscape::LivePathEffect::EffectType effecttype; // fixme: i think this is not needed
+ Inkscape::LivePathEffect::Effect *lpe;
+
+ bool effecttype_set;
+};
+
+/// The LivePathEffect vtable.
+struct LivePathEffectObjectClass {
+ SPObjectClass parent_class;
+};
+
+GType livepatheffect_get_type();
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/live_effects/makefile.in b/src/live_effects/makefile.in
new file mode 100644
index 000000000..360773401
--- /dev/null
+++ b/src/live_effects/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd .. && $(MAKE) live_effects/all
+
+clean %.a %.o:
+ cd .. && $(MAKE) live_effects/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/live_effects/n-art-bpath-2geom.cpp b/src/live_effects/n-art-bpath-2geom.cpp
new file mode 100644
index 000000000..5f4e4c7b9
--- /dev/null
+++ b/src/live_effects/n-art-bpath-2geom.cpp
@@ -0,0 +1,416 @@
+#define SEEN_LIBNR_N_ART_BPATH_2GEOM_CPP
+
+/** \file
+ * Contains functions to convert from NArtBpath to 2geom's Path
+ *
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+
+#include "live_effects/n-art-bpath-2geom.h"
+#include "svg/svg.h"
+#include <glib.h>
+#include <2geom/path.h>
+#include <2geom/svg-path.h>
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis-to-bezier.h>
+
+#define LPE_USE_2GEOM_CONVERSION
+
+//##########################################################
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <boost/format.hpp>
+
+static void curve_to_svgd(std::ostream & f, Geom::Curve const* c) {
+ if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const *>(c)) {
+ f << boost::format("L %g,%g ") % (*line_segment)[1][0] % (*line_segment)[1][1];
+ }
+ else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) {
+ f << boost::format("Q %g,%g %g,%g ") % (*quadratic_bezier)[1][0] % (*quadratic_bezier)[1][0]
+ % (*quadratic_bezier)[2][0] % (*quadratic_bezier)[2][1];
+ }
+ else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const *>(c)) {
+ f << boost::format("C %g,%g %g,%g %g,%g ")
+ % (*cubic_bezier)[1][0] % (*cubic_bezier)[1][1]
+ % (*cubic_bezier)[2][0] % (*cubic_bezier)[2][1]
+ % (*cubic_bezier)[3][0] % (*cubic_bezier)[3][1];
+ }
+// else if(Geom::SVGEllipticalArc const *svg_elliptical_arc = dynamic_cast<Geom::SVGEllipticalArc *>(c)) {
+// //get at the innards and spit them out as svgd
+// }
+ else {
+ //this case handles sbasis as well as all other curve types
+ Geom::Path sbasis_path;
+ path_from_sbasis(sbasis_path, c->sbasis(), 0.1);
+
+ //recurse to convert the new path resulting from the sbasis to svgd
+ for(Geom::Path::iterator iter = sbasis_path.begin(); iter != sbasis_path.end(); ++iter) {
+ curve_to_svgd(f, &(*iter));
+ }
+ }
+}
+
+static void write_svgd(std::ostream & f, Geom::Path const &p) {
+ if(f == NULL) {
+ f << "ERRRRRRORRRRR";
+ return;
+ }
+
+ f << boost::format("M %g,%g ") % p.initialPoint()[0] % p.initialPoint()[1];
+
+ for(Geom::Path::const_iterator iter(p.begin()), end(p.end()); iter != end; ++iter) {
+ curve_to_svgd(f, &(*iter));
+ }
+ if(p.closed())
+ f << "Z ";
+}
+
+static void write_svgd(std::ostream & f, std::vector<Geom::Path> const &p) {
+ std::vector<Geom::Path>::const_iterator it(p.begin());
+ for(; it != p.end(); it++) {
+ write_svgd(f, *it);
+ }
+}
+
+//##########################################################
+#ifndef LPE_USE_2GEOM_CONVERSION
+
+static
+Geom::Point point(double *nums, int ix) {
+ return Geom::Point(nums[ix], nums[ix + 1]);
+}
+
+using namespace Geom;
+
+class OldPathBuilder {
+public:
+ OldPathBuilder(double const &c = Geom_EPSILON) : _current_path(NULL) {
+ _continuity_tollerance = c;
+ }
+
+ void startPathRel(Point const &p0) { startPath(p0 + _current_point); }
+ void startPath(Point const &p0) {
+ _pathset.push_back(Geom::Path());
+ _current_path = &_pathset.back();
+ _initial_point = _current_point = p0;
+ }
+
+ void pushLineRel(Point const &p0) { pushLine(p0 + _current_point); }
+ void pushLine(Point const &p1) {
+ if (!_current_path) startPath(_current_point);
+ _current_path->appendNew<LineSegment>(p1);
+ _current_point = p1;
+ }
+
+ void pushLineRel(Point const &p0, Point const &p1) { pushLine(p0 + _current_point, p1 + _current_point); }
+ void pushLine(Point const &p0, Point const &p1) {
+ if(p0 != _current_point) startPath(p0);
+ pushLine(p1);
+ }
+
+ void pushHorizontalRel(Coord y) { pushHorizontal(y + _current_point[1]); }
+ void pushHorizontal(Coord y) {
+ if (!_current_path) startPath(_current_point);
+ pushLine(Point(_current_point[0], y));
+ }
+
+ void pushVerticalRel(Coord x) { pushVertical(x + _current_point[0]); }
+ void pushVertical(Coord x) {
+ if (!_current_path) startPath(_current_point);
+ pushLine(Point(x, _current_point[1]));
+ }
+
+ void pushQuadraticRel(Point const &p1, Point const &p2) { pushQuadratic(p1 + _current_point, p2 + _current_point); }
+ void pushQuadratic(Point const &p1, Point const &p2) {
+ if (!_current_path) startPath(_current_point);
+ _current_path->appendNew<QuadraticBezier>(p1, p2);
+ _current_point = p2;
+ }
+
+ void pushQuadraticRel(Point const &p0, Point const &p1, Point const &p2) {
+ pushQuadratic(p0 + _current_point, p1 + _current_point, p2 + _current_point);
+ }
+ void pushQuadratic(Point const &p0, Point const &p1, Point const &p2) {
+ if(p0 != _current_point) startPath(p0);
+ pushQuadratic(p1, p2);
+ }
+
+ void pushCubicRel(Point const &p1, Point const &p2, Point const &p3) {
+ pushCubic(p1 + _current_point, p2 + _current_point, p3 + _current_point);
+ }
+ void pushCubic(Point const &p1, Point const &p2, Point const &p3) {
+ if (!_current_path) startPath(_current_point);
+ _current_path->appendNew<CubicBezier>(p1, p2, p3);
+ _current_point = p3;
+ }
+
+ void pushCubicRel(Point const &p0, Point const &p1, Point const &p2, Point const &p3) {
+ pushCubic(p0 + _current_point, p1 + _current_point, p2 + _current_point, p3 + _current_point);
+ }
+ void pushCubic(Point const &p0, Point const &p1, Point const &p2, Point const &p3) {
+ if(p0 != _current_point) startPath(p0);
+ pushCubic(p1, p2, p3);
+ }
+/*
+ void pushEllipseRel(Point const &radii, double rotation, bool large, bool sweep, Point const &end) {
+ pushEllipse(radii, rotation, large, sweep, end + _current_point);
+ }
+ void pushEllipse(Point const &radii, double rotation, bool large, bool sweep, Point const &end) {
+ if (!_current_path) startPath(_current_point);
+ _current_path->append(SVGEllipticalArc(_current_point, radii[0], radii[1], rotation, large, sweep, end));
+ _current_point = end;
+ }
+
+ void pushEllipseRel(Point const &initial, Point const &radii, double rotation, bool large, bool sweep, Point const &end) {
+ pushEllipse(initial + _current_point, radii, rotation, large, sweep, end + _current_point);
+ }
+ void pushEllipse(Point const &initial, Point const &radii, double rotation, bool large, bool sweep, Point const &end) {
+ if(initial != _current_point) startPath(initial);
+ pushEllipse(radii, rotation, large, sweep, end);
+ }*/
+
+ void pushSBasis(SBasisCurve &sb) {
+ pushSBasis(sb.sbasis());
+ }
+ void pushSBasis(D2<SBasis> sb) {
+ Point initial = Point(sb[X][0][0], sb[Y][0][0]);
+ if (!_current_path) startPath(_current_point);
+ if (distance(initial, _current_point) > _continuity_tollerance) {
+ startPath(initial);
+ } else if (_current_point != initial) {
+ /* in this case there are three possible options
+ 1. connect the points with tiny line segments
+ this may well translate into bug reports from
+ users claiming "duplicate or extraneous nodes"
+ 2. fudge the initial point of the multidimsb
+ we've chosen to do this here but question the
+ numerical stability of this decision
+ 3. translate the whole sbasis so that initial is coincident
+ with _current_point. this could very well lead
+ to an accumulation of error for paths that expect
+ to meet in the end.
+ perhaps someday an option could be made to allow
+ the user to choose between these alternatives
+ if the need arises
+ */
+ sb[X][0][0] = _current_point[X];
+ sb[Y][0][0] = _current_point[Y];
+ }
+ _current_path->append(sb);
+ }
+
+ void closePath() {
+ if (_current_path) {
+ _current_path->close(true);
+ _current_path = NULL;
+ }
+ _current_point = _initial_point = Point();
+ }
+
+ std::vector<Path> const &peek() const { return _pathset; }
+
+private:
+ std::vector<Path> _pathset;
+ Path *_current_path;
+ Point _current_point;
+ Point _initial_point;
+ double _continuity_tollerance;
+};
+
+static
+std::vector<Geom::Path>
+read_svgd(std::istringstream & s) {
+ assert(s);
+
+ OldPathBuilder builder;
+
+ char mode = 0;
+
+ double nums[7];
+ int cur = 0;
+ while(!s.eof()) {
+ char ch;
+ s >> ch;
+ if((ch >= 'A' and ch <= 'Z') or (ch >= 'a' and ch <= 'z')) {
+ mode = ch;
+ cur = 0;
+ } else if (ch == ' ' or ch == '\t' or ch == '\n' or ch == '\r' or ch == ',')
+ continue;
+ else if ((ch >= '0' and ch <= '9') or ch == '-' or ch == '.' or ch == '+') {
+ s.unget();
+ //TODO: use something else, perhaps. Unless the svg path number spec matches scan.
+ s >> nums[cur];
+ cur++;
+ }
+
+ switch(mode) {
+ //FIXME: "If a moveto is followed by multiple pairs of coordinates, the subsequent pairs are treated as implicit lineto commands."
+ case 'm':
+ if(cur >= 2) {
+ builder.startPathRel(point(nums, 0));
+ cur = 0;
+ }
+ break;
+ case 'M':
+ if(cur >= 2) {
+ builder.startPath(point(nums, 0));
+ cur = 0;
+ }
+ break;
+ case 'l':
+ if(cur >= 2) {
+ builder.pushLineRel(point(nums, 0));
+ cur = 0;
+ }
+ break;
+ case 'L':
+ if(cur >= 2) {
+ builder.pushLine(point(nums, 0));
+ cur = 0;
+ }
+ break;
+ case 'h':
+ if(cur >= 1) {
+ builder.pushHorizontalRel(nums[0]);
+ cur = 0;
+ }
+ break;
+ case 'H':
+ if(cur >= 1) {
+ builder.pushHorizontal(nums[0]);
+ cur = 0;
+ }
+ break;
+ case 'v':
+ if(cur >= 1) {
+ builder.pushVerticalRel(nums[0]);
+ cur = 0;
+ }
+ break;
+ case 'V':
+ if(cur >= 1) {
+ builder.pushVertical(nums[0]);
+ cur = 0;
+ }
+ break;
+ case 'c':
+ if(cur >= 6) {
+ builder.pushCubicRel(point(nums, 0), point(nums, 2), point(nums, 4));
+ cur = 0;
+ }
+ break;
+ case 'C':
+ if(cur >= 6) {
+ builder.pushCubic(point(nums, 0), point(nums, 2), point(nums, 4));
+ cur = 0;
+ }
+ break;
+ case 'q':
+ if(cur >= 4) {
+ builder.pushQuadraticRel(point(nums, 0), point(nums, 2));
+ cur = 0;
+ }
+ break;
+ case 'Q':
+ if(cur >= 4) {
+ builder.pushQuadratic(point(nums, 0), point(nums, 2));
+ cur = 0;
+ }
+ break;
+ case 'a':
+ if(cur >= 7) {
+ //builder.pushEllipseRel(point(nums, 0), nums[2], nums[3] > 0, nums[4] > 0, point(nums, 5));
+ cur = 0;
+ }
+ break;
+ case 'A':
+ if(cur >= 7) {
+ //builder.pushEllipse(point(nums, 0), nums[2], nums[3] > 0, nums[4] > 0, point(nums, 5));
+ cur = 0;
+ }
+ break;
+ case 'z':
+ case 'Z':
+ builder.closePath();
+ break;
+ }
+ }
+ return builder.peek();
+}
+
+
+#endif
+//##########################################################
+
+std::vector<Geom::Path>
+SVGD_to_2GeomPath (char const *svgd)
+{
+ std::vector<Geom::Path> pathv;
+#ifdef LPE_USE_2GEOM_CONVERSION
+ try {
+ pathv = Geom::parse_svg_path(svgd);
+ }
+ catch (std::runtime_error e) {
+ g_warning("SVGPathParseError: %s", e.what());
+ }
+#else
+ std::istringstream ss;
+ std::string svgd_string = svgd;
+ ss.str(svgd_string);
+ pathv = read_svgd(ss);
+#endif
+ return pathv;
+}
+
+
+std::vector<Geom::Path>
+BPath_to_2GeomPath(NArtBpath const * bpath)
+{
+ std::vector<Geom::Path> pathv;
+ char *svgpath = sp_svg_write_path(bpath);
+ if (!svgpath) {
+ g_warning("BPath_to_2GeomPath - empty path returned");
+ return pathv;
+ }
+ pathv = SVGD_to_2GeomPath(svgpath);
+ g_free(svgpath);
+ return pathv;
+}
+
+char *
+SVGD_from_2GeomPath(std::vector<Geom::Path> const & path)
+{
+ std::ostringstream ss;
+ write_svgd(ss, path);
+ ss.flush();
+ std::string str = ss.str();
+ char * svgd = g_strdup(str.c_str());
+ return svgd;
+}
+
+NArtBpath *
+BPath_from_2GeomPath(std::vector<Geom::Path> const & path)
+{
+ char * svgd = SVGD_from_2GeomPath(path);
+ NArtBpath *bpath = sp_svg_read_path(svgd);
+ g_free(svgd);
+ return bpath;
+}
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/n-art-bpath-2geom.h b/src/live_effects/n-art-bpath-2geom.h
new file mode 100644
index 000000000..52b4cb9ee
--- /dev/null
+++ b/src/live_effects/n-art-bpath-2geom.h
@@ -0,0 +1,33 @@
+#ifndef SEEN_LIBNR_N_ART_BPATH_2GEOM_H
+#define SEEN_LIBNR_N_ART_BPATH_2GEOM_H
+
+/** \file
+ * Contains functions to convert from NArtBpath to 2geom's Path
+ *
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <vector>
+#include <2geom/path.h>
+#include <libnr/n-art-bpath.h>
+
+std::vector<Geom::Path> SVGD_to_2GeomPath (char const *svgd);
+std::vector<Geom::Path> BPath_to_2GeomPath (NArtBpath const *bpath);
+char * SVGD_from_2GeomPath(std::vector<Geom::Path> const & path);
+NArtBpath * BPath_from_2GeomPath (std::vector<Geom::Path> const & path);
+
+
+#endif /* !SEEN_LIBNR_N_ART_BPATH_2GEOM_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/parameter/Makefile_insert b/src/live_effects/parameter/Makefile_insert
new file mode 100644
index 000000000..a737da1f0
--- /dev/null
+++ b/src/live_effects/parameter/Makefile_insert
@@ -0,0 +1,18 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+live_effects/parameter/all: live_effects/parameter/liblpeparam.a
+
+live_effects/parameter/clean:
+ rm -f live_effects/parameter/liblpeparam.a $(live_effects_parameter_liblpeparam_a_OBJECTS)
+
+live_effects_parameter_liblpeparam_a_SOURCES = \
+ live_effects/parameter/parameter.cpp \
+ live_effects/parameter/parameter.h \
+ live_effects/parameter/point.cpp \
+ live_effects/parameter/point.h \
+ live_effects/parameter/enum.h \
+ live_effects/parameter/path.cpp \
+ live_effects/parameter/path.h
+
+
+
diff --git a/src/live_effects/parameter/enum.h b/src/live_effects/parameter/enum.h
new file mode 100644
index 000000000..d70360a24
--- /dev/null
+++ b/src/live_effects/parameter/enum.h
@@ -0,0 +1,87 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_ENUM_H
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_ENUM_H
+
+/*
+ * Inkscape::LivePathEffectParameters
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gtypes.h>
+
+#include "ui/widget/registry.h"
+#include "ui/widget/registered-enums.h"
+#include <gtkmm/tooltips.h>
+
+#include "live_effects/parameter/parameter.h"
+#include "verbs.h"
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+template<typename E> class EnumParam : public Parameter {
+public:
+ EnumParam( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ const Util::EnumDataConverter<E>& c,
+ Inkscape::UI::Widget::Registry* wr, Effect* effect)
+ : Parameter(label, tip, key, wr, effect)
+ {
+ regenum = NULL;
+ enumdataconv = &c;
+ };
+ ~EnumParam() {
+ if (regenum)
+ delete regenum;
+ };
+
+ Gtk::Widget * param_getWidget() {
+ if (!regenum) {
+ regenum = new Inkscape::UI::Widget::RegisteredEnum<E>();
+ regenum->init(param_label, param_tooltip, param_key, *enumdataconv, *param_wr, param_effect->getRepr(), param_effect->getSPDoc());
+ regenum->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change enum parameter"));
+ }
+ return dynamic_cast<Gtk::Widget *> (regenum->labelled);
+ };
+
+ bool param_readSVGValue(const gchar * strvalue) {
+ if (regenum)
+ regenum->combobox()->set_active_by_key(Glib::ustring(strvalue));
+ return true;
+ };
+ gchar * param_writeSVGValue() const {
+ if (regenum) {
+ gchar * str = g_strdup(regenum->combobox()->get_active_data()->key.c_str());
+ return str;
+ } else {
+ return NULL;
+ }
+ };
+
+ const Util::EnumData<E>* get_selected_data() {
+ if (regenum) {
+ return regenum->combobox()->get_active_data();
+ } else {
+ return NULL;
+ }
+ };
+
+private:
+ EnumParam(const EnumParam&);
+ EnumParam& operator=(const EnumParam&);
+
+ UI::Widget::RegisteredEnum<E> * regenum;
+
+ const Util::EnumDataConverter<E> * enumdataconv;
+};
+
+
+}; //namespace LivePathEffect
+
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/parameter/makefile.in b/src/live_effects/parameter/makefile.in
new file mode 100644
index 000000000..2a6294c4d
--- /dev/null
+++ b/src/live_effects/parameter/makefile.in
@@ -0,0 +1,17 @@
+# Convenience stub makefile to call the real Makefile.
+
+@SET_MAKE@
+
+# Explicit so that it's the default rule.
+all:
+ cd ../.. && $(MAKE) live_effects/parameter/all
+
+clean %.a %.o:
+ cd ../.. && $(MAKE) live_effects/parameter/$@
+
+.PHONY: all clean
+
+OBJEXT = @OBJEXT@
+
+.SUFFIXES:
+.SUFFIXES: .a .$(OBJEXT)
diff --git a/src/live_effects/parameter/parameter.cpp b/src/live_effects/parameter/parameter.cpp
new file mode 100644
index 000000000..6806a1d49
--- /dev/null
+++ b/src/live_effects/parameter/parameter.cpp
@@ -0,0 +1,105 @@
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/parameter/parameter.h"
+#include "live_effects/effect.h"
+#include "svg/svg.h"
+
+#include <gtkmm.h>
+#include "ui/widget/scalar.h"
+
+#include "svg/stringstream.h"
+
+#include "verbs.h"
+
+#define noLPEREALPARAM_DEBUG
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+
+Parameter::Parameter( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
+ Effect* effect )
+{
+ param_label = label;
+ param_tooltip = tip;
+ param_key = key;
+ param_wr = wr;
+ param_effect = effect;
+}
+
+
+
+/*###########################################
+ * REAL PARAM
+ */
+RealParam::RealParam( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
+ Effect* effect, gdouble initial_value)
+ : Parameter(label, tip, key, wr, effect)
+{
+ value = initial_value;
+ rsu = NULL;
+}
+
+RealParam::~RealParam()
+{
+ if (rsu)
+ delete rsu;
+}
+
+bool
+RealParam::param_readSVGValue(const gchar * strvalue)
+{
+ double newval;
+ unsigned int success = sp_svg_number_read_d(strvalue, &newval);
+ if (success == 1) {
+ value = newval;
+ return true;
+ }
+ return false;
+}
+
+gchar *
+RealParam::param_writeSVGValue() const
+{
+ Inkscape::SVGOStringStream os;
+ os << rsu->getS()->getValue();
+ gchar * str = g_strdup(os.str().c_str());
+ return str;
+}
+
+Gtk::Widget *
+RealParam::param_getWidget()
+{
+ if (!rsu) {
+ rsu = new Inkscape::UI::Widget::RegisteredScalar();
+ rsu->init(param_label, param_tooltip, param_key, *param_wr, param_effect->getRepr(), param_effect->getSPDoc());
+ rsu->setValue(value);
+ rsu->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change scalar parameter"));
+ }
+ return dynamic_cast<Gtk::Widget *> (rsu->getS());
+}
+
+
+}; /* namespace LivePathEffect */
+
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/parameter/parameter.h b/src/live_effects/parameter/parameter.h
new file mode 100644
index 000000000..327d3d153
--- /dev/null
+++ b/src/live_effects/parameter/parameter.h
@@ -0,0 +1,84 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_H
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_H
+
+/*
+ * Inkscape::LivePathEffectParameters
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glibmm/ustring.h>
+#include <2geom/point.h>
+#include <2geom/path.h>
+
+#include "ui/widget/registry.h"
+#include "ui/widget/registered-widget.h"
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+class Effect;
+
+class Parameter {
+public:
+ Parameter(const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, Effect* effect);
+ virtual ~Parameter() {};
+
+ virtual bool param_readSVGValue(const gchar * strvalue) = 0; // returns true if new value is valid / accepted.
+ virtual gchar * param_writeSVGValue() const = 0;
+
+ // This returns pointer to the parameter's widget to be put in the live-effects dialog. Must also create the
+ // necessary widget if it does not exist yet.
+ virtual Gtk::Widget * param_getWidget() = 0;
+ virtual Glib::ustring * param_getTooltip() { return &param_tooltip; };
+
+ Glib::ustring param_key;
+ Inkscape::UI::Widget::Registry * param_wr;
+ Glib::ustring param_label;
+
+protected:
+ Glib::ustring param_tooltip;
+
+ Effect* param_effect;
+
+private:
+ Parameter(const Parameter&);
+ Parameter& operator=(const Parameter&);
+};
+
+
+class RealParam : public Parameter {
+public:
+ RealParam( const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key,
+ Inkscape::UI::Widget::Registry* wr, Effect* effect, gdouble initial_value = 1.0);
+ ~RealParam();
+
+ bool param_readSVGValue(const gchar * strvalue);
+ gchar * param_writeSVGValue() const;
+
+ Gtk::Widget * param_getWidget();
+
+ inline operator gdouble()
+ { return value; };
+
+private:
+ RealParam(const RealParam&);
+ RealParam& operator=(const RealParam&);
+
+ gdouble value;
+ Inkscape::UI::Widget::RegisteredScalar * rsu;
+};
+
+
+}; //namespace LivePathEffect
+
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp
new file mode 100644
index 000000000..90974f686
--- /dev/null
+++ b/src/live_effects/parameter/path.cpp
@@ -0,0 +1,166 @@
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_PATH_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/parameter/path.h"
+#include "live_effects/effect.h"
+#include "live_effects/n-art-bpath-2geom.h"
+#include "svg/svg.h"
+#include <2geom/svg-path-parser.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/d2.h>
+
+#include "ui/widget/point.h"
+#include "widgets/icon.h"
+#include <gtk/gtkstock.h>
+#include "selection-chemistry.h"
+
+#include "desktop.h"
+#include "inkscape.h"
+#include "message-stack.h"
+#include "verbs.h"
+#include "document.h"
+
+#define noLPEPATHPARAM_DEBUG
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+PathParam::PathParam( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
+ Effect* effect )
+ : Parameter(label, tip, key, wr, effect)
+{
+ _widget = NULL;
+ _tooltips = NULL;
+}
+
+PathParam::~PathParam()
+{
+ if (_tooltips)
+ delete _tooltips;
+ // _widget is managed by GTK so do not delete!
+}
+
+bool
+PathParam::param_readSVGValue(const gchar * strvalue)
+{
+ if (strvalue) {
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > newpath;
+ std::vector<Geom::Path> temppath = SVGD_to_2GeomPath(strvalue);
+ for (unsigned int i=0; i < temppath.size(); i++) {
+ newpath.concat( temppath[i].toPwSb() );
+ }
+ *( dynamic_cast<Geom::Piecewise<Geom::D2<Geom::SBasis> > *> (this) ) = newpath;
+ return true;
+ }
+
+ return false;
+}
+
+gchar *
+PathParam::param_writeSVGValue() const
+{
+ const std::vector<Geom::Path> temppath =
+ Geom::path_from_piecewise(* dynamic_cast<const Geom::Piecewise<Geom::D2<Geom::SBasis> > *> (this), LPE_CONVERSION_TOLERANCE);
+ gchar * svgd = SVGD_from_2GeomPath( temppath );
+ return svgd;
+}
+
+Gtk::Widget *
+PathParam::param_getWidget()
+{
+ if (!_widget) {
+ _widget = Gtk::manage(new Gtk::HBox());
+ _tooltips = new Gtk::Tooltips();
+
+ Gtk::Label* pLabel = Gtk::manage(new Gtk::Label(param_label));
+ static_cast<Gtk::HBox*>(_widget)->pack_start(*pLabel, true, true);
+ _tooltips->set_tip(*pLabel, param_tooltip);
+
+ Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( "draw_node", Inkscape::ICON_SIZE_BUTTON) );
+ Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
+ pButton->set_relief(Gtk::RELIEF_NONE);
+ pIcon->show();
+ pButton->add(*pIcon);
+ pButton->show();
+ pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_edit_button_click));
+ static_cast<Gtk::HBox*>(_widget)->pack_start(*pButton, true, true);
+ _tooltips->set_tip(*pButton, _("Edit on-canvas"));
+#ifndef LPEPATHPARAM_DEBUG
+ pButton->set_sensitive(false);
+#endif
+
+ pIcon = Gtk::manage( sp_icon_get_icon( GTK_STOCK_PASTE, Inkscape::ICON_SIZE_BUTTON) );
+ pButton = Gtk::manage(new Gtk::Button());
+ pButton->set_relief(Gtk::RELIEF_NONE);
+ pIcon->show();
+ pButton->add(*pIcon);
+ pButton->show();
+ pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_paste_button_click));
+ static_cast<Gtk::HBox*>(_widget)->pack_start(*pButton, true, true);
+ _tooltips->set_tip(*pButton, _("Paste path"));
+
+ static_cast<Gtk::HBox*>(_widget)->show_all_children();
+
+ }
+ return dynamic_cast<Gtk::Widget *> (_widget);
+}
+
+void
+PathParam::on_edit_button_click()
+{
+ g_message("give this path to edit on canvas!");
+}
+
+void
+PathParam::on_paste_button_click()
+{
+ // check if something is in the clipboard
+ GSList * clipboard = sp_selection_get_clipboard();
+ if (clipboard == NULL || clipboard->data == NULL) {
+ SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Nothing on the clipboard."));
+ return;
+ }
+
+ Inkscape::XML::Node *repr = (Inkscape::XML::Node *) clipboard->data;
+ if (!strcmp (repr->name(), "svg:path")) {
+ const char * svgd = repr->attribute("d");
+ if (svgd) {
+ param_write_to_repr(svgd);
+ signal_path_pasted.emit();
+ sp_document_done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
+ _("Paste path parameter"));
+ }
+ } else {
+ SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Clipboard does not contain a path."));
+ }
+
+}
+
+void
+PathParam::param_write_to_repr(const char * svgd)
+{
+ param_effect->getRepr()->setAttribute(param_key.c_str(), svgd);
+}
+
+
+}; /* namespace LivePathEffect */
+
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/parameter/path.h b/src/live_effects/parameter/path.h
new file mode 100644
index 000000000..39ea9e2d8
--- /dev/null
+++ b/src/live_effects/parameter/path.h
@@ -0,0 +1,56 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_PATH_H
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_PATH_H
+
+/*
+ * Inkscape::LivePathEffectParameters
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gtypes.h>
+#include <2geom/path.h>
+
+#include "ui/widget/registry.h"
+#include <gtkmm/tooltips.h>
+
+#include "live_effects/parameter/parameter.h"
+
+#include <sigc++/sigc++.h>
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+class PathParam : public Geom::Piecewise<Geom::D2<Geom::SBasis> >, public Parameter {
+public:
+ PathParam(const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, Effect* effect);;
+ ~PathParam();
+
+ Gtk::Widget * param_getWidget();
+
+ bool param_readSVGValue(const gchar * strvalue);
+ gchar * param_writeSVGValue() const;
+
+ sigc::signal <void> signal_path_pasted;
+
+private:
+ PathParam(const PathParam&);
+ PathParam& operator=(const PathParam&);
+
+ Gtk::Widget * _widget;
+ Gtk::Tooltips * _tooltips;
+
+ void param_write_to_repr(const char * svgd);
+
+ void on_edit_button_click();
+ void on_paste_button_click();
+};
+
+
+}; //namespace LivePathEffect
+
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/parameter/point.cpp b/src/live_effects/parameter/point.cpp
new file mode 100644
index 000000000..8079f54eb
--- /dev/null
+++ b/src/live_effects/parameter/point.cpp
@@ -0,0 +1,166 @@
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_POINT_CPP
+
+/*
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/parameter/point.h"
+#include "live_effects/effect.h"
+#include "svg/svg.h"
+#include "svg/stringstream.h"
+#include <gtkmm.h>
+#include "ui/widget/point.h"
+#include "widgets/icon.h"
+
+#include "knot.h"
+#include "inkscape.h"
+#include "verbs.h"
+
+#define noLPEPOINTPARAM_DEBUG
+
+#define PRM_KNOT_COLOR_NORMAL 0xffffff00
+#define PRM_KNOT_COLOR_SELECTED 0x0000ff00
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+PointParam::PointParam( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
+ Effect* effect )
+ : Geom::Point(0,0), Parameter(label, tip, key, wr, effect)
+{
+ _widget = NULL;
+ pointwdg = NULL;
+ knot = NULL;
+ _tooltips = NULL;
+}
+
+PointParam::~PointParam()
+{
+ if (pointwdg)
+ delete pointwdg;
+ if (_tooltips)
+ delete _tooltips;
+
+ if (knot)
+ g_object_unref (G_OBJECT (knot));
+}
+
+bool
+PointParam::param_readSVGValue(const gchar * strvalue)
+{
+ gchar ** strarray = g_strsplit(strvalue, ",", 2);
+ double newx, newy;
+ unsigned int success = sp_svg_number_read_d(strarray[0], &newx);
+ success += sp_svg_number_read_d(strarray[1], &newy);
+ g_strfreev (strarray);
+ if (success == 2) {
+ *dynamic_cast<Geom::Point *>( this ) = Geom::Point(newx, newy);
+ return true;
+ }
+ return false;
+}
+
+gchar *
+PointParam::param_writeSVGValue() const
+{
+ Inkscape::SVGOStringStream os;
+ os << pointwdg->getPoint()->getXValue() << "," << pointwdg->getPoint()->getYValue();
+ gchar * str = g_strdup(os.str().c_str());
+ return str;
+}
+
+Gtk::Widget *
+PointParam::param_getWidget()
+{
+ if (!_widget) {
+ pointwdg = new Inkscape::UI::Widget::RegisteredPoint();
+ pointwdg->init(param_label, param_tooltip, param_key, *param_wr, param_effect->getRepr(), param_effect->getSPDoc());
+ pointwdg->setValue(0.1, 0.2);
+ pointwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change point parameter"));
+
+ Gtk::Widget* pIcon = Gtk::manage( sp_icon_get_icon( "draw_node", Inkscape::ICON_SIZE_BUTTON) );
+ Gtk::Button * pButton = Gtk::manage(new Gtk::Button());
+ pButton->set_relief(Gtk::RELIEF_NONE);
+ pIcon->show();
+ pButton->add(*pIcon);
+ pButton->show();
+ pButton->signal_clicked().connect(sigc::mem_fun(*this, &PointParam::on_button_click));
+#ifndef LPEPOINTPARAM_DEBUG
+ pButton->set_sensitive(false);
+#endif
+
+ _widget = Gtk::manage( new Gtk::HBox() );
+ static_cast<Gtk::HBox*>(_widget)->pack_start(*pButton, true, true);
+ static_cast<Gtk::HBox*>(_widget)->pack_start(*(pointwdg->getPoint()), true, true);
+ static_cast<Gtk::HBox*>(_widget)->show_all_children();
+
+ _tooltips = new Gtk::Tooltips();
+ _tooltips->set_tip(*pButton, _("Edit on-canvas"));
+ }
+ return dynamic_cast<Gtk::Widget *> (_widget);
+}
+
+void
+PointParam::param_setValue(Geom::Point newpoint)
+{
+ *dynamic_cast<Geom::Point *>( this ) = newpoint;
+ pointwdg->setValue(newpoint[0], newpoint[1]);
+}
+
+
+// CALLBACKS:
+
+void
+PointParam::on_button_click()
+{
+ g_message("add knot to canvas on correct location :S");
+
+ if (!knot) {
+ // create the knot
+ knot = sp_knot_new (SP_ACTIVE_DESKTOP, NULL);
+ knot->setMode(SP_KNOT_MODE_XOR);
+ knot->setFill(PRM_KNOT_COLOR_NORMAL, PRM_KNOT_COLOR_NORMAL, PRM_KNOT_COLOR_NORMAL);
+ knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff);
+ sp_knot_update_ctrl(knot);
+
+ // move knot to the given point
+ sp_knot_set_position (knot, &NR::Point((*this)[0], (*this)[1]), SP_KNOT_STATE_NORMAL);
+ sp_knot_show (knot);
+/*
+ // connect knot's signals
+ if ( (draggable) // it can be NULL if a node in unsnapped (eg. focus point unsnapped from center)
+ // luckily, midstops never snap to other nodes so are never unsnapped...
+ && ( (draggable->point_type == POINT_LG_MID)
+ || (draggable->point_type == POINT_RG_MID1)
+ || (draggable->point_type == POINT_RG_MID2) ) )
+ {
+ this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_midpoint_handler), this);
+ } else {
+ this->handler_id = g_signal_connect (G_OBJECT (this->knot), "moved", G_CALLBACK (gr_knot_moved_handler), this);
+ }
+ g_signal_connect (G_OBJECT (this->knot), "clicked", G_CALLBACK (gr_knot_clicked_handler), this);
+ g_signal_connect (G_OBJECT (this->knot), "doubleclicked", G_CALLBACK (gr_knot_doubleclicked_handler), this);
+ g_signal_connect (G_OBJECT (this->knot), "grabbed", G_CALLBACK (gr_knot_grabbed_handler), this);
+ g_signal_connect (G_OBJECT (this->knot), "ungrabbed", G_CALLBACK (gr_knot_ungrabbed_handler), this);
+*/
+ }
+}
+
+}; /* namespace LivePathEffect */
+
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/live_effects/parameter/point.h b/src/live_effects/parameter/point.h
new file mode 100644
index 000000000..1240ea3d1
--- /dev/null
+++ b/src/live_effects/parameter/point.h
@@ -0,0 +1,57 @@
+#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_POINT_H
+#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_POINT_H
+
+/*
+ * Inkscape::LivePathEffectParameters
+ *
+* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gtypes.h>
+#include <2geom/point.h>
+
+#include "ui/widget/registry.h"
+#include "ui/widget/registered-widget.h"
+#include <gtkmm/tooltips.h>
+
+#include "live_effects/parameter/parameter.h"
+
+struct SPKnot;
+
+namespace Inkscape {
+
+namespace LivePathEffect {
+
+
+class PointParam : public Geom::Point, public Parameter {
+public:
+ PointParam(const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, Effect* effect);;
+ ~PointParam();
+
+ Gtk::Widget * param_getWidget();
+
+ bool param_readSVGValue(const gchar * strvalue);
+ gchar * param_writeSVGValue() const;
+
+ void param_setValue(Geom::Point newpoint);
+
+private:
+ PointParam(const PointParam&);
+ PointParam& operator=(const PointParam&);
+
+ Gtk::Widget * _widget;
+ Gtk::Tooltips * _tooltips;
+ Inkscape::UI::Widget::RegisteredPoint * pointwdg;
+ void on_button_click();
+
+ SPKnot *knot;
+};
+
+
+}; //namespace LivePathEffect
+
+}; //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/todo.txt b/src/live_effects/todo.txt
new file mode 100644
index 000000000..87bafe4a1
--- /dev/null
+++ b/src/live_effects/todo.txt
@@ -0,0 +1,19 @@
+reminder list
+
+
+cleanup nodepath code that draws helper path
+
+implement effect application to shapes: sp_shape_apply_path_effect
+ (done: star, ellipse, spiral)
+ARCS !!! see sp_arc_set_elliptical_path_attribute(SPArc *arc, Inkscape::XML::Node *repr)
+
+make sp_nodepath_is_over_stroke perhaps
+
+Parameters:
+- make robust (?)
+- add range checking etc
+- add more types! (straightlinepath, enum, bool)
+- -->> add write to svg functionality (for lpeobject->write)
+
+
+find dir "fixme" and fix'em! \ No newline at end of file
diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h
index cdf725232..422d1c804 100644
--- a/src/menus-skeleton.h
+++ b/src/menus-skeleton.h
@@ -215,6 +215,8 @@ static char const menus_skeleton[] =
" <separator/>\n"
" <verb verb-id=\"SelectionSimplify\" />\n"
" <verb verb-id=\"SelectionReverse\" />\n"
+" <separator/>\n"
+" <verb verb-id=\"DialogLivePathEffect\" />\n"
" </submenu>\n"
" <submenu name=\"" N_("_Text") "\">\n"
" <verb verb-id=\"DialogText\" />\n"
diff --git a/src/node-context.cpp b/src/node-context.cpp
index 3084cdb29..3b7a085be 100644
--- a/src/node-context.cpp
+++ b/src/node-context.cpp
@@ -191,111 +191,8 @@ sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEve
{
gint ret = FALSE;
- SPDesktop *desktop = event_context->desktop;
- Inkscape::Selection *selection = sp_desktop_selection (desktop);
-
- SPNodeContext *nc = SP_NODE_CONTEXT(event_context);
-
- switch (event->type) {
- case GDK_2BUTTON_PRESS:
- case GDK_BUTTON_RELEASE:
- if (event->button.button == 1 && !event_context->space_panning) {
- if (!nc->drag) {
-
- // find out clicked item, disregarding groups, honoring Alt
- SPItem *item_clicked = sp_event_context_find_item (desktop,
- NR::Point(event->button.x, event->button.y),
- (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
- // find out if we're over the selected item, disregarding groups
- SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
- NR::Point(event->button.x, event->button.y));
-
- bool over_stroke = false;
- if (item_over && nc->shape_editor->has_nodepath()) {
- over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
- }
-
- if (over_stroke || nc->added_node) {
- switch (event->type) {
- case GDK_BUTTON_RELEASE:
- if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
- //add a node
- nc->shape_editor->add_node_near_point();
- } else {
- if (nc->added_node) { // we just received double click, ignore release
- nc->added_node = false;
- break;
- }
- //select the segment
- if (event->button.state & GDK_SHIFT_MASK) {
- nc->shape_editor->select_segment_near_point(true);
- } else {
- nc->shape_editor->select_segment_near_point(false);
- }
- desktop->updateNow();
- }
- break;
- case GDK_2BUTTON_PRESS:
- //add a node
- nc->shape_editor->add_node_near_point();
- nc->added_node = true;
- break;
- default:
- break;
- }
- } else if (event->button.state & GDK_SHIFT_MASK) {
- selection->toggle(item_clicked);
- desktop->updateNow();
- } else {
- selection->set(item_clicked);
- desktop->updateNow();
- }
-
- ret = TRUE;
- }
- break;
- }
- break;
- case GDK_BUTTON_PRESS:
- if (event->button.button == 1 && !(event->button.state & GDK_SHIFT_MASK) && !event_context->space_panning) {
- // save drag origin
- event_context->xp = (gint) event->button.x;
- event_context->yp = (gint) event->button.y;
- event_context->within_tolerance = true;
- nc->shape_editor->cancel_hit();
-
- if (!nc->drag) {
- // find out if we're over the selected item, disregarding groups
- SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
- NR::Point(event->button.x, event->button.y));
-
- if (nc->shape_editor->has_nodepath() && selection->single() && item_over) {
-
- // save drag origin
- bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
- //only dragging curves
- if (over_stroke) {
- ret = TRUE;
- } else {
- break;
- }
- } else {
- break;
- }
-
- ret = TRUE;
- }
- break;
- }
- break;
- default:
- break;
- }
-
- if (!ret) {
- if (((SPEventContextClass *) parent_class)->item_handler)
- ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
- }
+ if (((SPEventContextClass *) parent_class)->item_handler)
+ ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event);
return ret;
}
@@ -313,7 +210,6 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
double const offset = prefs_get_double_attribute_limited("options.defaultscale", "value", 2, 0, 1000);
gint ret = FALSE;
-
switch (event->type) {
case GDK_BUTTON_PRESS:
if (event->button.button == 1 && !event_context->space_panning) {
@@ -323,6 +219,19 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
event_context->within_tolerance = true;
nc->shape_editor->cancel_hit();
+ if (!(event->button.state & GDK_SHIFT_MASK)) {
+ if (!nc->drag) {
+ if (nc->shape_editor->has_nodepath() && selection->single() /* && item_over */) {
+ // save drag origin
+ bool over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), true);
+ //only dragging curves
+ if (over_stroke) {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+ }
NR::Point const button_w(event->button.x,
event->button.y);
NR::Point const button_dt(desktop->w2d(button_w));
@@ -389,12 +298,8 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
break;
}
- SPItem *item_over = sp_event_context_over_item (desktop, selection->singleItem(),
- NR::Point(event->motion.x, event->motion.y));
bool over_stroke = false;
- if (item_over && nc->shape_editor->has_nodepath()) {
- over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
- }
+ over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->motion.x, event->motion.y), false);
if (nc->cursor_drag && !over_stroke) {
event_context->cursor_shape = cursor_node_xpm;
@@ -411,32 +316,87 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
}
}
break;
+
+ case GDK_2BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
- event_context->xp = event_context->yp = 0;
- if (event->button.button == 1 && !event_context->space_panning) {
+ if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) {
+ // find out clicked item, disregarding groups, honoring Alt
+ SPItem *item_clicked = sp_event_context_find_item (desktop,
+ NR::Point(event->button.x, event->button.y),
+ (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE);
- NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
+ event_context->xp = event_context->yp = 0;
- if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
- nc->shape_editor->finish_drag();
- } else if (b && !event_context->within_tolerance) { // drag to select
- nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
- } else {
- if (!(nc->rb_escaped)) { // unless something was cancelled
- if (nc->shape_editor->has_selection())
- nc->shape_editor->deselect();
- else
- sp_desktop_selection(desktop)->clear();
+ bool over_stroke = false;
+ if (nc->shape_editor->has_nodepath()) {
+ over_stroke = nc->shape_editor->is_over_stroke(NR::Point(event->button.x, event->button.y), false);
+ }
+
+ if (item_clicked || over_stroke) {
+ if (over_stroke || nc->added_node) {
+ switch (event->type) {
+ case GDK_BUTTON_RELEASE:
+ if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) {
+ //add a node
+ nc->shape_editor->add_node_near_point();
+ } else {
+ if (nc->added_node) { // we just received double click, ignore release
+ nc->added_node = false;
+ break;
+ }
+ //select the segment
+ if (event->button.state & GDK_SHIFT_MASK) {
+ nc->shape_editor->select_segment_near_point(true);
+ } else {
+ nc->shape_editor->select_segment_near_point(false);
+ }
+ desktop->updateNow();
+ }
+ break;
+ case GDK_2BUTTON_PRESS:
+ //add a node
+ nc->shape_editor->add_node_near_point();
+ nc->added_node = true;
+ break;
+ default:
+ break;
+ }
+ } else if (event->button.state & GDK_SHIFT_MASK) {
+ selection->toggle(item_clicked);
+ desktop->updateNow();
+ } else {
+ selection->set(item_clicked);
+ desktop->updateNow();
+ }
+ ret = TRUE;
+ break;
+ }
+ }
+ if (event->type == GDK_BUTTON_RELEASE) {
+ event_context->xp = event_context->yp = 0;
+ if (event->button.button == 1) {
+ NR::Maybe<NR::Rect> b = Inkscape::Rubberband::get()->getRectangle();
+
+ if (nc->shape_editor->hits_curve() && !event_context->within_tolerance) { //drag curve
+ nc->shape_editor->finish_drag();
+ } else if (b && !event_context->within_tolerance) { // drag to select
+ nc->shape_editor->select_rect(*b, event->button.state & GDK_SHIFT_MASK);
+ } else {
+ if (!(nc->rb_escaped)) { // unless something was cancelled
+ if (nc->shape_editor->has_selection())
+ nc->shape_editor->deselect();
+ else
+ sp_desktop_selection(desktop)->clear();
+ }
}
+ ret = TRUE;
+ Inkscape::Rubberband::get()->stop();
+ desktop->updateNow();
+ nc->rb_escaped = false;
+ nc->drag = FALSE;
+ nc->shape_editor->cancel_hit();
+ nc->current_state = SP_NODE_CONTEXT_INACTIVE;
}
- ret = TRUE;
- Inkscape::Rubberband::get()->stop();
- desktop->updateNow();
- nc->rb_escaped = false;
- nc->drag = FALSE;
- nc->shape_editor->cancel_hit();
- nc->current_state = SP_NODE_CONTEXT_INACTIVE;
- break;
}
break;
case GDK_KEY_PRESS:
diff --git a/src/nodepath.cpp b/src/nodepath.cpp
index 28c845492..29a6f94cf 100644
--- a/src/nodepath.cpp
+++ b/src/nodepath.cpp
@@ -15,11 +15,13 @@
#endif
#include <gdk/gdkkeysyms.h>
+#include "display/canvas-bpath.h"
#include "display/curve.h"
#include "display/sp-ctrlline.h"
#include "display/sodipodi-ctrl.h"
#include <glibmm/i18n.h>
#include "libnr/n-art-bpath.h"
+#include "libnr/nr-path.h"
#include "helper/units.h"
#include "knot.h"
#include "inkscape.h"
@@ -45,6 +47,7 @@
#include "display/bezier-utils.h"
#include <vector>
#include <algorithm>
+#include "live_effects/lpeobject.h"
class NR::Matrix;
@@ -135,71 +138,96 @@ static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *
static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me);
+static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key);
+static void sp_nodepath_object_set_curve (SPObject *object, SPCurve *curve);
+
// active_node indicates mouseover node
Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL;
/**
* \brief Creates new nodepath from item
+* repr_key_in should be NULL, unless you are called Johan or really know what you are doing! (See "if (repr_key_in)" below)
*/
-Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool show_handles)
+Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in)
{
- Inkscape::XML::Node *repr = SP_OBJECT(item)->repr;
+ Inkscape::XML::Node *repr = object->repr;
/** \todo
* FIXME: remove this. We don't want to edit paths inside flowtext.
* Instead we will build our flowtext with cloned paths, so that the
* real paths are outside the flowtext and thus editable as usual.
*/
- if (SP_IS_FLOWTEXT(item)) {
- for (SPObject *child = sp_object_first_child(SP_OBJECT(item)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+ if (SP_IS_FLOWTEXT(object)) {
+ for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
if SP_IS_FLOWREGION(child) {
SPObject *grandchild = sp_object_first_child(SP_OBJECT(child));
if (grandchild && SP_IS_PATH(grandchild)) {
- item = SP_ITEM(grandchild);
+ object = SP_ITEM(grandchild);
break;
}
}
}
}
- if (!SP_IS_PATH(item))
- return NULL;
- SPPath *path = SP_PATH(item);
- SPCurve *curve = sp_shape_get_curve(SP_SHAPE(path));
+ SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in);
+
if (curve == NULL)
return NULL;
NArtBpath *bpath = sp_curve_first_bpath(curve);
gint length = curve->end;
- if (length == 0)
+ if (length == 0) {
+ sp_curve_unref(curve);
return NULL; // prevent crash for one-node paths
-
- gchar const *nodetypes = repr->attribute("sodipodi:nodetypes");
- gchar *typestr = parse_nodetypes(nodetypes, length);
+ }
//Create new nodepath
Inkscape::NodePath::Path *np = g_new(Inkscape::NodePath::Path, 1);
- if (!np)
+ if (!np) {
+ sp_curve_unref(curve);
return NULL;
+ }
// Set defaults
np->desktop = desktop;
- np->path = path;
+ np->object = object;
np->subpaths = NULL;
np->selected = NULL;
np->shape_editor = NULL; //Let the shapeeditor that makes this set it
np->livarot_path = NULL;
np->local_change = 0;
np->show_handles = show_handles;
+ np->helper_path = NULL;
+ np->curve = sp_curve_copy(curve);
+ np->show_helperpath = false;
+ np->straight_path = false;
+
// we need to update item's transform from the repr here,
// because they may be out of sync when we respond
// to a change in repr by regenerating nodepath --bb
- sp_object_read_attr(SP_OBJECT(item), "transform");
+ sp_object_read_attr(object, "transform");
- np->i2d = sp_item_i2d_affine(SP_ITEM(path));
+ np->i2d = sp_item_i2d_affine(SP_ITEM(object));
np->d2i = np->i2d.inverse();
+
np->repr = repr;
+ if (repr_key_in) {
+ np->repr_key = g_strdup(repr_key_in);
+ np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL);
+ np->show_helperpath = true;
+ } else {
+ np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes");
+ if ( SP_SHAPE(np->object)->path_effect_href ) {
+ np->repr_key = g_strdup("inkscape:original-d");
+ np->show_helperpath = true;
+ } else {
+ np->repr_key = g_strdup("d");
+ }
+ }
+
+ gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key);
+ gchar *typestr = parse_nodetypes(nodetypes, length);
// create the subpath(s) from the bpath
NArtBpath *b = bpath;
@@ -216,6 +244,17 @@ Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPItem *item, bool
// create the livarot representation from the same item
sp_nodepath_ensure_livarot_path(np);
+ // Draw helper curve
+ if (np->show_helperpath) {
+ SPCurve *helper_curve = sp_curve_copy(np->curve);
+ sp_curve_transform(helper_curve, np->i2d );
+ np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(desktop), helper_curve);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), 0xff0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO);
+ sp_canvas_item_show(np->helper_path);
+ sp_curve_unref(helper_curve);
+ }
+
return np;
}
@@ -241,6 +280,25 @@ void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
delete np->livarot_path;
np->livarot_path = NULL;
}
+
+ if (np->helper_path) {
+ GtkObject *temp = np->helper_path;
+ np->helper_path = NULL;
+ gtk_object_destroy(temp);
+ }
+ if (np->curve) {
+ sp_curve_unref(np->curve);
+ np->curve = NULL;
+ }
+
+ if (np->repr_key) {
+ g_free(np->repr_key);
+ np->repr_key = NULL;
+ }
+ if (np->repr_nodetypes_key) {
+ g_free(np->repr_nodetypes_key);
+ np->repr_nodetypes_key = NULL;
+ }
np->desktop = NULL;
@@ -250,10 +308,15 @@ void sp_nodepath_destroy(Inkscape::NodePath::Path *np) {
void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np)
{
- if (np && np->livarot_path == NULL && np->path && SP_IS_ITEM(np->path)) {
- np->livarot_path = Path_for_item (np->path, true, true);
+ if (np && np->livarot_path == NULL && np->object && SP_IS_ITEM(np->object)) {
+ SPCurve *curve = create_curve(np);
+ NArtBpath *bpath = SP_CURVE_BPATH(curve);
+ np->livarot_path = bpath_to_Path(bpath);
+
if (np->livarot_path)
np->livarot_path->ConvertWithBackData(0.01);
+
+ sp_curve_unref(curve);
}
}
@@ -450,11 +513,17 @@ static void update_object(Inkscape::NodePath::Path *np)
{
g_assert(np);
- SPCurve *curve = create_curve(np);
+ sp_curve_unref(np->curve);
+ np->curve = create_curve(np);
- sp_shape_set_curve(SP_SHAPE(np->path), curve, TRUE);
+ sp_nodepath_object_set_curve(np->object, np->curve);
- sp_curve_unref(curve);
+ if (np->show_helperpath) {
+ SPCurve * helper_curve = sp_curve_copy(np->curve);
+ sp_curve_transform(helper_curve, np->i2d );
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
+ sp_curve_unref(helper_curve);
+ }
}
/**
@@ -464,26 +533,35 @@ static void update_repr_internal(Inkscape::NodePath::Path *np)
{
g_assert(np);
- Inkscape::XML::Node *repr = SP_OBJECT(np->path)->repr;
+ Inkscape::XML::Node *repr = np->object->repr;
- SPCurve *curve = create_curve(np);
+ sp_curve_unref(np->curve);
+ np->curve = create_curve(np);
+
gchar *typestr = create_typestr(np);
- gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
+ gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(np->curve));
- if (repr->attribute("d") == NULL || strcmp(svgpath, repr->attribute("d"))) { // d changed
+ // determine if path has an effect applied and write to correct "d" attribute.
+ if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed
np->local_change++;
- repr->setAttribute("d", svgpath);
+ repr->setAttribute(np->repr_key, svgpath);
}
- if (repr->attribute("sodipodi:nodetypes") == NULL || strcmp(typestr, repr->attribute("sodipodi:nodetypes"))) { // nodetypes changed
+ if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed
np->local_change++;
- repr->setAttribute("sodipodi:nodetypes", typestr);
+ repr->setAttribute(np->repr_nodetypes_key, typestr);
}
g_free(svgpath);
g_free(typestr);
- sp_curve_unref(curve);
-}
+
+ if (np->show_helperpath) {
+ SPCurve * helper_curve = sp_curve_copy(np->curve);
+ sp_curve_transform(helper_curve, np->i2d );
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve);
+ sp_curve_unref(helper_curve);
+ }
+ }
/**
* Update XML path node with data from path object, commit changes forever.
@@ -527,7 +605,7 @@ static void stamp_repr(Inkscape::NodePath::Path *np)
{
g_assert(np);
- Inkscape::XML::Node *old_repr = SP_OBJECT(np->path)->repr;
+ Inkscape::XML::Node *old_repr = np->object->repr;
Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document());
// remember the position of the item
@@ -540,8 +618,8 @@ static void stamp_repr(Inkscape::NodePath::Path *np)
gchar *svgpath = sp_svg_write_path(SP_CURVE_BPATH(curve));
- new_repr->setAttribute("d", svgpath);
- new_repr->setAttribute("sodipodi:nodetypes", typestr);
+ new_repr->setAttribute(np->repr_key, svgpath);
+ new_repr->setAttribute(np->repr_nodetypes_key, typestr);
// add the new repr to the parent
parent->appendChild(new_repr);
@@ -999,7 +1077,7 @@ static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath,
for (GList *l = nodepath->selected; l != NULL; l = l->next) {
Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
- Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, n->subpath->nodepath->path);
+ Inkscape::SnappedPoint const s = m.freeSnap(Inkscape::Snapper::SNAPPOINT_NODE, n->pos + delta, SP_PATH(n->subpath->nodepath->object));
if (s.getDistance() < best) {
best = s.getDistance();
best_pt = s.getPoint() - n->pos;
@@ -4338,6 +4416,60 @@ sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to Sha
}
}
+/*
+ * returns a *copy* of the curve of that object.
+ */
+SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) {
+ if (!object)
+ return NULL;
+
+ SPCurve *curve = NULL;
+ if (SP_IS_PATH(object)) {
+ SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object));
+ curve = sp_curve_copy(curve_new);
+ } else if ( IS_LIVEPATHEFFECT(object) && key) {
+ const gchar *svgd = object->repr->attribute(key);
+ if (svgd) {
+ NArtBpath *bpath = sp_svg_read_path(svgd);
+ SPCurve *curve_new = sp_curve_new_from_bpath(bpath);
+ if (curve_new) {
+ curve = curve_new; // don't do curve_copy because curve_new is already only created for us!
+ } else {
+ g_free(bpath);
+ }
+ }
+ }
+
+ return curve;
+}
+
+void sp_nodepath_object_set_curve (SPObject *object, SPCurve *curve) {
+ if (!object || !curve)
+ return;
+
+ if (SP_IS_PATH(object)) {
+ if (SP_SHAPE(object)->path_effect_href) {
+ sp_path_set_original_curve(SP_PATH(object), curve, true, false);
+ } else {
+ sp_shape_set_curve(SP_SHAPE(object), curve, true);
+ }
+ } else if ( IS_LIVEPATHEFFECT(object) ) {
+ g_warning("sp_nodepath_set_curve not implemented yet for lpeobjects");
+ }
+}
+
+void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) {
+ np->show_helperpath = show;
+}
+
+void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) {
+ np->straight_path = true;
+ np->show_handles = false;
+ g_message("add code to make the path straight.");
+ // do sp_nodepath_convert_node_type on all nodes?
+ // search for this text !!! "Make selected segments lines"
+}
+
/*
Local Variables:
diff --git a/src/nodepath.h b/src/nodepath.h
index 7e4066769..29d34addd 100644
--- a/src/nodepath.h
+++ b/src/nodepath.h
@@ -17,6 +17,7 @@
//#include "desktop-handles.h"
#include "libnr/nr-path-code.h"
#include "livarot/Path.h"
+#include <glibmm/ustring.h>
#include <list>
@@ -111,7 +112,7 @@ class Path {
/** Pointer to the current desktop, for reporting purposes */
SPDesktop * desktop;
/** The parent path of this nodepath */
- SPPath * path;
+ SPObject * object;
/** The context which created this nodepath. Important if this nodepath is deleted */
ShapeEditor *shape_editor;
/** The subpaths which comprise this NodePath */
@@ -122,12 +123,19 @@ class Path {
njh: I'd be guessing that these are item <-> desktop transforms.*/
NR::Matrix i2d, d2i;
/** The DOM node which describes this NodePath */
- Inkscape::XML::Node *repr;
+ Inkscape::XML::Node *repr;
+ gchar *repr_key;
+ gchar *repr_nodetypes_key;
//STL compliant method to get the selected nodes
void selection(std::list<Node *> &l);
/// livarot library is used for "point on path" and "nearest position on path", so we need to maintain its path representation as well
::Path *livarot_path;
+
+ /// draw a "sketch" of the path by using these variables
+ SPCanvasItem *helper_path;
+ SPCurve *curve;
+ bool show_helperpath;
/// true if we changed repr, to tell this change from an external one such as from undo, simplify, or another desktop
unsigned int local_change;
@@ -135,6 +143,9 @@ class Path {
/// true if we're showing selected nodes' handles
bool show_handles;
+ /// true if the path cannot contain curves, just straight lines
+ bool straight_path;
+
/// active_node points to the node that is currently mouseovered (= NULL if
/// there isn't any); we also consider the node mouseovered if it is covered
/// by one of its handles and the latter is mouseovered
@@ -246,7 +257,7 @@ enum {
};
// Do function documentation in nodepath.cpp
-Inkscape::NodePath::Path * sp_nodepath_new (SPDesktop * desktop, SPItem * item, bool show_handles);
+Inkscape::NodePath::Path * sp_nodepath_new (SPDesktop * desktop, SPObject *object, bool show_handles, const char * repr_key = NULL);
void sp_nodepath_destroy (Inkscape::NodePath::Path * nodepath);
void sp_nodepath_ensure_livarot_path(Inkscape::NodePath::Path *np);
void sp_nodepath_deselect (Inkscape::NodePath::Path *nodepath);
diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp
index 8e916193b..1e1f135f9 100644
--- a/src/selection-chemistry.cpp
+++ b/src/selection-chemistry.cpp
@@ -2962,6 +2962,11 @@ void unhide_all_in_all_layers(SPDesktop *dt) {
process_all(&unhide, dt, false);
}
+
+GSList * sp_selection_get_clipboard() {
+ return clipboard;
+}
+
/*
Local Variables:
mode:c++
diff --git a/src/selection-chemistry.h b/src/selection-chemistry.h
index 0ad465ec4..b21cbc2a1 100644
--- a/src/selection-chemistry.h
+++ b/src/selection-chemistry.h
@@ -105,6 +105,7 @@ void unlock_all_in_all_layers(SPDesktop *dt);
void unhide_all(SPDesktop *dt);
void unhide_all_in_all_layers(SPDesktop *dt);
+GSList * sp_selection_get_clipboard();
/* selection cycling */
diff --git a/src/shape-editor.cpp b/src/shape-editor.cpp
index f865d6a4d..72c5ef058 100644
--- a/src/shape-editor.cpp
+++ b/src/shape-editor.cpp
@@ -113,7 +113,7 @@ void ShapeEditor::decrement_local_change () {
SPItem *ShapeEditor::get_item () {
SPItem *item = NULL;
if (this->has_nodepath()) {
- item = SP_ITEM(this->nodepath->path);
+ item = SP_ITEM(this->nodepath->object);
} else if (this->has_knotholder()) {
item = SP_ITEM(this->knotholder->item);
}
@@ -205,6 +205,37 @@ void ShapeEditor::set_item(SPItem *item) {
}
}
+void ShapeEditor::set_livepatheffect_parameter(SPObject *lpeobject, const char * key) {
+
+ unset_item();
+
+ this->grab_node = -1;
+
+ if (lpeobject) {
+ this->nodepath = sp_nodepath_new( desktop, lpeobject,
+ (prefs_get_int_attribute("tools.nodes", "show_handles", 1) != 0),
+ key);
+ if (this->nodepath) {
+ this->nodepath->shape_editor = this;
+ }
+ //this->knotholder = sp_item_knot_holder(item, desktop);
+ g_message("create knotholder?");
+
+ if (this->nodepath || this->knotholder) {
+ // setting new listener
+ Inkscape::XML::Node *repr;
+ if (this->knotholder)
+ repr = this->knotholder->repr;
+ else
+ repr = SP_OBJECT_REPR(lpeobject);
+ if (repr) {
+ Inkscape::GC::anchor(repr);
+ sp_repr_add_listener(repr, &shapeeditor_repr_events, this);
+ }
+ }
+ }
+}
+
void ShapeEditor::nodepath_destroyed () {
this->nodepath = NULL;
}
@@ -224,7 +255,6 @@ bool ShapeEditor::is_over_stroke (NR::Point event_p, bool remember) {
//Translate click point into proper coord system
this->curvepoint_doc = desktop->w2d(event_p);
this->curvepoint_doc *= sp_item_dt2i_affine(item);
- this->curvepoint_doc *= sp_item_i2doc_affine(item);
sp_nodepath_ensure_livarot_path(this->nodepath);
@@ -285,7 +315,7 @@ bool ShapeEditor::hits_curve() {
void ShapeEditor::curve_drag(gdouble eventx, gdouble eventy) {
- if (this->nodepath) {
+ if (this->nodepath && !this->nodepath->straight_path) {
if (this->grab_node == -1) // don't know which segment to drag
return;
diff --git a/src/shape-editor.h b/src/shape-editor.h
index 8eca230a4..0e5bc22ef 100644
--- a/src/shape-editor.h
+++ b/src/shape-editor.h
@@ -36,6 +36,7 @@ public:
~ShapeEditor();
void set_item (SPItem *item);
+ void set_livepatheffect_parameter(SPObject *lpeobject, const char * key);
void unset_item ();
SPItem *get_item ();
diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp
index 6ab497116..f558bc229 100644
--- a/src/sp-ellipse.cpp
+++ b/src/sp-ellipse.cpp
@@ -76,6 +76,8 @@ static void sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags);
static void sp_genericellipse_snappoints(SPItem const *item, SnapPointsIter p);
static void sp_genericellipse_set_shape(SPShape *shape);
+static void sp_genericellipse_update_patheffect (SPShape *shape, bool write);
+
static Inkscape::XML::Node *sp_genericellipse_write(SPObject *object, Inkscape::XML::Node *repr,
guint flags);
@@ -119,6 +121,7 @@ static void sp_genericellipse_class_init(SPGenericEllipseClass *klass)
item_class->snappoints = sp_genericellipse_snappoints;
shape_class->set_shape = sp_genericellipse_set_shape;
+ shape_class->update_patheffect = sp_genericellipse_update_patheffect;
}
static void
@@ -154,6 +157,31 @@ sp_genericellipse_update(SPObject *object, SPCtx *ctx, guint flags)
((SPObjectClass *) ge_parent_class)->update(object, ctx, flags);
}
+static void
+sp_genericellipse_update_patheffect(SPShape *shape, bool write)
+{
+ sp_genericellipse_set_shape(shape);
+
+ if (write) {
+ Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
+ if ( shape->curve != NULL ) {
+ NArtBpath *abp = sp_curve_first_bpath(shape->curve);
+ if (abp) {
+ gchar *str = sp_svg_write_path(abp);
+ repr->setAttribute("d", str);
+ g_free(str);
+ } else {
+ repr->setAttribute("d", "");
+ }
+ } else {
+ repr->setAttribute("d", NULL);
+ }
+ }
+
+ ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+
#define C1 0.552
/* fixme: Think (Lauris) */
@@ -248,6 +276,7 @@ static void sp_genericellipse_set_shape(SPShape *shape)
SPCurve *c = sp_curve_new_from_bpath(nr_artpath_affine(bpath, aff));
g_assert(c != NULL);
+ sp_shape_perform_path_effect(c, SP_SHAPE (ellipse));
sp_shape_set_curve_insync((SPShape *) ellipse, c, TRUE);
sp_curve_unref(c);
}
diff --git a/src/sp-object-repr.cpp b/src/sp-object-repr.cpp
index f05455e84..9338f1019 100644
--- a/src/sp-object-repr.cpp
+++ b/src/sp-object-repr.cpp
@@ -66,6 +66,8 @@
#include "sp-fetile.h"
#include "sp-feturbulence.h"
#include "sp-femergenode.h"
+#include "live_effects/lpeobject.h"
+
enum NameType { REPR_NAME, SODIPODI_TYPE };
static unsigned const N_NAME_TYPES = SODIPODI_TYPE + 1;
@@ -176,7 +178,8 @@ populate_dtables()
{ "svg:textPath", SP_TYPE_TEXTPATH },
{ "svg:tref", SP_TYPE_TREF },
{ "svg:tspan", SP_TYPE_TSPAN },
- { "svg:use", SP_TYPE_USE }
+ { "svg:use", SP_TYPE_USE },
+ { "inkscape:path-effect", TYPE_LIVEPATHEFFECT }
};
NameTypeEntry const sodipodi_name_entries[] = {
{ "arc", SP_TYPE_ARC },
diff --git a/src/sp-object.cpp b/src/sp-object.cpp
index 6526435de..f76e70a51 100644
--- a/src/sp-object.cpp
+++ b/src/sp-object.cpp
@@ -848,7 +848,6 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No
}
/* Invoke derived methods, if any */
-
if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
(*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
}
diff --git a/src/sp-path.cpp b/src/sp-path.cpp
index 3b455e505..53cbb1637 100644
--- a/src/sp-path.cpp
+++ b/src/sp-path.cpp
@@ -48,6 +48,7 @@ static NR::Matrix sp_path_set_transform(SPItem *item, NR::Matrix const &xform);
static gchar * sp_path_description(SPItem *item);
static void sp_path_update(SPObject *object, SPCtx *ctx, guint flags);
+static void sp_path_update_patheffect(SPShape *shape, bool write);
static SPShapeClass *parent_class;
@@ -86,6 +87,7 @@ sp_path_class_init(SPPathClass * klass)
GObjectClass *gobject_class = (GObjectClass *) klass;
SPObjectClass *sp_object_class = (SPObjectClass *) klass;
SPItemClass *item_class = (SPItemClass *) klass;
+ SPShapeClass *shape_class = (SPShapeClass *) klass;
parent_class = (SPShapeClass *)g_type_class_peek_parent(klass);
@@ -99,6 +101,8 @@ sp_path_class_init(SPPathClass * klass)
item_class->description = sp_path_description;
item_class->set_transform = sp_path_set_transform;
+
+ shape_class->update_patheffect = sp_path_update_patheffect;
}
@@ -125,12 +129,14 @@ sp_path_description(SPItem * item)
}
/**
- * Initializes an SPPath. Currently does nothing.
+ * Initializes an SPPath.
*/
static void
sp_path_init(SPPath *path)
{
new (&path->connEndPair) SPConnEndPair(path);
+
+ path->original_curve = NULL;
}
static void
@@ -148,14 +154,6 @@ sp_path_finalize(GObject *obj)
static void
sp_path_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
- sp_object_read_attr(object, "d");
-
- /* d is a required attribute */
- gchar const *d = sp_object_getAttribute(object, "d", NULL);
- if (d == NULL) {
- sp_object_set(object, sp_attribute_lookup("d"), "");
- }
-
/* Are these calls actually necessary? */
sp_object_read_attr(object, "marker");
sp_object_read_attr(object, "marker-start");
@@ -167,6 +165,15 @@ sp_path_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
if (((SPObjectClass *) parent_class)->build) {
((SPObjectClass *) parent_class)->build(object, document, repr);
}
+
+ sp_object_read_attr(object, "inkscape:original-d");
+ sp_object_read_attr(object, "d");
+
+ /* d is a required attribute */
+ gchar const *d = sp_object_getAttribute(object, "d", NULL);
+ if (d == NULL) {
+ sp_object_set(object, sp_attribute_lookup("d"), "");
+ }
}
static void
@@ -176,6 +183,10 @@ sp_path_release(SPObject *object)
path->connEndPair.release();
+ if (path->original_curve) {
+ path->original_curve = sp_curve_unref (path->original_curve);
+ }
+
if (((SPObjectClass *) parent_class)->release) {
((SPObjectClass *) parent_class)->release(object);
}
@@ -191,18 +202,33 @@ sp_path_set(SPObject *object, unsigned int key, gchar const *value)
SPPath *path = (SPPath *) object;
switch (key) {
- case SP_ATTR_D:
- if (value) {
- NArtBpath *bpath = sp_svg_read_path(value);
- SPCurve *curve = sp_curve_new_from_bpath(bpath);
- if (curve) {
- sp_shape_set_curve((SPShape *) path, curve, TRUE);
- sp_curve_unref(curve);
+ case SP_ATTR_INKSCAPE_ORIGINAL_D:
+ if (value) {
+ NArtBpath *bpath = sp_svg_read_path(value);
+ SPCurve *curve = sp_curve_new_from_bpath(bpath);
+ if (curve) {
+ sp_path_set_original_curve(path, curve, TRUE, true);
+ sp_curve_unref(curve);
+ }
+ } else {
+ sp_path_set_original_curve(path, NULL, TRUE, true);
}
- } else {
- sp_shape_set_curve((SPShape *) path, NULL, TRUE);
+ object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ break;
+ case SP_ATTR_D:
+ if (!((SPShape *) path)->path_effect_href) {
+ if (value) {
+ NArtBpath *bpath = sp_svg_read_path(value);
+ SPCurve *curve = sp_curve_new_from_bpath(bpath);
+ if (curve) {
+ sp_shape_set_curve((SPShape *) path, curve, TRUE);
+ sp_curve_unref(curve);
+ }
+ } else {
+ sp_shape_set_curve((SPShape *) path, NULL, TRUE);
+ }
+ object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}
- object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_PROP_MARKER:
case SP_PROP_MARKER_START:
@@ -251,6 +277,20 @@ sp_path_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
repr->setAttribute("d", NULL);
}
+ SPPath *path = (SPPath *) object;
+ if ( path->original_curve != NULL ) {
+ NArtBpath *abp = sp_curve_first_bpath(path->original_curve);
+ if (abp) {
+ gchar *str = sp_svg_write_path(abp);
+ repr->setAttribute("inkscape:original-d", str);
+ g_free(str);
+ } else {
+ repr->setAttribute("inkscape:original-d", "");
+ }
+ } else {
+ repr->setAttribute("inkscape:original-d", NULL);
+ }
+
SP_PATH(shape)->connEndPair.writeRepr(repr);
if (((SPObjectClass *)(parent_class))->write) {
@@ -283,19 +323,30 @@ static NR::Matrix
sp_path_set_transform(SPItem *item, NR::Matrix const &xform)
{
SPShape *shape = (SPShape *) item;
+ SPPath *path = (SPPath *) item;
if (!shape->curve) { // 0 nodes, nothing to transform
return NR::identity();
}
- /* Transform the path */
- NRBPath dpath, spath;
- spath.path = SP_CURVE_BPATH(shape->curve);
- nr_path_duplicate_transform(&dpath, &spath, xform);
- SPCurve *curve = sp_curve_new_from_bpath(dpath.path);
- if (curve) {
- sp_shape_set_curve(shape, curve, TRUE);
- sp_curve_unref(curve);
+ if (path->original_curve) { /* Transform the original-d path */
+ NRBPath dorigpath, sorigpath;
+ sorigpath.path = SP_CURVE_BPATH(path->original_curve);
+ nr_path_duplicate_transform(&dorigpath, &sorigpath, xform);
+ SPCurve *origcurve = sp_curve_new_from_bpath(dorigpath.path);
+ if (origcurve) {
+ sp_path_set_original_curve(path, origcurve, TRUE, true);
+ sp_curve_unref(origcurve);
+ }
+ } else { /* Transform the path */
+ NRBPath dpath, spath;
+ spath.path = SP_CURVE_BPATH(shape->curve);
+ nr_path_duplicate_transform(&dpath, &spath, xform);
+ SPCurve *curve = sp_curve_new_from_bpath(dpath.path);
+ if (curve) {
+ sp_shape_set_curve(shape, curve, TRUE);
+ sp_curve_unref(curve);
+ }
}
// Adjust stroke
@@ -313,6 +364,88 @@ sp_path_set_transform(SPItem *item, NR::Matrix const &xform)
return NR::identity();
}
+static void
+sp_path_update_patheffect(SPShape *shape, bool write)
+{
+ SPPath *path = (SPPath *) shape;
+ if (path->original_curve) {
+ SPCurve *curve = sp_curve_copy (path->original_curve);
+ sp_shape_perform_path_effect(curve, shape);
+ sp_shape_set_curve(shape, curve, TRUE);
+ sp_curve_unref(curve);
+
+ if (write) {
+ // could also do SP_OBJECT(shape)->updateRepr(); but only the d attribute needs updating.
+ Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
+ if ( shape->curve != NULL ) {
+ NArtBpath *abp = sp_curve_first_bpath(shape->curve);
+ if (abp) {
+ gchar *str = sp_svg_write_path(abp);
+ repr->setAttribute("d", str);
+ g_free(str);
+ } else {
+ repr->setAttribute("d", "");
+ }
+ } else {
+ repr->setAttribute("d", NULL);
+ }
+ }
+ } else {
+
+ }
+}
+
+
+/**
+ * Adds a original_curve to the path. If owner is specified, a reference
+ * will be made, otherwise the curve will be copied into the path.
+ * Any existing curve in the path will be unreferenced first.
+ * This routine triggers reapplication of the an effect is present
+ * an also triggers a request to update the display. Does not write
+* result to XML when write=false.
+ */
+void
+sp_path_set_original_curve (SPPath *path, SPCurve *curve, unsigned int owner, bool write)
+{
+ if (path->original_curve) {
+ path->original_curve = sp_curve_unref (path->original_curve);
+ }
+ if (curve) {
+ if (owner) {
+ path->original_curve = sp_curve_ref (curve);
+ } else {
+ path->original_curve = sp_curve_copy (curve);
+ }
+ }
+ sp_path_update_patheffect(path, write);
+ SP_OBJECT(path)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
+/**
+ * Return duplicate of original_curve (if any exists) or NULL if there is no curve
+ */
+SPCurve *
+sp_path_get_original_curve (SPPath *path)
+{
+ if (path->original_curve) {
+ return sp_curve_copy (path->original_curve);
+ }
+ return NULL;
+}
+
+/**
+ * Return duplicate of edittable curve which is original_curve if it exists or
+ * shape->curve if not.
+ */
+SPCurve*
+sp_path_get_curve_for_edit (SPPath *path)
+{
+ if (path->original_curve) {
+ return sp_path_get_original_curve(path);
+ } else {
+ return sp_shape_get_curve( (SPShape *) path );
+ }
+}
/*
Local Variables:
diff --git a/src/sp-path.h b/src/sp-path.h
index d55c97829..cd413db54 100644
--- a/src/sp-path.h
+++ b/src/sp-path.h
@@ -22,6 +22,8 @@
#define SP_IS_PATH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_PATH))
struct SPPath : public SPShape {
+ SPCurve *original_curve;
+
SPConnEndPair connEndPair;
};
@@ -32,6 +34,10 @@ struct SPPathClass {
GType sp_path_get_type (void);
gint sp_nodes_in_path(SPPath *path);
+void sp_path_set_original_curve (SPPath *path, SPCurve *curve, unsigned int owner, bool write);
+SPCurve* sp_path_get_original_curve (SPPath *path);
+SPCurve* sp_path_get_curve_for_edit (SPPath *path);
+
#endif
/*
diff --git a/src/sp-shape.cpp b/src/sp-shape.cpp
index 6dba2afb6..9d08fc58d 100644
--- a/src/sp-shape.cpp
+++ b/src/sp-shape.cpp
@@ -35,6 +35,16 @@
#include "marker.h"
#include "sp-path.h"
#include "prefs-utils.h"
+#include "attributes.h"
+
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/lpeobject-reference.h"
+#include "uri.h"
+#include "extract-uri.h"
+#include "uri-references.h"
+#include "bad-uri-exception.h"
+#include "xml/repr.h"
#define noSHAPE_VERBOSE
@@ -45,8 +55,10 @@ static void sp_shape_finalize (GObject *object);
static void sp_shape_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
static void sp_shape_release (SPObject *object);
+static void sp_shape_set(SPObject *object, unsigned key, gchar const *value);
static void sp_shape_update (SPObject *object, SPCtx *ctx, unsigned int flags);
static void sp_shape_modified (SPObject *object, unsigned int flags);
+static Inkscape::XML::Node *sp_shape_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
static void sp_shape_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
void sp_shape_print (SPItem * item, SPPrintContext * ctx);
@@ -56,6 +68,9 @@ static void sp_shape_snappoints (SPItem const *item, SnapPointsIter p);
static void sp_shape_update_marker_view (SPShape *shape, NRArenaItem *ai);
+static void lpeobject_ref_changed(SPObject *old_ref, SPObject *ref, SPShape *shape);
+static void lpeobject_ref_modified(SPObject *href, guint flags, SPShape *shape);
+
static SPItemClass *parent_class;
/**
@@ -104,14 +119,19 @@ sp_shape_class_init (SPShapeClass *klass)
sp_object_class->build = sp_shape_build;
sp_object_class->release = sp_shape_release;
+ sp_object_class->set = sp_shape_set;
sp_object_class->update = sp_shape_update;
sp_object_class->modified = sp_shape_modified;
+ sp_object_class->write = sp_shape_write;
item_class->bbox = sp_shape_bbox;
item_class->print = sp_shape_print;
item_class->show = sp_shape_show;
item_class->hide = sp_shape_hide;
item_class->snappoints = sp_shape_snappoints;
+
+ klass->set_shape = NULL;
+ klass->update_patheffect = NULL;
}
/**
@@ -120,6 +140,10 @@ sp_shape_class_init (SPShapeClass *klass)
static void
sp_shape_init (SPShape *shape)
{
+ shape->path_effect_href = NULL;
+ shape->path_effect_ref = new Inkscape::LivePathEffect::LPEObjectReference(SP_OBJECT(shape));
+ shape->path_effect_ref->changedSignal().connect(sigc::bind(sigc::ptr_fun(lpeobject_ref_changed), shape));
+
for ( int i = 0 ; i < SP_MARKER_LOC_QTY ; i++ ) {
new (&shape->release_connect[i]) sigc::connection();
new (&shape->modified_connect[i]) sigc::connection();
@@ -146,17 +170,18 @@ sp_shape_finalize (GObject *object)
/**
* Virtual build callback for SPMarker.
*
- * This is to be invoked immediately after creation of an SPShape. This is
- * just a stub.
+ * This is to be invoked immediately after creation of an SPShape.
*
* \see sp_object_build()
*/
static void
sp_shape_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
{
- if (((SPObjectClass *) (parent_class))->build) {
- (*((SPObjectClass *) (parent_class))->build) (object, document, repr);
- }
+ sp_object_read_attr(object, "inkscape:path-effect");
+
+ if (((SPObjectClass *) (parent_class))->build) {
+ (*((SPObjectClass *) (parent_class))->build) (object, document, repr);
+ }
}
/**
@@ -193,11 +218,78 @@ sp_shape_release (SPObject *object)
shape->curve = sp_curve_unref (shape->curve);
}
+ if (shape->path_effect_href) {
+ g_free(shape->path_effect_href);
+ }
+ shape->path_effect_ref->detach();
+
if (((SPObjectClass *) parent_class)->release) {
((SPObjectClass *) parent_class)->release (object);
}
}
+
+
+static void
+sp_shape_set(SPObject *object, unsigned int key, gchar const *value)
+{
+ SPShape *shape = (SPShape *) object;
+ bool path_effect_changed = false;
+
+ switch (key) {
+ case SP_ATTR_INKSCAPE_PATH_EFFECT:
+ if ( value && shape->path_effect_href && ( strcmp(value, shape->path_effect_href) == 0 ) ) {
+ /* No change, do nothing. */
+ } else {
+ if (shape->path_effect_href) {
+ g_free(shape->path_effect_href);
+ shape->path_effect_href = NULL;
+ }
+ if (value) {
+ shape->path_effect_href = g_strdup(value);
+
+ // Now do the attaching, which emits the changed signal.
+ try {
+ shape->path_effect_ref->attach(Inkscape::URI(value));
+ } catch (Inkscape::BadURIException &e) {
+ g_warning("%s", e.what());
+ shape->path_effect_ref->detach();
+ }
+ } else {
+ shape->path_effect_ref->detach();
+ }
+ }
+ path_effect_changed = true; // updated twice now when connected to changed signal??
+ break;
+ default:
+ if (((SPObjectClass *) parent_class)->set) {
+ ((SPObjectClass *) parent_class)->set(object, key, value);
+ }
+ break;
+ }
+
+ if (path_effect_changed)
+ sp_shape_update_patheffect ((SPShape *) object, false);
+}
+
+static Inkscape::XML::Node *
+sp_shape_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
+{
+ SPShape *shape = (SPShape *) object;
+
+ if ( shape->path_effect_href ) {
+ repr->setAttribute("inkscape:path-effect", shape->path_effect_href);
+ } else {
+ repr->setAttribute("inkscape:path-effect", NULL);
+ }
+
+ if (((SPObjectClass *)(parent_class))->write) {
+ ((SPObjectClass *)(parent_class))->write(object, repr, flags);
+ }
+
+ return repr;
+}
+
/**
* Updates the shape when its attributes have changed. Also establishes
* marker objects to match the style settings.
@@ -1033,6 +1125,83 @@ static void sp_shape_snappoints(SPItem const *item, SnapPointsIter p)
}
+LivePathEffectObject *
+sp_shape_get_livepatheffectobject(SPShape *shape) {
+ if (!shape) return NULL;
+
+ return shape->path_effect_ref->lpeobject;
+}
+
+/**
+ * Calls any registered handlers for the update_patheffect action
+ */
+void
+sp_shape_update_patheffect (SPShape *shape, bool write)
+{
+ g_return_if_fail (shape != NULL);
+ g_return_if_fail (SP_IS_SHAPE (shape));
+
+ if (SP_SHAPE_CLASS (G_OBJECT_GET_CLASS (shape))->update_patheffect) {
+ SP_SHAPE_CLASS (G_OBJECT_GET_CLASS (shape))->update_patheffect (shape, write);
+ }
+}
+
+void sp_shape_perform_path_effect(SPCurve *curve, SPShape *shape) {
+ if (!shape) return;
+ if (!curve) return;
+
+ LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(shape);
+ if (lpeobj && lpeobj->lpe) {
+ lpeobj->lpe->doEffect(curve);
+ }
+}
+
+/**
+ * Gets called when (re)attached to another lpeobject.
+ */
+static void
+lpeobject_ref_changed(SPObject *old_ref, SPObject *ref, SPShape *shape)
+{
+ if (old_ref) {
+ sp_signal_disconnect_by_data(old_ref, shape);
+ }
+ if ( IS_LIVEPATHEFFECT(ref) && ref != shape )
+ {
+ ref->connectModified(sigc::bind(sigc::ptr_fun(&lpeobject_ref_modified), shape));
+ }
+
+ lpeobject_ref_modified(ref, 0, shape);
+}
+
+/**
+ * Gets called when lpeobject repr contents change: i.e. parameter change.
+ */
+static void
+lpeobject_ref_modified(SPObject *href, guint flags, SPShape *shape)
+{
+ sp_shape_update_patheffect (shape, true);
+}
+
+void sp_shape_set_path_effect(SPShape *shape, gchar *value)
+{
+ if (!value) {
+ sp_shape_remove_path_effect(shape);
+ } else {
+ SP_OBJECT_REPR(shape)->setAttribute("inkscape:path-effect", value);
+ }
+}
+
+void sp_shape_remove_path_effect(SPShape *shape)
+{
+ Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
+ repr->setAttribute("inkscape:path-effect", NULL);
+ if (SP_IS_PATH(shape)) {
+ repr->setAttribute("d", repr->attribute("inkscape:original-d"));
+ repr->setAttribute("inkscape:original-d", NULL);
+ }
+}
+
+
/*
Local Variables:
mode:c++
diff --git a/src/sp-shape.h b/src/sp-shape.h
index 2b265fdaa..e8ec6d040 100644
--- a/src/sp-shape.h
+++ b/src/sp-shape.h
@@ -27,12 +27,23 @@
#define SP_SHAPE_WRITE_PATH (1 << 2)
+struct LivePathEffectObject;
+namespace Inkscape{
+namespace LivePathEffect{
+ class LPEObjectReference;
+};
+};
+
+
struct SPShape : public SPItem {
- SPCurve *curve;
+ SPCurve *curve;
SPObject *marker[SP_MARKER_LOC_QTY];
sigc::connection release_connect [SP_MARKER_LOC_QTY];
sigc::connection modified_connect [SP_MARKER_LOC_QTY];
+
+ gchar *path_effect_href;
+ Inkscape::LivePathEffect::LPEObjectReference *path_effect_ref;
};
struct SPShapeClass {
@@ -40,6 +51,8 @@ struct SPShapeClass {
/* Build bpath from extra shape attributes */
void (* set_shape) (SPShape *shape);
+
+ void (* update_patheffect) (SPShape *shape, bool write);
};
GType sp_shape_get_type (void);
@@ -62,4 +75,11 @@ int sp_shape_number_of_markers (SPShape* Shape, int type);
NR::Matrix sp_shape_marker_get_transform(SPShape const *shape, NArtBpath const *bp);
bool sp_shape_marker_required(SPShape const *shape, int const m, NArtBpath *bp);
+LivePathEffectObject * sp_shape_get_livepatheffectobject(SPShape *shape);
+void sp_shape_update_patheffect (SPShape *shape, bool write);
+void sp_shape_perform_path_effect(SPCurve *curve, SPShape *shape);
+
+void sp_shape_set_path_effect(SPShape *shape, gchar *value);
+void sp_shape_remove_path_effect(SPShape *shape);
+
#endif
diff --git a/src/sp-spiral.cpp b/src/sp-spiral.cpp
index a2449fb9a..3e8ce4997 100644
--- a/src/sp-spiral.cpp
+++ b/src/sp-spiral.cpp
@@ -37,7 +37,9 @@ static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags);
static gchar * sp_spiral_description (SPItem * item);
static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p);
+
static void sp_spiral_set_shape (SPShape *shape);
+static void sp_spiral_update_patheffect (SPShape *shape, bool write);
static NR::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t);
@@ -95,7 +97,8 @@ sp_spiral_class_init (SPSpiralClass *klass)
item_class->description = sp_spiral_description;
item_class->snappoints = sp_spiral_snappoints;
- shape_class->set_shape = sp_spiral_set_shape;
+ shape_class->set_shape = sp_spiral_set_shape;
+ shape_class->update_patheffect = sp_spiral_update_patheffect;
}
/**
@@ -293,6 +296,30 @@ sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
((SPObjectClass *) parent_class)->update (object, ctx, flags);
}
+static void
+sp_spiral_update_patheffect(SPShape *shape, bool write)
+{
+ sp_spiral_set_shape(shape);
+
+ if (write) {
+ Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
+ if ( shape->curve != NULL ) {
+ NArtBpath *abp = sp_curve_first_bpath(shape->curve);
+ if (abp) {
+ gchar *str = sp_svg_write_path(abp);
+ repr->setAttribute("d", str);
+ g_free(str);
+ } else {
+ repr->setAttribute("d", "");
+ }
+ } else {
+ repr->setAttribute("d", NULL);
+ }
+ }
+
+ ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
/**
* Return textual description of spiral.
*/
@@ -436,8 +463,9 @@ sp_spiral_set_shape (SPShape *shape)
sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0),
darray, hat1, hat2, &t);
- sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE);
- sp_curve_unref (c);
+ sp_shape_perform_path_effect(c, SP_SHAPE (spiral));
+ sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE);
+ sp_curve_unref (c);
}
/**
diff --git a/src/sp-star.cpp b/src/sp-star.cpp
index ea2525054..767175e87 100644
--- a/src/sp-star.cpp
+++ b/src/sp-star.cpp
@@ -38,6 +38,7 @@ static gchar * sp_star_description (SPItem * item);
static void sp_star_snappoints(SPItem const *item, SnapPointsIter p);
static void sp_star_set_shape (SPShape *shape);
+static void sp_star_update_patheffect (SPShape *shape, bool write);
static SPShapeClass *parent_class;
@@ -88,6 +89,7 @@ sp_star_class_init (SPStarClass *klass)
item_class->snappoints = sp_star_snappoints;
shape_class->set_shape = sp_star_set_shape;
+ shape_class->update_patheffect = sp_star_update_patheffect;
}
static void
@@ -269,6 +271,30 @@ sp_star_update (SPObject *object, SPCtx *ctx, guint flags)
((SPObjectClass *) parent_class)->update (object, ctx, flags);
}
+static void
+sp_star_update_patheffect(SPShape *shape, bool write)
+{
+ sp_star_set_shape(shape);
+
+ if (write) {
+ Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
+ if ( shape->curve != NULL ) {
+ NArtBpath *abp = sp_curve_first_bpath(shape->curve);
+ if (abp) {
+ gchar *str = sp_svg_write_path(abp);
+ repr->setAttribute("d", str);
+ g_free(str);
+ } else {
+ repr->setAttribute("d", "");
+ }
+ } else {
+ repr->setAttribute("d", NULL);
+ }
+ }
+
+ ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+}
+
static gchar *
sp_star_description (SPItem *item)
{
@@ -470,9 +496,10 @@ sp_star_set_shape (SPShape *shape)
}
}
- sp_curve_closepath (c);
- sp_shape_set_curve_insync (SP_SHAPE (star), c, TRUE);
- sp_curve_unref (c);
+ sp_curve_closepath (c);
+ sp_shape_perform_path_effect(c, SP_SHAPE (star));
+ sp_shape_set_curve_insync (SP_SHAPE (star), c, TRUE);
+ sp_curve_unref (c);
}
void
diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert
index 60be61678..11011d0c5 100644
--- a/src/ui/dialog/Makefile_insert
+++ b/src/ui/dialog/Makefile_insert
@@ -34,6 +34,8 @@ ui_dialog_libuidialog_a_SOURCES = \
ui/dialog/inkscape-preferences.h \
ui/dialog/layer-editor.cpp \
ui/dialog/layer-editor.h \
+ ui/dialog/livepatheffect-editor.cpp \
+ ui/dialog/livepatheffect-editor.h \
ui/dialog/memory.cpp \
ui/dialog/memory.h \
ui/dialog/messages.cpp \
diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp
index 6b0277610..23c64f1d5 100644
--- a/src/ui/dialog/dialog-manager.cpp
+++ b/src/ui/dialog/dialog-manager.cpp
@@ -27,6 +27,7 @@
#include "ui/dialog/find.h"
#include "ui/dialog/inkscape-preferences.h"
#include "ui/dialog/layer-editor.h"
+#include "ui/dialog/livepatheffect-editor.h"
#include "ui/dialog/memory.h"
#include "ui/dialog/messages.h"
#include "ui/dialog/scriptdialog.h"
@@ -80,6 +81,7 @@ DialogManager::DialogManager() {
registerFactory("Find", &create<Find>);
registerFactory("InkscapePreferences", &create<InkscapePreferences>);
registerFactory("LayerEditor", &create<LayerEditor>);
+ registerFactory("LivePathEffect", &create<LivePathEffectEditor>);
registerFactory("Memory", &create<Memory>);
registerFactory("Messages", &create<Messages>);
registerFactory("Script", &create<ScriptDialog>);
diff --git a/src/ui/dialog/livepatheffect-editor.cpp b/src/ui/dialog/livepatheffect-editor.cpp
new file mode 100644
index 000000000..5f765b420
--- /dev/null
+++ b/src/ui/dialog/livepatheffect-editor.cpp
@@ -0,0 +1,290 @@
+/**
+ * \brief LivePathEffect dialog
+ *
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glibmm/i18n.h>
+#include "livepatheffect-editor.h"
+#include "verbs.h"
+#include "selection.h"
+#include "sp-shape.h"
+#include "sp-path.h"
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "gtkmm/widget.h"
+#include <vector>
+#include "inkscape.h"
+#include "desktop-handles.h"
+#include "desktop.h"
+#include "document-private.h"
+#include "xml/node.h"
+#include "xml/document.h"
+
+namespace Inkscape {
+class Application;
+
+namespace UI {
+namespace Dialog {
+
+
+/*####################
+ * Callback functions
+ */
+static void lpeeditor_selection_changed (Inkscape::Selection * selection, gpointer data)
+{
+ LivePathEffectEditor *lpeeditor = static_cast<LivePathEffectEditor *>(data);
+ lpeeditor->onSelectionChanged(selection);
+}
+
+static void lpeeditor_selection_modified (Inkscape::Selection *selection, guint flags, gpointer data)
+{
+ lpeeditor_selection_changed (selection, data);
+}
+
+
+static void lpeeditor_desktop_change(Inkscape::Application*, SPDesktop* desktop, void *data)
+{
+ if (!desktop) {
+ return;
+ }
+ LivePathEffectEditor* editor = reinterpret_cast<LivePathEffectEditor*>(data);
+ editor->setDesktop(desktop);
+}
+
+
+
+/*#######################
+ * LivePathEffectEditor
+ */
+LivePathEffectEditor::LivePathEffectEditor()
+ : Dialog ("dialogs.livepatheffect", SP_VERB_DIALOG_LIVE_PATH_EFFECT),
+ combo_effecttype(Inkscape::LivePathEffect::LPETypeConverter),
+ button_apply(_("_Apply"), _("Apply chosen effect to selection")),
+ button_remove(_("_Remove"), _("Remove effect from selection")),
+ effectwidget(NULL),
+ explain_label("", Gtk::ALIGN_CENTER),
+ effectapplication_frame(_("Apply new effect")),
+ effectcontrol_frame(_("Current effect")),
+ current_desktop(NULL)
+{
+ // Top level vbox
+ Gtk::VBox *vbox = get_vbox();
+ vbox->set_spacing(4);
+
+ effectapplication_vbox.set_spacing(4);
+ effectcontrol_vbox.set_spacing(4);
+
+ effectapplication_vbox.pack_start(combo_effecttype, true, true);
+ effectapplication_vbox.pack_start(button_apply, true, true);
+ effectapplication_vbox.pack_start(button_remove, true, true);
+ effectapplication_frame.add(effectapplication_vbox);
+
+ effectcontrol_vbox.pack_start(explain_label, true, true);
+ effectcontrol_frame.add(effectcontrol_vbox);
+
+ vbox->pack_start(effectapplication_frame, true, true);
+ vbox->pack_start(effectcontrol_frame, true, true);
+
+ // connect callback functions to buttons
+ button_apply.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onApply));
+ button_remove.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onRemove));
+
+ // connect callback functions to changes in selected desktop.
+ g_signal_connect( G_OBJECT(INKSCAPE), "activate_desktop",
+ G_CALLBACK(lpeeditor_desktop_change), this);
+
+ g_signal_connect( G_OBJECT(INKSCAPE), "deactivate_desktop",
+ G_CALLBACK(lpeeditor_desktop_change), this);
+
+ setDesktop(SP_ACTIVE_DESKTOP);
+ show_all_children();
+}
+
+LivePathEffectEditor::~LivePathEffectEditor()
+{
+ if (effectwidget) {
+ effectcontrol_vbox.remove(*effectwidget);
+ effectwidget = NULL;
+ }
+
+ if (current_desktop) {
+ selection_changed_connection.disconnect();
+ selection_modified_connection.disconnect();
+ }
+}
+
+void
+LivePathEffectEditor::showParams(LivePathEffect::Effect* effect)
+{
+ if (effectwidget) {
+ effectcontrol_vbox.remove(*effectwidget);
+ effectwidget = NULL;
+ }
+
+ explain_label.set_markup("<b>" + effect->getName() + "</b>");
+ effectwidget = effect->getWidget();
+ if (effectwidget) {
+ effectcontrol_vbox.pack_start(*effectwidget, true, true);
+ }
+
+ effectcontrol_vbox.show_all_children();
+ // fixme: do resizing of dialog
+}
+
+void
+LivePathEffectEditor::showText(Glib::ustring const &str)
+{
+ if (effectwidget) {
+ effectcontrol_vbox.remove(*effectwidget);
+ effectwidget = NULL;
+ }
+
+ explain_label.set_label(str);
+
+ // fixme: do resizing of dialog ?
+}
+
+void
+LivePathEffectEditor::set_sensitize_all(bool sensitive)
+{
+ combo_effecttype.set_sensitive(sensitive);
+ button_apply.set_sensitive(sensitive);
+ button_remove.set_sensitive(sensitive);
+}
+
+void
+LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel)
+{
+ if ( sel && !sel->isEmpty() ) {
+ SPItem *item = sel->singleItem();
+ if ( item ) {
+ if ( SP_IS_SHAPE(item) ) {
+ SPShape *shape = SP_SHAPE(item);
+ LivePathEffectObject *lpeobj = sp_shape_get_livepatheffectobject(shape);
+ set_sensitize_all(true);
+ if (lpeobj) {
+ if (lpeobj->lpe) {
+ showParams(lpeobj->lpe);
+ } else {
+ showText(_("Unknown effect is applied"));
+ }
+ } else {
+ showText(_("No effect applied"));
+ button_remove.set_sensitive(false);
+ }
+ } else {
+ showText(_("Item is not a shape"));
+ set_sensitize_all(false);
+ }
+ } else {
+ showText(_("Only one item can be selected"));
+ set_sensitize_all(false);
+ }
+ } else {
+ showText(_("Empty selection"));
+ set_sensitize_all(false);
+ }
+}
+
+void
+LivePathEffectEditor::setDesktop(SPDesktop *desktop)
+{
+
+ if ( desktop == current_desktop ) {
+ return;
+ }
+
+ if (current_desktop) {
+ selection_changed_connection.disconnect();
+ selection_modified_connection.disconnect();
+ }
+
+ current_desktop = desktop;
+ if (desktop) {
+ Inkscape::Selection *selection = sp_desktop_selection(desktop);
+ selection_changed_connection = selection->connectChanged(
+ sigc::bind (sigc::ptr_fun(&lpeeditor_selection_changed), this ) );
+ selection_modified_connection = selection->connectModified(
+ sigc::bind (sigc::ptr_fun(&lpeeditor_selection_modified), this ) );
+
+ onSelectionChanged(selection);
+ } else {
+ onSelectionChanged(NULL);
+ }
+}
+
+
+
+
+/*########################################################################
+# BUTTON CLICK HANDLERS (callbacks)
+########################################################################*/
+
+void
+LivePathEffectEditor::onApply()
+{
+ Inkscape::Selection *sel = _getSelection();
+ if ( sel && !sel->isEmpty() ) {
+ SPItem *item = sel->singleItem();
+ if ( item && SP_IS_SHAPE(item) ) {
+ SPDocument *doc = current_desktop->doc();
+
+ const Util::EnumData<LivePathEffect::EffectType>* data = combo_effecttype.get_active_data();
+ if (!data) return;
+
+ Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
+ Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect");
+ repr->setAttribute("effect", data->key.c_str() );
+
+ SP_OBJECT_REPR(SP_DOCUMENT_DEFS(doc))->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute
+ const gchar * repr_id = repr->attribute("id");
+ Inkscape::GC::release(repr);
+
+ gchar *href = g_strdup_printf("#%s", repr_id);
+ sp_shape_set_path_effect(SP_SHAPE(item), href);
+ g_free(href);
+
+ // make sure there is an original-d for paths!!!
+ if ( SP_IS_PATH(item) ) {
+ Inkscape::XML::Node *pathrepr = SP_OBJECT_REPR(item);
+ if ( ! pathrepr->attribute("inkscape:original-d") ) {
+ pathrepr->setAttribute("inkscape:original-d", pathrepr->attribute("d"));
+ }
+ }
+
+ sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
+ _("Create and apply live effect"));
+ }
+ }
+}
+
+void
+LivePathEffectEditor::onRemove()
+{
+ Inkscape::Selection *sel = _getSelection();
+ if ( sel && !sel->isEmpty() ) {
+ SPItem *item = sel->singleItem();
+ if ( item && SP_IS_SHAPE(item) ) {
+ sp_shape_remove_path_effect(SP_SHAPE(item));
+ sp_document_done ( sp_desktop_document (current_desktop), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
+ _("Remove live path effect") );
+ }
+ }
+}
+
+
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
diff --git a/src/ui/dialog/livepatheffect-editor.h b/src/ui/dialog/livepatheffect-editor.h
new file mode 100644
index 000000000..5476f8a1d
--- /dev/null
+++ b/src/ui/dialog/livepatheffect-editor.h
@@ -0,0 +1,85 @@
+/**
+ * \brief LivePathEffect dialog
+ *
+ * Author:
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ *
+ * Copyright (C) 2007 Author
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_DIALOG_LIVE_PATH_EFFECT_H
+#define INKSCAPE_UI_DIALOG_LIVE_PATH_EFFECT_H
+
+#include "dialog.h"
+#include "ui/widget/button.h"
+
+#include <gtkmm/label.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/frame.h>
+#include "ui/widget/combo-enums.h"
+#include "live_effects/effect.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+
+namespace UI {
+namespace Dialog {
+
+class LivePathEffectEditor : public Dialog {
+public:
+ LivePathEffectEditor();
+ virtual ~LivePathEffectEditor();
+
+ static LivePathEffectEditor *create() { return new LivePathEffectEditor(); }
+
+ void onSelectionChanged(Inkscape::Selection *sel);
+ void setDesktop(SPDesktop *desktop);
+
+private:
+ sigc::connection selection_changed_connection;
+ sigc::connection selection_modified_connection;
+
+ void set_sensitize_all(bool sensitive);
+
+ void showParams(LivePathEffect::Effect* effect);
+ void showText(Glib::ustring const &str);
+
+ // callback methods for buttons on grids page.
+ void onApply();
+ void onRemove();
+
+ Inkscape::UI::Widget::ComboBoxEnum<LivePathEffect::EffectType> combo_effecttype;
+ Inkscape::UI::Widget::Button button_apply;
+ Inkscape::UI::Widget::Button button_remove;
+ Gtk::Widget * effectwidget;
+ Gtk::Label explain_label;
+ Gtk::Frame effectapplication_frame;
+ Gtk::Frame effectcontrol_frame;
+ Gtk::VBox effectapplication_vbox;
+ Gtk::VBox effectcontrol_vbox;
+
+ SPDesktop * current_desktop;
+
+ LivePathEffectEditor(LivePathEffectEditor const &d);
+ LivePathEffectEditor& operator=(LivePathEffectEditor const &d);
+};
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_DIALOG_LIVE_PATH_EFFECT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/widget/Makefile_insert b/src/ui/widget/Makefile_insert
index 11c18738c..178e954a4 100644
--- a/src/ui/widget/Makefile_insert
+++ b/src/ui/widget/Makefile_insert
@@ -37,10 +37,13 @@ ui_widget_libuiwidget_a_SOURCES = \
ui/widget/page-sizer.h \
ui/widget/panel.cpp \
ui/widget/panel.h \
+ ui/widget/point.cpp \
+ ui/widget/point.h \
ui/widget/preferences-widget.cpp \
ui/widget/preferences-widget.h \
ui/widget/registered-widget.cpp \
ui/widget/registered-widget.h \
+ ui/widget/registered-enums.h \
ui/widget/registry.cpp \
ui/widget/registry.h \
ui/widget/ruler.cpp \
diff --git a/src/ui/widget/combo-enums.h b/src/ui/widget/combo-enums.h
index b384ca474..22aa55dab 100644
--- a/src/ui/widget/combo-enums.h
+++ b/src/ui/widget/combo-enums.h
@@ -17,6 +17,7 @@
#include <gtkmm/liststore.h>
#include "attr-widget.h"
#include "util/enums.h"
+#include "ui/widget/labelled.h"
namespace Inkscape {
namespace UI {
@@ -74,6 +75,23 @@ public:
row[_columns.data] = 0;
row[_columns.label] = s;
}
+
+ void set_active_by_id(E id) {
+ for(Gtk::TreeModel::iterator i = _model->children().begin();
+ i != _model->children().end(); ++i)
+ {
+ const Util::EnumData<E>* data = (*i)[_columns.data];
+ if(data->id == id) {
+ set_active(i);
+ break;
+ }
+ }
+ };
+
+ void set_active_by_key(const Glib::ustring& key) {
+ set_active_by_id( _converter.get_id_from_key(key) );
+ };
+
private:
class Columns : public Gtk::TreeModel::ColumnRecord
{
@@ -93,6 +111,24 @@ private:
const Util::EnumDataConverter<E>& _converter;
};
+
+template<typename E> class LabelledComboBoxEnum : public Labelled
+{
+public:
+ LabelledComboBoxEnum( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ const Util::EnumDataConverter<E>& c,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true)
+ : Labelled(label, tooltip, new ComboBoxEnum<E>(c), suffix, icon, mnemonic)
+ { }
+
+ ComboBoxEnum<E>* getCombobox() {
+ return static_cast< ComboBoxEnum<E>* > (_widget);
+ }
+};
+
}
}
}
diff --git a/src/ui/widget/point.cpp b/src/ui/widget/point.cpp
new file mode 100644
index 000000000..cfaa4303d
--- /dev/null
+++ b/src/ui/widget/point.cpp
@@ -0,0 +1,237 @@
+/**
+ * \brief Point Widget - A labelled text box, with spin buttons and optional
+ * icon or suffix, for entering arbitrary coordinate values.
+ *
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2007 Authors
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include "ui/widget/point.h"
+#include "ui/widget/labelled.h"
+#include "ui/widget/scalar.h"
+#include <gtkmm/box.h>
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+/**
+ * Construct a Point Widget.
+ *
+ * \param label Label.
+ * \param suffix Suffix, placed after the widget (defaults to "").
+ * \param icon Icon filename, placed before the label (defaults to "").
+ * \param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ setProgrammatically(false),
+ xwidget("X:",""),
+ ywidget("Y:","")
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+/**
+ * Construct a Point Widget.
+ *
+ * \param label Label.
+ * \param digits Number of decimal digits to display.
+ * \param suffix Suffix, placed after the widget (defaults to "").
+ * \param icon Icon filename, placed before the label (defaults to "").
+ * \param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to false).
+ */
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ setProgrammatically(false),
+ xwidget("X:","", digits),
+ ywidget("Y:","", digits)
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+/**
+ * Construct a Point Widget.
+ *
+ * \param label Label.
+ * \param adjust Adjustment to use for the SpinButton.
+ * \param digits Number of decimal digits to display (defaults to 0).
+ * \param suffix Suffix, placed after the widget (defaults to "").
+ * \param icon Icon filename, placed before the label (defaults to "").
+ * \param mnemonic Mnemonic toggle; if true, an underscore (_) in the label
+ * indicates the next character should be used for the
+ * mnemonic accelerator key (defaults to true).
+ */
+Point::Point(Glib::ustring const &label, Glib::ustring const &tooltip,
+ Gtk::Adjustment &adjust,
+ unsigned digits,
+ Glib::ustring const &suffix,
+ Glib::ustring const &icon,
+ bool mnemonic)
+ : Labelled(label, tooltip, new Gtk::VBox(), suffix, icon, mnemonic),
+ setProgrammatically(false),
+ xwidget("X:","", adjust, digits),
+ ywidget("Y:","", adjust, digits)
+{
+ static_cast<Gtk::VBox*>(_widget)->pack_start(xwidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->pack_start(ywidget, true, true);
+ static_cast<Gtk::VBox*>(_widget)->show_all_children();
+}
+
+/** Fetches the precision of the spin buton */
+unsigned
+Point::getDigits() const
+{
+ return xwidget.getDigits();
+}
+
+/** Gets the current step ingrement used by the spin button */
+double
+Point::getStep() const
+{
+ return xwidget.getStep();
+}
+
+/** Gets the current page increment used by the spin button */
+double
+Point::getPage() const
+{
+ return xwidget.getPage();
+}
+
+/** Gets the minimum range value allowed for the spin button */
+double
+Point::getRangeMin() const
+{
+ return xwidget.getRangeMin();
+}
+
+/** Gets the maximum range value allowed for the spin button */
+double
+Point::getRangeMax() const
+{
+ return xwidget.getRangeMax();
+}
+
+/** Get the value in the spin_button . */
+double
+Point::getXValue() const
+{
+ return xwidget.getValue();
+}
+double
+Point::getYValue() const
+{
+ return ywidget.getValue();
+}
+
+/** Get the value spin_button represented as an integer. */
+int
+Point::getXValueAsInt() const
+{
+ return xwidget.getValueAsInt();
+}
+int
+Point::getYValueAsInt() const
+{
+ return ywidget.getValueAsInt();
+}
+
+
+/** Sets the precision to be displayed by the spin button */
+void
+Point::setDigits(unsigned digits)
+{
+ xwidget.setDigits(digits);
+ ywidget.setDigits(digits);
+}
+
+/** Sets the step and page increments for the spin button */
+void
+Point::setIncrements(double step, double page)
+{
+ xwidget.setIncrements(step, page);
+ ywidget.setIncrements(step, page);
+}
+
+/** Sets the minimum and maximum range allowed for the spin button */
+void
+Point::setRange(double min, double max)
+{
+ xwidget.setRange(min, max);
+ ywidget.setRange(min, max);
+}
+
+/** Sets the value of the spin button */
+void
+Point::setValue(double xvalue, double yvalue)
+{
+ setProgrammatically = true; // callback is supposed to reset back, if it cares
+ xwidget.setValue(xvalue);
+ ywidget.setValue(yvalue);
+}
+
+/** Manually forces an update of the spin button */
+void
+Point::update() {
+ xwidget.update();
+ ywidget.update();
+}
+
+
+
+/** Signal raised when the spin button's value changes */
+Glib::SignalProxy0<void>
+Point::signal_x_value_changed()
+{
+ return xwidget.signal_value_changed();
+}
+Glib::SignalProxy0<void>
+Point::signal_y_value_changed()
+{
+ return ywidget.signal_value_changed();
+}
+
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/widget/point.h b/src/ui/widget/point.h
new file mode 100644
index 000000000..291ae131e
--- /dev/null
+++ b/src/ui/widget/point.h
@@ -0,0 +1,96 @@
+/**
+ * \brief Point Widget - A labelled text box, with spin buttons and optional
+ * icon or suffix, for entering arbitrary coordinate values.
+ *
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Carl Hetherington <inkscape@carlh.net>
+ * Derek P. Moore <derekm@hackunix.org>
+ * Bryce Harrington <bryce@bryceharrington.org>
+ *
+ * Copyright (C) 2007 Authors
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_POINT_H
+#define INKSCAPE_UI_WIDGET_POINT_H
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/spinbutton.h>
+
+#include "ui/widget/labelled.h"
+#include "ui/widget/scalar.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+class Point : public Labelled
+{
+public:
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ unsigned digits,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+ Point( Glib::ustring const &label,
+ Glib::ustring const &tooltip,
+ Gtk::Adjustment &adjust,
+ unsigned digits = 0,
+ Glib::ustring const &suffix = "",
+ Glib::ustring const &icon = "",
+ bool mnemonic = true);
+
+ unsigned getDigits() const;
+ double getStep() const;
+ double getPage() const;
+ double getRangeMin() const;
+ double getRangeMax() const;
+ bool getSnapToTicks() const;
+ double getXValue() const;
+ double getYValue() const;
+ int getXValueAsInt() const;
+ int getYValueAsInt() const;
+
+ void setDigits(unsigned digits);
+ void setIncrements(double step, double page);
+ void setRange(double min, double max);
+ void setValue(double xvalue, double yvalue);
+
+ void update();
+
+ Glib::SignalProxy0<void> signal_x_value_changed();
+ Glib::SignalProxy0<void> signal_y_value_changed();
+
+ bool setProgrammatically; // true if the value was set by setValue, not changed by the user;
+ // if a callback checks it, it must reset it back to false
+
+protected:
+ Scalar xwidget, ywidget;
+
+};
+
+} // namespace Widget
+} // namespace UI
+} // namespace Inkscape
+
+#endif // INKSCAPE_UI_WIDGET_POINT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/widget/registered-enums.h b/src/ui/widget/registered-enums.h
new file mode 100644
index 000000000..bfa866e29
--- /dev/null
+++ b/src/ui/widget/registered-enums.h
@@ -0,0 +1,110 @@
+/**
+ * \brief Simplified management of enumerations in the UI as combobox.
+ *
+ * Authors:
+ * Johan Engelen <j.b.c.engelen@ewi.utwente.nl>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL. Read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H
+#define INKSCAPE_UI_WIDGET_REGISTERED_ENUMS_H
+
+#include "ui/widget/combo-enums.h"
+#include "ui/widget/registered-widget.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+template<typename E> class RegisteredEnum : public RegisteredWidget
+{
+public:
+ RegisteredEnum() {
+ labelled = NULL;
+ }
+
+ ~RegisteredEnum() {
+ _changed_connection.disconnect();
+ if (labelled)
+ delete labelled;
+ }
+
+ void init ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ const Util::EnumDataConverter<E>& c,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in)
+ {
+ init_parent(key, wr, repr_in, doc_in);
+
+ labelled = new LabelledComboBoxEnum<E> (label, tip, c);
+
+ _changed_connection = combobox()->signal_changed().connect (sigc::mem_fun (*this, &RegisteredEnum::on_changed));
+ }
+
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& key,
+ Registry& wr)
+ {
+ init(label, key, wr, NULL, NULL);
+ }
+
+ void set_active_by_id (E id) {
+ combobox()->set_active_by_id(id);
+ };
+
+ void set_active_by_key (const Glib::ustring& key) {
+ combobox()->set_active_by_key(key);
+ }
+
+ inline const Util::EnumData<E>* get_active_data() {
+ return combobox()->get_active_data();
+ }
+
+ ComboBoxEnum<E> * combobox() {
+ if (labelled) {
+ return labelled->getCombobox();
+ } else {
+ return NULL;
+ }
+ }
+
+ LabelledComboBoxEnum<E> * labelled;
+ sigc::connection _changed_connection;
+
+protected:
+ void on_changed() {
+ if (_wr->isUpdating())
+ return;
+ _wr->setUpdating (true);
+
+ const Util::EnumData<E>* data = combobox()->get_active_data();
+ if (data) {
+ write_to_xml(data->key.c_str());
+ }
+
+ _wr->setUpdating (false);
+ }
+};
+
+}
+}
+}
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/widget/registered-widget.cpp b/src/ui/widget/registered-widget.cpp
index 6ef264b0d..8a569bc32 100644
--- a/src/ui/widget/registered-widget.cpp
+++ b/src/ui/widget/registered-widget.cpp
@@ -2,13 +2,14 @@
*
*
* Authors:
+ * Johan Engelen <j.b.c.engelen@utwente.nl>
* bulia byak <buliabyak@users.sf.net>
* Bryce W. Harrington <bryce@bryceharrington.org>
* Lauris Kaplinski <lauris@kaplinski.com>
* Jon Phillips <jon@rejon.org>
* Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
*
- * Copyright (C) 2000 - 2005 Authors
+ * Copyright (C) 2000 - 2007 Authors
*
* Released under GNU GPL. Read the file 'COPYING' for more information
*/
@@ -21,6 +22,8 @@
#include "ui/widget/color-picker.h"
#include "ui/widget/registry.h"
#include "ui/widget/scalar-unit.h"
+#include "ui/widget/point.h"
+#include "widgets/spinbutton-events.h"
#include "helper/units.h"
#include "xml/repr.h"
@@ -35,6 +38,9 @@
#include "registered-widget.h"
#include "verbs.h"
+// for interruptability bug:
+#include "display/sp-canvas.h"
+
namespace Inkscape {
namespace UI {
namespace Widget {
@@ -44,6 +50,31 @@ namespace Widget {
//---------------------------------------------------
+void
+RegisteredWidget::write_to_xml(const char * svgstr)
+{
+ // Use local repr here. When repr is specified, use that one, but
+ // if repr==NULL, get the repr of namedview of active desktop.
+ Inkscape::XML::Node *local_repr = repr;
+ SPDocument *local_doc = doc;
+ if (!local_repr) {
+ // no repr specified, use active desktop's namedview's repr
+ SPDesktop* dt = SP_ACTIVE_DESKTOP;
+ local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
+ local_doc = sp_desktop_document(dt);
+ }
+
+ bool saved = sp_document_get_undo_sensitive (local_doc);
+ sp_document_set_undo_sensitive (local_doc, false);
+ if (!write_undo) local_repr->setAttribute(_key.c_str(), svgstr);
+ local_doc->rroot->setAttribute("sodipodi:modified", "true");
+ sp_document_set_undo_sensitive (local_doc, saved);
+ if (write_undo) {
+ local_repr->setAttribute(_key.c_str(), svgstr);
+ sp_document_done (local_doc, event_type, event_description);
+ }
+}
+
//====================================================
@@ -61,20 +92,15 @@ RegisteredCheckButton::~RegisteredCheckButton()
void
RegisteredCheckButton::init (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, Registry& wr, bool right, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
+ init_parent(key, wr, repr_in, doc_in);
+
_button = new Gtk::CheckButton;
_tt.set_tip (*_button, tip);
Gtk::Label *l = new Gtk::Label (label);
l->set_use_underline (true);
_button->add (*manage (l));
_button->set_alignment (right? 1.0 : 0.0, 0.5);
- _key = key;
- _wr = &wr;
_toggled_connection = _button->signal_toggled().connect (sigc::mem_fun (*this, &RegisteredCheckButton::on_toggled));
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_error("Initialization of registered widget using defined repr but with doc==NULL");
}
void
@@ -93,33 +119,15 @@ RegisteredCheckButton::on_toggled()
if (_wr->isUpdating())
return;
- // Use local repr here. When repr is specified, use that one, but
- // if repr==NULL, get the repr of namedview of active desktop.
- Inkscape::XML::Node *local_repr = repr;
- SPDocument *local_doc = doc;
- if (!local_repr) {
- // no repr specified, use active desktop's namedview's repr
- SPDesktop *dt = SP_ACTIVE_DESKTOP;
- if (!dt)
- return;
- local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
- local_doc = sp_desktop_document(dt);
- }
-
_wr->setUpdating (true);
- //The slave button is greyed out if the master button is unchecked
- for (std::list<Gtk::ToggleButton*>::const_iterator i = _slavebuttons.begin(); i != _slavebuttons.end(); i++) {
- (*i)->set_sensitive(_button->get_active());
- }
-
- bool saved = sp_document_get_undo_sensitive (local_doc);
- sp_document_set_undo_sensitive (local_doc, false);
- sp_repr_set_boolean(local_repr, _key.c_str(), _button->get_active());
- local_doc->rroot->setAttribute("sodipodi:modified", "true");
- sp_document_set_undo_sensitive (local_doc, saved);
- sp_document_done (local_doc, SP_VERB_NONE,
- /* TODO: annotate */ "registered-widget.cpp: RegisteredCheckButton::on_toggled");
+ write_to_xml(_button->get_active() ? "true" : "false");
+ //The slave button is greyed out if the master button is unchecked
+ for (std::list<Gtk::ToggleButton*>::const_iterator i = _slavebuttons.begin(); i != _slavebuttons.end(); i++) {
+ (*i)->set_sensitive(_button->get_active());
+ }
+
+ write_to_xml(_button->get_active() ? "true" : "false");
_wr->setUpdating (false);
}
@@ -139,19 +147,14 @@ RegisteredUnitMenu::~RegisteredUnitMenu()
void
RegisteredUnitMenu::init (const Glib::ustring& label, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
+ init_parent(key, wr, repr_in, doc_in);
+
_label = new Gtk::Label (label, 1.0, 0.5);
_label->set_use_underline (true);
_sel = new UnitMenu ();
_label->set_mnemonic_widget (*_sel);
_sel->setUnitType (UNIT_TYPE_LINEAR);
- _wr = &wr;
- _key = key;
_changed_connection = _sel->signal_changed().connect (sigc::mem_fun (*this, &RegisteredUnitMenu::on_changed));
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_warning("Initialization of registered widget using defined repr but with doc==NULL");
}
void
@@ -166,31 +169,12 @@ RegisteredUnitMenu::on_changed()
if (_wr->isUpdating())
return;
- // Use local repr here. When repr is specified, use that one, but
- // if repr==NULL, get the repr of namedview of active desktop.
- Inkscape::XML::Node *local_repr = repr;
- SPDocument *local_doc = doc;
- if (!local_repr) {
- // no repr specified, use active desktop's namedview's repr
- SPDesktop *dt = SP_ACTIVE_DESKTOP;
- if (!dt)
- return;
- local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
- local_doc = sp_desktop_document(dt);
- }
-
Inkscape::SVGOStringStream os;
os << _sel->getUnitAbbr();
_wr->setUpdating (true);
- bool saved = sp_document_get_undo_sensitive (local_doc);
- sp_document_set_undo_sensitive (local_doc, false);
- local_repr->setAttribute(_key.c_str(), os.str().c_str());
- local_doc->rroot->setAttribute("sodipodi:modified", "true");
- sp_document_set_undo_sensitive (local_doc, saved);
- sp_document_done (local_doc, SP_VERB_NONE,
- /* TODO: annotate */ "registered-widget.cpp: RegisteredUnitMenu::on_changed");
+ write_to_xml(os.str().c_str());
_wr->setUpdating (false);
}
@@ -210,19 +194,14 @@ RegisteredScalarUnit::~RegisteredScalarUnit()
void
RegisteredScalarUnit::init (const Glib::ustring& label, const Glib::ustring& tip, const Glib::ustring& key, const RegisteredUnitMenu &rum, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
+ init_parent(key, wr, repr_in, doc_in);
+
_widget = new ScalarUnit (label, tip, UNIT_TYPE_LINEAR, "", "", rum._sel);
_widget->initScalar (-1e6, 1e6);
_widget->setUnit (rum._sel->getUnitAbbr());
_widget->setDigits (2);
- _key = key;
_um = rum._sel;
_value_changed_connection = _widget->signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalarUnit::on_value_changed));
- _wr = &wr;
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_warning("Initialization of registered widget using defined repr but with doc==NULL");
}
ScalarUnit*
@@ -244,37 +223,83 @@ RegisteredScalarUnit::on_value_changed()
if (_wr->isUpdating())
return;
- // Use local repr here. When repr is specified, use that one, but
- // if repr==NULL, get the repr of namedview of active desktop.
- Inkscape::XML::Node *local_repr = repr;
- SPDocument *local_doc = doc;
- if (!local_repr) {
- // no repr specified, use active desktop's namedview's repr
- SPDesktop *dt = SP_ACTIVE_DESKTOP;
- if (!dt)
- return;
- local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
- local_doc = sp_desktop_document(dt);
- }
+ _wr->setUpdating (true);
Inkscape::SVGOStringStream os;
os << _widget->getValue("");
if (_um)
os << _um->getUnitAbbr();
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+
+RegisteredScalar::RegisteredScalar()
+{
+ _widget = NULL;
+}
+
+RegisteredScalar::~RegisteredScalar()
+{
+ if (_widget)
+ delete _widget;
+
+ _value_changed_connection.disconnect();
+}
+
+void
+RegisteredScalar::init ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument * doc_in )
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ _widget = new Scalar (label, tip);
+ _widget->setRange (-1e6, 1e6);
+ _widget->setDigits (2);
+ _widget->setIncrements(0.1, 1.0);
+ _value_changed_connection = _widget->signal_value_changed().connect (sigc::mem_fun (*this, &RegisteredScalar::on_value_changed));
+}
+
+Scalar*
+RegisteredScalar::getS()
+{
+ return _widget;
+}
+
+void
+RegisteredScalar::setValue (double val)
+{
+ _widget->setValue (val);
+ on_value_changed();
+}
+
+void
+RegisteredScalar::on_value_changed()
+{
+ if (_wr->isUpdating())
+ return;
_wr->setUpdating (true);
- bool saved = sp_document_get_undo_sensitive (local_doc);
- sp_document_set_undo_sensitive (local_doc, false);
- local_repr->setAttribute(_key.c_str(), os.str().c_str());
- local_doc->rroot->setAttribute("sodipodi:modified", "true");
- sp_document_set_undo_sensitive (local_doc, saved);
- sp_document_done (local_doc, SP_VERB_NONE,
- /* TODO: annotate */ "registered-widget.cpp: RegisteredScalarUnit::on_value_changed");
+ // FIXME: gtk bug?
+ // disable interruptibility: see http://inkscape.svn.sourceforge.net/viewvc/inkscape/inkscape/trunk/src/ui/widget/selected-style.cpp?r1=13149&r2=13257&sortby=date
+ SPDesktop* dt = SP_ACTIVE_DESKTOP;
+ sp_canvas_force_full_redraw_after_interruptions(sp_desktop_canvas(dt), 0);
+
+ Inkscape::SVGOStringStream os;
+ os << _widget->getValue();
+
+ write_to_xml(os.str().c_str());
+
+ // resume interruptibility
+ sp_canvas_end_forced_full_redraws(sp_desktop_canvas(dt));
_wr->setUpdating (false);
}
+
RegisteredColorPicker::RegisteredColorPicker()
: _label(0), _cp(0)
{
@@ -290,19 +315,15 @@ RegisteredColorPicker::~RegisteredColorPicker()
void
RegisteredColorPicker::init (const Glib::ustring& label, const Glib::ustring& title, const Glib::ustring& tip, const Glib::ustring& ckey, const Glib::ustring& akey, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
+ init_parent("", wr, repr_in, doc_in);
+
_label = new Gtk::Label (label, 1.0, 0.5);
_label->set_use_underline (true);
_cp = new ColorPicker (title,tip,0,true);
_label->set_mnemonic_widget (*_cp);
_ckey = ckey;
_akey = akey;
- _wr = &wr;
_changed_connection = _cp->connectChanged (sigc::mem_fun (*this, &RegisteredColorPicker::on_changed));
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_warning("Initialization of registered widget using defined repr but with doc==NULL");
}
void
@@ -370,7 +391,8 @@ RegisteredSuffixedInteger::~RegisteredSuffixedInteger()
void
RegisteredSuffixedInteger::init (const Glib::ustring& label, const Glib::ustring& suffix, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
- _key = key;
+ init_parent(key, wr, repr_in, doc_in);
+
_label = new Gtk::Label (label);
_label->set_alignment (1.0, 0.5);
_label->set_use_underline();
@@ -381,12 +403,6 @@ RegisteredSuffixedInteger::init (const Glib::ustring& label, const Glib::ustring
_hbox.pack_start (*_suffix, false, false, 0);
_changed_connection = _adj.signal_value_changed().connect (sigc::mem_fun(*this, &RegisteredSuffixedInteger::on_value_changed));
- _wr = &wr;
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_warning("Initialization of registered widget using defined repr but with doc==NULL");
}
void
@@ -403,26 +419,11 @@ RegisteredSuffixedInteger::on_value_changed()
_wr->setUpdating (true);
- // Use local repr here. When repr is specified, use that one, but
- // if repr==NULL, get the repr of namedview of active desktop.
- Inkscape::XML::Node *local_repr = repr;
- SPDocument *local_doc = doc;
- if (!local_repr) {
- // no repr specified, use active desktop's namedview's repr
- SPDesktop *dt = SP_ACTIVE_DESKTOP;
- if (!dt)
- return;
- local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
- local_doc = sp_desktop_document(dt);
- }
-
Inkscape::SVGOStringStream os;
int value = int(_adj.get_value());
os << value;
- local_repr->setAttribute(_key.c_str(), os.str().c_str());
- sp_document_done(local_doc, SP_VERB_NONE,
- /* TODO: annotate */ "registered-widget.cpp: RegisteredSuffixedInteger::on_value_changed");
+ write_to_xml(os.str().c_str());
_wr->setUpdating (false);
}
@@ -443,6 +444,8 @@ const Glib::ustring& label1, const Glib::ustring& label2,
const Glib::ustring& tip1, const Glib::ustring& tip2,
const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
{
+ init_parent(key, wr, repr_in, doc_in);
+
_hbox = new Gtk::HBox;
_hbox->add (*manage (new Gtk::Label (label)));
_rb1 = manage (new Gtk::RadioButton (label1, true));
@@ -453,14 +456,7 @@ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument
_rb2->set_active();
_tt.set_tip (*_rb1, tip1);
_tt.set_tip (*_rb2, tip2);
- _key = key;
- _wr = &wr;
_changed_connection = _rb1->signal_toggled().connect (sigc::mem_fun (*this, &RegisteredRadioButtonPair::on_value_changed));
-
- repr = repr_in;
- doc = doc_in;
- if (repr && !doc) // doc cannot be NULL when repr is not NULL
- g_error("Initialization of registered widget using defined repr but with doc==NULL");
}
void
@@ -476,29 +472,72 @@ RegisteredRadioButtonPair::on_value_changed()
if (_wr->isUpdating())
return;
- // Use local repr here. When repr is specified, use that one, but
- // if repr==NULL, get the repr of namedview of active desktop.
- Inkscape::XML::Node *local_repr = repr;
- SPDocument *local_doc = doc;
- if (!local_repr) {
- // no repr specified, use active desktop's namedview's repr
- SPDesktop *dt = SP_ACTIVE_DESKTOP;
- if (!dt)
- return;
- local_repr = SP_OBJECT_REPR (sp_desktop_namedview(dt));
- local_doc = sp_desktop_document(dt);
- }
-
_wr->setUpdating (true);
bool second = _rb2->get_active();
- bool saved = sp_document_get_undo_sensitive (local_doc);
- sp_document_set_undo_sensitive (local_doc, false);
- local_repr->setAttribute(_key.c_str(), second ? "true" : "false");
- local_doc->rroot->setAttribute("sodipodi:modified", "true");
- sp_document_set_undo_sensitive (local_doc, saved);
- sp_document_done (local_doc, SP_VERB_NONE,
- /* TODO: annotate */ "registered-widget.cpp: RegisteredRadioButtonPair::on_value_changed");
+ write_to_xml(second ? "true" : "false");
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
+ * Registered POINT
+ */
+
+RegisteredPoint::RegisteredPoint()
+{
+ _widget = NULL;
+}
+
+RegisteredPoint::~RegisteredPoint()
+{
+ if (_widget)
+ delete _widget;
+
+ _value_x_changed_connection.disconnect();
+ _value_y_changed_connection.disconnect();
+}
+
+void
+RegisteredPoint::init ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ _widget = new Point (label, tip);
+ _widget->setRange (-1e6, 1e6);
+ _widget->setDigits (2);
+ _widget->setIncrements(0.1, 1.0);
+ _value_x_changed_connection = _widget->signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed));
+ _value_y_changed_connection = _widget->signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredPoint::on_value_changed));
+}
+
+Point*
+RegisteredPoint::getPoint()
+{
+ return _widget;
+}
+
+void
+RegisteredPoint::setValue (double xval, double yval)
+{
+ _widget->setValue(xval, yval);
+ on_value_changed();
+}
+
+void
+RegisteredPoint::on_value_changed()
+{
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Inkscape::SVGOStringStream os;
+ os << _widget->getXValue() << "," << _widget->getYValue();
+
+ write_to_xml(os.str().c_str());
_wr->setUpdating (false);
}
diff --git a/src/ui/widget/registered-widget.h b/src/ui/widget/registered-widget.h
index 8e54e1bbd..008ca0077 100644
--- a/src/ui/widget/registered-widget.h
+++ b/src/ui/widget/registered-widget.h
@@ -14,6 +14,9 @@
#include <gtkmm/box.h>
#include <gtkmm/adjustment.h>
+#include <gtkmm/tooltips.h>
+
+#include "xml/node.h"
class SPUnit;
class SPDocument;
@@ -31,10 +34,53 @@ namespace Widget {
class ColorPicker;
class Registry;
+class Scalar;
class ScalarUnit;
class UnitMenu;
+class Point;
+
+class RegisteredWidget {
+public:
+ void set_undo_parameters(const unsigned int _event_type, Glib::ustring _event_description)
+ {
+ event_type = _event_type;
+ event_description = _event_description;
+ write_undo = true;
+ }
+
+protected:
+ RegisteredWidget()
+ {
+ _wr = NULL;
+ repr = NULL;
+ doc = NULL;
+ write_undo = false;
+ }
+
+ void init_parent(const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in, SPDocument *doc_in)
+ {
+ _wr = &wr;
+ _key = key;
+ repr = repr_in;
+ doc = doc_in;
+ if (repr && !doc) // doc cannot be NULL when repr is not NULL
+ g_warning("Initialization of registered widget using defined repr but with doc==NULL");
+ }
+
+ void write_to_xml(const char * svgstr);
-class RegisteredCheckButton {
+ Registry * _wr;
+ Glib::ustring _key;
+ Inkscape::XML::Node * repr;
+ SPDocument * doc;
+ unsigned int event_type;
+ Glib::ustring event_description;
+ bool write_undo;
+};
+
+//#######################################################
+
+class RegisteredCheckButton : public RegisteredWidget {
public:
RegisteredCheckButton();
~RegisteredCheckButton();
@@ -55,18 +101,23 @@ public:
protected:
Gtk::Tooltips _tt;
sigc::connection _toggled_connection;
- Registry *_wr;
- Glib::ustring _key;
void on_toggled();
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
-class RegisteredUnitMenu {
+class RegisteredUnitMenu : public RegisteredWidget {
public:
RegisteredUnitMenu();
~RegisteredUnitMenu();
- void init (const Glib::ustring& label, const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in=NULL, SPDocument *doc_in=NULL);
+ void init ( const Glib::ustring& label,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& key,
+ Registry& wr)
+ { init(label, key, wr, NULL, NULL); };
+
void setUnit (const SPUnit*);
Gtk::Label *_label;
UnitMenu *_sel;
@@ -74,13 +125,9 @@ public:
protected:
void on_changed();
- Registry *_wr;
- Glib::ustring _key;
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
-class RegisteredScalarUnit {
+class RegisteredScalarUnit : public RegisteredWidget {
public:
RegisteredScalarUnit();
~RegisteredScalarUnit();
@@ -89,8 +136,15 @@ public:
const Glib::ustring& key,
const RegisteredUnitMenu &rum,
Registry& wr,
- Inkscape::XML::Node* repr_in=NULL,
- SPDocument *doc_in=NULL);
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ const RegisteredUnitMenu &rum,
+ Registry& wr)
+ { init(label, tip, key, rum, wr, NULL, NULL); };
+
ScalarUnit* getSU();
void setValue (double);
@@ -98,14 +152,35 @@ protected:
ScalarUnit *_widget;
sigc::connection _value_changed_connection;
UnitMenu *_um;
- Registry *_wr;
- Glib::ustring _key;
void on_value_changed();
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
-class RegisteredColorPicker {
+class RegisteredScalar : public RegisteredWidget {
+public:
+ RegisteredScalar();
+ ~RegisteredScalar();
+ void init (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr)
+ { init(label, tip, key, wr, NULL, NULL); };
+
+ Scalar* getS();
+ void setValue (double);
+
+protected:
+ Scalar *_widget;
+ sigc::connection _value_changed_connection;
+ void on_value_changed();
+};
+
+class RegisteredColorPicker : public RegisteredWidget {
public:
RegisteredColorPicker();
~RegisteredColorPicker();
@@ -115,8 +190,16 @@ public:
const Glib::ustring& ckey,
const Glib::ustring& akey,
Registry& wr,
- Inkscape::XML::Node* repr_in=NULL,
- SPDocument *doc_in=NULL);
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& title,
+ const Glib::ustring& tip,
+ const Glib::ustring& ckey,
+ const Glib::ustring& akey,
+ Registry& wr)
+ { init(label, title, tip, ckey, akey, wr, NULL, NULL); };
+
void setRgba32 (guint32);
void closeWindow();
@@ -125,14 +208,11 @@ public:
protected:
Glib::ustring _ckey, _akey;
- Registry *_wr;
void on_changed (guint32);
sigc::connection _changed_connection;
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
-class RegisteredSuffixedInteger {
+class RegisteredSuffixedInteger : public RegisteredWidget {
public:
RegisteredSuffixedInteger();
~RegisteredSuffixedInteger();
@@ -140,8 +220,14 @@ public:
const Glib::ustring& label2,
const Glib::ustring& key,
Registry& wr,
- Inkscape::XML::Node* repr_in=NULL,
- SPDocument *doc_in=NULL);
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label1,
+ const Glib::ustring& label2,
+ const Glib::ustring& key,
+ Registry& wr)
+ { init(label1, label2, key, wr, NULL, NULL); };
+
void setValue (int);
Gtk::Label *_label;
Gtk::HBox _hbox;
@@ -150,15 +236,11 @@ protected:
Gtk::SpinButton *_sb;
Gtk::Adjustment _adj;
Gtk::Label *_suffix;
- Glib::ustring _key;
- Registry *_wr;
sigc::connection _changed_connection;
void on_value_changed();
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
-class RegisteredRadioButtonPair {
+class RegisteredRadioButtonPair : public RegisteredWidget {
public:
RegisteredRadioButtonPair();
~RegisteredRadioButtonPair();
@@ -169,23 +251,52 @@ public:
const Glib::ustring& tip2,
const Glib::ustring& key,
Registry& wr,
- Inkscape::XML::Node* repr_in=NULL,
- SPDocument *doc_in=NULL);
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& label1,
+ const Glib::ustring& label2,
+ const Glib::ustring& tip1,
+ const Glib::ustring& tip2,
+ const Glib::ustring& key,
+ Registry& wr)
+ { init(label, label1, label2, tip1, tip2, key, wr, NULL, NULL); };
+
void setValue (bool second);
Gtk::HBox *_hbox;
protected:
Gtk::RadioButton *_rb1, *_rb2;
Gtk::Tooltips _tt;
- Glib::ustring _key;
- Registry *_wr;
sigc::connection _changed_connection;
void on_value_changed();
- Inkscape::XML::Node *repr;
- SPDocument *doc;
};
+class RegisteredPoint : public RegisteredWidget {
+public:
+ RegisteredPoint();
+ ~RegisteredPoint();
+ void init (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in,
+ SPDocument *doc_in);
+ inline void init ( const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr)
+ { init(label, tip, key, wr, NULL, NULL); };
+
+ Point* getPoint();
+ void setValue (double xval, double yval);
+protected:
+ Point *_widget;
+ sigc::connection _value_x_changed_connection;
+ sigc::connection _value_y_changed_connection;
+ void on_value_changed();
+};
} // namespace Widget
} // namespace UI
diff --git a/src/verbs.cpp b/src/verbs.cpp
index dc9ebc38e..fbf13ef7a 100644
--- a/src/verbs.cpp
+++ b/src/verbs.cpp
@@ -1745,6 +1745,9 @@ DialogVerb::perform(SPAction *action, void *data, void *pdata)
case SP_VERB_DIALOG_LAYERS:
show_panel( Inkscape::UI::Dialogs::LayersPanel::getInstance(), "dialogs.layers", SP_VERB_DIALOG_LAYERS );
break;
+ case SP_VERB_DIALOG_LIVE_PATH_EFFECT:
+ dt->_dlg_mgr->showDialog("LivePathEffect");
+ break;
case SP_VERB_DIALOG_FILTER_EFFECTS:
dt->_dlg_mgr->showDialog("FilterEffectsDialog");
break;
@@ -2529,6 +2532,8 @@ Verb *Verb::_base_verbs[] = {
N_("Query information about extensions"), NULL),
new DialogVerb(SP_VERB_DIALOG_LAYERS, "DialogLayers", N_("Layer_s..."),
N_("View Layers"), "layers"),
+ new DialogVerb(SP_VERB_DIALOG_LIVE_PATH_EFFECT, "DialogLivePathEffect", N_("Live Path Effect..."),
+ N_("View Live Path Effect parameters"), NULL),
new DialogVerb(SP_VERB_DIALOG_FILTER_EFFECTS, "DialogFilterEffects", N_("Filter Effects..."),
N_("Manage SVG filter effects"), NULL),
diff --git a/src/verbs.h b/src/verbs.h
index ebe5e2fed..a9100de71 100644
--- a/src/verbs.h
+++ b/src/verbs.h
@@ -218,6 +218,7 @@ enum {
SP_VERB_DIALOG_INPUT,
SP_VERB_DIALOG_EXTENSIONEDITOR,
SP_VERB_DIALOG_LAYERS,
+ SP_VERB_DIALOG_LIVE_PATH_EFFECT,
SP_VERB_DIALOG_FILTER_EFFECTS,
/* Help */
SP_VERB_HELP_KEYS,