From: johanengelen Date: Tue, 14 Aug 2007 20:54:48 +0000 (+0000) Subject: Commit LivePathEffect branch to trunk! X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=981b809bc6ed10a21e89444d9447e5475801874f;p=inkscape.git Commit LivePathEffect branch to trunk! (disabled extension/internal/bitmap/*.* in build.xml to fix compilation) --- diff --git a/build.xml b/build.xml index bd798ea68..7e152429d 100644 --- a/build.xml +++ b/build.xml @@ -50,8 +50,7 @@ - - + @@ -249,6 +248,7 @@ + -Wall -O3 @@ -291,6 +291,9 @@ -Wno-comment -I${gtk}/perl/lib/CORE -I${gtk}/python/include + + + -I${boost} @@ -381,7 +384,9 @@ -llcms.dll -lssl -lcrypto -lpng -ljpeg.dll -lpopt ${gtk}/lib/zdll.lib - -lgc -mwindows -lws2_32 -lintl -lm + -lgc -mconsole -lws2_32 -lintl -lgdi32 -lcomdlg32 -lm + + diff --git a/configure.ac b/configure.ac index b776663dc..8e063e0ec 100644 --- a/configure.ac +++ b/configure.ac @@ -589,6 +589,9 @@ else fi PKG_CHECK_MODULES(INKSCAPE, gdkmm-2.4 glibmm-2.4 gtkmm-2.4 gtk+-2.0 >= 2.8.0 libxml-2.0 >= 2.6.11 libxslt >= 1.0.15 cairo sigc++-2.0 >= $min_sigc_version $ink_spell_pkg gthread-2.0 >= 2.0 libpng >= 1.2) +# Check for some boost header files +AC_CHECK_HEADERS([boost/concept_check.hpp], [], AC_MSG_ERROR([You need the boost package (e.g. libboost-dev)])) + PKG_CHECK_MODULES(CAIRO_PDF, cairo-pdf, cairo_pdf=yes, cairo_pdf=no) if test "x$cairo_pdf" = "xyes"; then AC_DEFINE(HAVE_CAIRO_PDF, 1, [Whether the Cairo PDF backend is available]) @@ -877,6 +880,8 @@ src/libnr/makefile src/libnrtype/makefile src/libavoid/makefile src/livarot/makefile +src/live_effects/makefile +src/live_effects/parameter/makefile src/pedro/makefile src/jabber_whiteboard/makefile src/removeoverlap/makefile @@ -892,6 +897,7 @@ src/utest/makefile src/util/makefile src/widgets/makefile src/xml/makefile +src/2geom/makefile doc/Makefile po/Makefile.in share/Makefile diff --git a/share/examples/live-path-effects-gears.svg b/share/examples/live-path-effects-gears.svg new file mode 100644 index 000000000..e85e000d3 --- /dev/null +++ b/share/examples/live-path-effects-gears.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/share/examples/live-path-effects-skeletal.svg b/share/examples/live-path-effects-skeletal.svg new file mode 100644 index 000000000..24044301a --- /dev/null +++ b/share/examples/live-path-effects-skeletal.svg @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + 3 + + + + + 4 + + + + + + + + + + + + + + + + + + + Transform shape into single path + + Copy to clipboard(it is easiest later on to move the noseof the fish to the top-left corner of the document (0,0) before copying to the clipboard, so you don't have to manually tweak the origin parameter) + + Draw the skeleton path + Press Ctrl-Shift-7 and apply Skeletal Strokes to the skeleton path.Press the paste button next to "pattern" + + + 1 + + + 2 + Press F2 and notice that the red skeleton path is live edittable on-canvas + + + 5 + + diff --git a/share/examples/live-path-effects-slant.svg b/share/examples/live-path-effects-slant.svg new file mode 100644 index 000000000..7b547bd91 --- /dev/null +++ b/share/examples/live-path-effects-slant.svg @@ -0,0 +1,73 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/share/examples/live-path-effects-uri.svg b/share/examples/live-path-effects-uri.svg new file mode 100644 index 000000000..a6ecb3ee8 --- /dev/null +++ b/share/examples/live-path-effects-uri.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/share/keys/default.xml b/share/keys/default.xml index 24f4dd3ae..16f8f2e29 100644 --- a/share/keys/default.xml +++ b/share/keys/default.xml @@ -510,6 +510,11 @@ override) the bindings in the main default.xml. + + + + + diff --git a/share/keys/inkscape.xml b/share/keys/inkscape.xml index 24f4dd3ae..e42da28f1 100644 --- a/share/keys/inkscape.xml +++ b/share/keys/inkscape.xml @@ -510,6 +510,10 @@ override) the bindings in the main default.xml. + + + + 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 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 > +find_intersections( OldBezier a, OldBezier b); + +static std::vector > +find_self_intersections(OldBezier const &Sb, D2 const & A); + +std::vector > +find_intersections( vector const & A, + vector const & B) { + OldBezier a, b; + a.p = A; + b.p = B; + return find_intersections(a,b); +} + +std::vector > +find_self_intersections(OldBezier const &Sb) { + throw NotImplemented(); +} + +std::vector > +find_self_intersections(D2 const & A) { + OldBezier Sb; + Sb.p = sbasis_to_bezier(A); + return find_self_intersections(Sb, A); +} + + +static std::vector > +find_self_intersections(OldBezier const &Sb, D2 const & A) { + + + vector dr = roots(derivative(A[X])); + { + vector 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 > all_si; + + vector 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 > 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 > ¶meters) +{ + 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(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 > find_intersections( OldBezier a, OldBezier b) +{ + std::vector > 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 > +find_intersections( D2 const & A, + D2 const & B); + +std::vector > +find_self_intersections(D2 const & A); + +// Bezier form +std::vector > +find_intersections( std::vector const & A, + std::vector const & B); + +std::vector > +find_self_intersections(std::vector 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 + * + * 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 +struct bezier_to_sbasis_impl { + static inline SBasis compute(Coord const *handles) { + return multiply(Linear(1, 0), bezier_to_sbasis_impl::compute(handles)) + + multiply(Linear(0, 1), bezier_to_sbasis_impl::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 +inline SBasis bezier_to_sbasis(Coord const *handles) { + return bezier_to_sbasis_impl::compute(handles); +} + +template +inline D2 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(bezier_to_sbasis(v[0]), + bezier_to_sbasis(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 + * Peter Moulder + * + * 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 +#endif + +#include "bezier-utils.h" + +#include "isnan.h" +#include + +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 + * + * 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 +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 + * Copyright 2007 Michael Sloan + * + * 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 +class Bezier { +private: + Coord c_[order+1]; + +protected: + Bezier(Coord c[]) { + std::copy(c, c+order+1, c_); + } + +public: + template + static void assert_order(Bezier 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(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 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 +Bezier reverse(const Bezier & a) { + Bezier result; + for(int i = 0; i <= order; i++) + result[i] = a[order - i]; + return result; +} + +template +vector bezier_points(const D2 > & a) { + vector 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 + * + * 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 +T choose(unsigned n, unsigned k) { + static std::vector 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 + * + * 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 +#include +#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 + * + * 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 + +namespace Geom { + +template +class Circulator { +public: + typedef std::random_access_iterator_tag std::iterator_category; + typedef std::iterator_traits::value_type value_type; + typedef std::iterator_traits::difference_type difference_type; + typedef std::iterator_traits::pointer pointer; + typedef std::iterator_traits::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 +Geom::Circulator operator+(int n, Geom::Circulator 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 + * + * 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 + +namespace Geom { + +//forward decls +template class D2; + +template struct ResultTraits; + +template <> struct ResultTraits { + typedef Interval bounds_type; + typedef SBasis sb_type; +}; + +template <> struct ResultTraits { + typedef D2 bounds_type; + typedef D2 sb_type; +}; + +//A concept for one-dimensional functions defined on [0,1] +template +struct FragmentConcept { + typedef typename T::output_type OutputType; + typedef typename ResultTraits::bounds_type BoundsType; + typedef typename ResultTraits::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 +inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); } + +template +struct NearConcept { + T a, b; + double tol; + bool res; + void constraints() { + res = near(a, b, tol); + } +}; + +template +struct OffsetableConcept { + T t; + typename T::output_type d; + void constraints() { + t = t + d; t += d; + t = t - d; t -= d; + } +}; + +template +struct ScalableConcept { + T t; + typename T::output_type d; + void constraints() { + t = -t; + t = t * d; t *= d; + t = t / d; t /= d; + } +}; + +template +struct AddableConcept { + T i, j; + void constraints() { + i += j; i = i + j; + i -= j; i = i - j; + } +}; + +template +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 + * + * 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 +#include +#include +#include +#include "conjugate_gradient.h" + +/* lifted wholely from wikipedia. */ + +using std::valarray; + +static void +matrix_times_vector(valarray const &matrix, /* m * n */ + valarray const &vec, /* n */ + valarray &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 const &vec) { + return std::max(vec.max(), -vec.min()); +} + +double +inner(valarray const &x, + valarray 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 vA(n*n); + valarray vx(n); + valarray vb(n); + for(unsigned i=0;i const &A, + valarray &x, + valarray const &b, + unsigned n, double tol, + unsigned max_iterations, bool ortho1) { + valarray 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 + * + * 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 + +double +inner(std::valarray const &x, + std::valarray const &y); + +void +conjugate_gradient(std::valarray const &A, + std::valarray &x, + std::valarray 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 + * Copyright 2006 Michael G. Sloan + * + * 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 +#include +/** 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 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::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::const_iterator pivot = boundary.begin(); + for(std::vector::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, map > +bridges(ConvexHull a, ConvexHull b) { + map abridges; + map 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 bridge_points(ConvexHull a, ConvexHull b) { + vector ret; + pair< map, map > indices = bridges(a, b); + for(map::iterator it = indices.first.begin(); it != indices.first.end(); it++) { + ret.push_back(a[it->first]); + ret.push_back(b[it->second]); + } + for(map::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, map > bpair = bridges(a, b); + map ab = bpair.first; + map 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 + * Copyright 2006 Michael G. Sloan + * + * 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 + +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 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 const & points) { + boundary = points; + graham(); + } + + template + 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 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 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 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 + * + * 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 + +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 + * + * 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 const & a, unsigned k) { return sqrt(dot(a, a), k); } +double L2(D2 const & a) { return hypot(a[0], a[1]); } + +D2 multiply(Linear const & a, D2 const & b) { + return D2(multiply(a, b[X]), multiply(a, b[Y])); +} + +D2 multiply(SBasis const & a, D2 const & b) { + return D2(multiply(a, b[X]), multiply(a, b[Y])); +} + +D2 truncate(D2 const & a, unsigned terms) { + return D2(truncate(a[X], terms), truncate(a[Y], terms)); +} + +unsigned sbasis_size(D2 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 const & a, unsigned tail) { + return std::max(a[0].tailError(tail), a[1].tailError(tail)); +} + +Piecewise > sectionize(D2 > const &a) { + Piecewise x = partition(a[0], a[1].cuts), y = partition(a[1], a[0].cuts); + assert(x.size() == y.size()); + Piecewise > ret; + for(unsigned i = 0; i < x.size(); i++) + ret.push_seg(D2(x[i], y[i])); + ret.cuts.insert(ret.cuts.end(), x.cuts.begin(), x.cuts.end()); + return ret; +} + +D2 > make_cuts_independant(Piecewise > const &a) { + D2 > 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 > rot90(Piecewise > const &M){ + Piecewise > result; + if (M.empty()) return M; + result.push_cut(M.cuts[0]); + for (unsigned i=0; i dot(Piecewise > const &a, + Piecewise > const &b){ + Piecewise result; + if (a.empty() || b.empty()) return result; + Piecewise > aa = partition(a,b.cuts); + Piecewise > bb = partition(b,a.cuts); + + result.push_cut(aa.cuts.front()); + for (unsigned i=0; i cross(Piecewise > const &a, + Piecewise > const &b){ + Piecewise result; + if (a.empty() || b.empty()) return result; + Piecewise > aa = partition(a,b.cuts); + Piecewise > bb = partition(b,a.cuts); + + result.push_cut(aa.cuts.front()); + for (unsigned i=0; i > remove_short_cuts(Piecewise > const &f, double tol){ + double min = tol; + unsigned idx = f.size(); + for(unsigned i=0; i 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>... + return f; + } + Piecewise > 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 (idx0, only force continuity where the jump is smaller than tol. +Piecewise > force_continuity(Piecewise > const &f, + double tol, + bool closed){ + if (f.size()==0) return f; + Piecewise > result=f; + unsigned cur = (closed)? 0:1; + unsigned prev = (closed)? f.size()-1:0; + while(cur + * + * 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 +#include "concepts.h" + +namespace Geom{ + +template +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 >(); + return f[X].isZero() && f[Y].isZero(); + } + bool isFinite() const { + boost::function_requires >(); + return f[X].isFinite() && f[Y].isFinite(); + } + Point at0() const { + boost::function_requires >(); + return Point(f[X].at0(), f[Y].at0()); + } + Point at1() const { + boost::function_requires >(); + return Point(f[X].at1(), f[Y].at1()); + } + Point valueAt(double t) const { + boost::function_requires >(); + return (*this)(t); + } + D2 toSBasis() const { + boost::function_requires >(); + return D2(f[X].toSBasis(), f[Y].toSBasis()); + } + + Point operator()(double t) const; + Point operator()(double x, double y) const; +}; + +template +D2 reverse(const D2 &a) { + boost::function_requires >(); + return D2(reverse(a[X]), reverse(a[Y])); +} + +//IMPL: boost::EqualityComparableConcept +template +inline bool +operator==(D2 const &a, D2 const &b) { + boost::function_requires >(); + return a[0]==b[0] && a[1]==b[1]; +} +template +inline bool +operator!=(D2 const &a, D2 const &b) { + boost::function_requires >(); + return a[0]!=b[0] || a[1]!=b[1]; +} + +//IMPL: NearConcept +template +inline bool +near(D2 const &a, D2 const &b, double tol) { + boost::function_requires >(); + return near(a[0], b[0]) && near(a[1], b[1]); +} + +//IMPL: AddableConcept +template +inline D2 +operator+(D2 const &a, D2 const &b) { + boost::function_requires >(); + + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] + b[i]; + return r; +} +template +inline D2 +operator-(D2 const &a, D2 const &b) { + boost::function_requires >(); + + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] - b[i]; + return r; +} +template +inline D2 +operator+=(D2 &a, D2 const &b) { + boost::function_requires >(); + + for(unsigned i = 0; i < 2; i++) + a[i] += b[i]; + return a; +} +template +inline D2 +operator-=(D2 &a, D2 const & b) { + boost::function_requires >(); + + for(unsigned i = 0; i < 2; i++) + a[i] -= b[i]; + return a; +} + +//IMPL: ScalableConcept +template +inline D2 +operator-(D2 const & a) { + boost::function_requires >(); + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = -a[i]; + return r; +} +template +inline D2 +operator*(D2 const & a, Point const & b) { + boost::function_requires >(); + + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] * b[i]; + return r; +} +template +inline D2 +operator/(D2 const & a, Point const & b) { + boost::function_requires >(); + //TODO: b==0? + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] / b[i]; + return r; +} +template +inline D2 +operator*=(D2 &a, Point const & b) { + boost::function_requires >(); + + for(unsigned i = 0; i < 2; i++) + a[i] *= b[i]; + return a; +} +template +inline D2 +operator/=(D2 &a, Point const & b) { + boost::function_requires >(); + //TODO: b==0? + for(unsigned i = 0; i < 2; i++) + a[i] /= b[i]; + return a; +} + +template +inline D2 operator*(D2 const & a, double b) { return D2(a[0]*b, a[1]*b); } +template +inline D2 operator*=(D2 & a, double b) { a[0] *= b; a[1] *= b; return a; } +template +inline D2 operator/(D2 const & a, double b) { return D2(a[0]/b, a[1]/b); } +template +inline D2 operator/=(D2 & a, double b) { a[0] /= b; a[1] /= b; return a; } + +template +D2 operator*(D2 const &v, Matrix const &m) { + boost::function_requires >(); + boost::function_requires >(); + D2 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 +inline D2 +operator+(D2 const & a, Point b) { + boost::function_requires >(); + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] + b[i]; + return r; +} +template +inline D2 +operator-(D2 const & a, Point b) { + boost::function_requires >(); + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = a[i] - b[i]; + return r; +} +template +inline D2 +operator+=(D2 & a, Point b) { + boost::function_requires >(); + for(unsigned i = 0; i < 2; i++) + a[i] += b[i]; + return a; +} +template +inline D2 +operator-=(D2 & a, Point b) { + boost::function_requires >(); + for(unsigned i = 0; i < 2; i++) + a[i] -= b[i]; + return a; +} + +template +inline T +dot(D2 const & a, D2 const & b) { + boost::function_requires >(); + boost::function_requires >(); + + T r; + for(unsigned i = 0; i < 2; i++) + r += a[i] * b[i]; + return r; +} + +template +inline T +cross(D2 const & a, D2 const & b) { + boost::function_requires >(); + boost::function_requires >(); + + return a[1] * b[0] - a[0] * b[1]; +} + + +//equivalent to cw/ccw, for use in situations where rotation direction doesn't matter. +template +inline D2 +rot90(D2 const & a) { + boost::function_requires >(); + return D2(-a[Y], a[X]); +} + +//TODO: concepterize the following functions +template +inline D2 +compose(D2 const & a, T const & b) { + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a[i],b); + return r; +} + +template +inline D2 +compose_each(D2 const & a, D2 const & b) { + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a[i],b[i]); + return r; +} + +template +inline D2 +compose_each(T const & a, D2 const & b) { + D2 r; + for(unsigned i = 0; i < 2; i++) + r[i] = compose(a,b[i]); + return r; +} + + +template +inline Point +D2::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 +inline Point +D2::operator()(double x, double y) const { + Point p; + for(unsigned i = 0; i < 2; i++) + p[i] = (*this)[i](x, y); + return p; +} + + +template +D2 derivative(D2 const & a) { + return D2(derivative(a[X]), derivative(a[Y])); +} + +template +D2 integral(D2 const & a) { + return D2(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 +Rect bounds_fast(const D2 &a) { + boost::function_requires >(); + return Rect(bounds_fast(a[X]), bounds_fast(a[Y])); +} +template +Rect bounds_exact(const D2 &a) { + boost::function_requires >(); + return Rect(bounds_exact(a[X]), bounds_exact(a[Y])); +} +template +Rect bounds_local(const D2 &a, const Interval &t) { + boost::function_requires >(); + return Rect(bounds_local(a[X], t), bounds_local(a[Y], t)); +} + +//D2 specific decls: + +inline D2 compose(D2 const & a, SBasis const & b) { + return D2(compose(a[X], b), compose(a[Y], b)); +} + +SBasis L2(D2 const & a, unsigned k); +double L2(D2 const & a); + +inline D2 portion(D2 const &M, double t0, double t1){ + return D2(portion(M[0],t0,t1),portion(M[1],t0,t1)); +} + +D2 multiply(Linear const & a, D2 const & b); +inline D2 operator*(Linear const & a, D2 const & b) { return multiply(a, b); } +D2 multiply(SBasis const & a, D2 const & b); +inline D2 operator*(SBasis const & a, D2 const & b) { return multiply(a, b); } +D2 truncate(D2 const & a, unsigned terms); + +unsigned sbasis_size(D2 const & a); +double tail_error(D2 const & a, unsigned tail); + +//Piecewise > specific decls: + +Piecewise > sectionize(D2 > const &a); +D2 > make_cuts_independant(Piecewise > const &a); +Piecewise > rot90(Piecewise > const &a); +Piecewise dot(Piecewise > const &a, Piecewise > const &b); +Piecewise cross(Piecewise > const &a, Piecewise > const &b); + +Piecewise > force_continuity(Piecewise > const &f, + double tol=0, + bool closed=false); + +class CoordIterator +: public std::iterator +{ +public: + CoordIterator(std::vector >::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 >::const_iterator impl_; + unsigned ix_; +}; + +inline CoordIterator iterateCoord(Piecewise > const &a, unsigned d) { + return CoordIterator(a.segs.begin(), d); +} + +//bounds specializations with order +inline Rect bounds_fast(D2 const & s, unsigned order=0) { + return Rect(bounds_fast(s[X], order), + bounds_fast(s[Y], order)); +} +inline Rect bounds_local(D2 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 +#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 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 + * + * 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 +#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 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 + * + * Original Rect/Range code by: + * Lauris Kaplinski + * Nathan Hurst + * bulia byak + * MenTaLguY + * + * 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 +#include "coord.h" + +#include + +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 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() + : boost::optional(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 +/* You might try changing the above to 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 + * Michael Sloan + * + * 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 + * Michael G. Sloan + * + * 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 : + * Original NRMatrix definition and related macros. + * + * Nathan Hurst : + * Geom::Matrix class version of the above. + * + * Michael G. Sloan : + * Reorganization and additions. + * + * This code is in public domain. + */ + +//#include + +#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 + * + * 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 +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 CurveHelpers::sbasis_winding(D2 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(); + } + + 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 ts = roots(fy); + + static const unsigned MAX_DERIVATIVES=8; + boost::optional ds[MAX_DERIVATIVES]; + ds[0] = derivative(fy); + + /* winding determined by summing signs of derivatives at intersections */ + int winding=0; + for ( std::vector::iterator ti = ts.begin() + ; ti != ts.end() + ; ++ti ) + { + double t = *ti; + if ( sb[X](t) >= p[X] ) { /* root is ray intersection */ + for ( boost::optional *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 ignore = boost::optional(); + for ( const_iterator iter = begin() + ; iter != end_closed() + ; ++iter ) + { + boost::optional w = iter->winding(p); + if (w) { + winding += *w; + ignore = boost::optional(); + } 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 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 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 + * + * 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 +#include +#include +#include +#include +#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 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() const = 0; +}; + +struct CurveHelpers { +protected: + static boost::optional sbasis_winding(D2 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 +class Bezier : public Curve, private CurveHelpers, private BezierHelpers { +public: + template + static void assert_degree(Bezier 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 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() const { + return handles_to_sbasis(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 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() 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 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 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() const { return coeffs_; } + +private: + D2 coeffs_; +}; + +template +class BaseIterator +: public std::iterator +{ +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 +class DuplicatingIterator +: public std::iterator +{ +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 Sequence; + +public: + typedef BaseIterator iterator; + typedef BaseIterator 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 + Path(BaseIterator first, BaseIterator 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 > toPwSb() const { + Piecewise > 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 + void insert(iterator pos, BaseIterator first, BaseIterator last) + { + Sequence source(DuplicatingIterator(first.impl_), + DuplicatingIterator(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 + void replace(iterator replaced, + BaseIterator first, BaseIterator last) + { + Sequence source(DuplicatingIterator(first.impl_), + DuplicatingIterator(last.impl_)); + try { + do_update(replaced.impl_, replaced.impl_+1, source.begin(), source.end()); + } catch (...) { + delete_range(source.begin(), source.end()); + throw; + } + } + + template + void replace(iterator first_replaced, iterator last_replaced, + BaseIterator first, BaseIterator 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 const &curve); + + template + void appendNew(A a) { + do_append(new CurveType((*final_)[0], a)); + } + + template + void appendNew(A a, B b) { + do_append(new CurveType((*final_)[0], a, b)); + } + + template + void appendNew(A a, B b, C c) { + do_append(new CurveType((*final_)[0], a, b, c)); + } + + template + void appendNew(A a, B b, C c, D d) { + do_append(new CurveType((*final_)[0], a, b, c, d)); + } + + template + void appendNew(A a, B b, C c, D d, E e) { + do_append(new CurveType((*final_)[0], a, b, c, d, e)); + } + + template + 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 + 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 + 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 + 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 > paths_to_pw(std::vector paths) { + Piecewise > ret = paths[0].toPwSb(); + for(unsigned i = 1; i < paths.size(); i++) { + ret.concat(paths[i].toPwSb()); + } + return ret; +} + +template +inline Path const &Bezier::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 &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 + * 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 +#include + +namespace Geom { + +Piecewise divide(Piecewise const &a, Piecewise const &b, unsigned k) { + Piecewise pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise ret = Piecewise(); + 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 +divide(Piecewise const &a, Piecewise const &b, double tol, unsigned k, double zero) { + Piecewise pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise ret = Piecewise(); + assert(pa.size() == pb.size()); + for (unsigned i = 0; i < pa.size(); i++){ + Piecewise 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 divide(Piecewise const &a, SBasis const &b, double tol, unsigned k, double zero){ + return divide(a,Piecewise(b),tol,k,zero); +} +Piecewise divide(SBasis const &a, Piecewise const &b, double tol, unsigned k, double zero){ + return divide(Piecewise(a),b,tol,k,zero); +} +Piecewise 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(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(c); + } + + Piecewise 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,SBasis) --------------- +/* + the purpose of the following functions is only to reduce the code in piecewise.h + TODO: use a vector > instead of a map. + */ + +std::map compose_pullback(std::vector const &values, SBasis const &g){ + std::map result; + + std::vector > roots = multi_roots(g, values); + for(unsigned i=0; ivalues[i])) i++; + result[0.]=i; + } + if(result.count(1.)==0){ + unsigned i=0; + while (ivalues[i])) i++; + result[1.]=i; + } + return(result); +} + +int compose_findSegIdx(std::map::iterator const &cut, + std::map::iterator const &next, + std::vector const &levels, + SBasis const &g){ + double t0=(*cut).first; + unsigned idx0=(*cut).second; + double t1=(*next).first; + unsigned idx1=(*next).second; + assert(t0 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 roots(Piecewise const &f){ + std::vector result; + for (unsigned i=0; i rts=roots(f.segs[i]); + rts=roots(f.segs[i]); + + for (unsigned r=0; r + * + * 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 +#include + +#include "concepts.h" +#include "isnan.h" +#include + +namespace Geom { + +template +class Piecewise { + BOOST_CLASS_REQUIRE(T, Geom, FragmentConcept); + + public: + std::vector cuts; + std::vector 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 operator()(SBasis f); + Piecewise operator()(Piecewisef); + + 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 &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 &other) { + boost::function_requires >(); + 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 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 +inline Interval bounds_fast(const Piecewise &f) { + boost::function_requires >(); + + 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 +inline Interval bounds_exact(const Piecewise &f) { + boost::function_requires >(); + + 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 +inline Interval bounds_local(const Piecewise &f, const Interval &m) { + boost::function_requires >(); + + 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, given the piece's index and a to/from time. +template +T elem_portion(const Piecewise &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 partition(const Piecewise &pw, std::vector const &c); + * Further subdivides the Piecewise such that there is a cut at every value in c. + * Precondition: c sorted lower to higher. + * + * //Given Piecewise a and b: + * Piecewise ac = a.partition(b.cuts); + * Piecewise bc = b.partition(a.cuts); + * //ac.cuts should be equivalent to bc.cuts + */ +template +Piecewise partition(const Piecewise &pw, std::vector const &c) { + assert(pw.invariants()); + if(c.empty()) return Piecewise(pw); + + Piecewise ret = Piecewise(); + 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, 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 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, 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 portion(const Piecewise &pw, double from, double to); + * Returns a Piecewise with a defined domain of [min(from, to), max(from, to)]. + */ +template +Piecewise portion(const Piecewise &pw, double from, double to) { + if(pw.empty() || from == to) return Piecewise(); + + Piecewise 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 +Piecewise remove_short_cuts(Piecewise const &f, double tol) { + if(f.empty()) return f; + Piecewise ret; + ret.push_cut(f.cuts[0]); + for(unsigned i=0; i= tol || i==f.size()-1) { + ret.push(f[i], f.cuts[i+1]); + } + } + return ret; +} + +template +Piecewise remove_short_cuts_extending(Piecewise const &f, double tol) { + if(f.empty()) return f; + Piecewise ret; + ret.push_cut(f.cuts[0]); + double last = f.cuts[0]; // last cut included + for(unsigned i=0; 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 +std::vector roots(const Piecewise &pw) { + std::vector ret; + for(unsigned i = 0; i < pw.size(); i++) { + std::vector 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 +Piecewise operator+(Piecewise const &a, typename T::output_type b) { + boost::function_requires >(); +//TODO:empty + Piecewise ret = Piecewise(); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] + b); + return ret; +} +template +Piecewise operator-(Piecewise const &a, typename T::output_type b) { + boost::function_requires >(); +//TODO: empty + Piecewise ret = Piecewise(); + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] - b); + return ret; +} +template +Piecewise operator+=(Piecewise& a, typename T::output_type b) { + boost::function_requires >(); + + 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 +Piecewise operator-=(Piecewise& a, typename T::output_type b) { + boost::function_requires >(); + + 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 +Piecewise operator-(Piecewise const &a) { + boost::function_requires >(); + + Piecewise ret; + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(- a[i]); + return ret; +} +template +Piecewise operator*(Piecewise const &a, double b) { + boost::function_requires >(); + + if(a.empty()) return Piecewise(); + + Piecewise ret; + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] * b); + return ret; +} +template +Piecewise operator/(Piecewise const &a, double b) { + boost::function_requires >(); + + //FIXME: b == 0? + if(a.empty()) return Piecewise(); + + Piecewise ret; + ret.cuts = a.cuts; + for(unsigned i = 0; i < a.size();i++) + ret.push_seg(a[i] / b); + return ret; +} +template +Piecewise operator*=(Piecewise& a, double b) { + boost::function_requires >(); + + if(a.empty()) return Piecewise(); + + for(unsigned i = 0; i < a.size();i++) + a[i] *= b; + return a; +} +template +Piecewise operator/=(Piecewise& a, double b) { + boost::function_requires >(); + + //FIXME: b == 0? + if(a.empty()) return Piecewise(); + + for(unsigned i = 0; i < a.size();i++) + a[i] /= b; + return a; +} + +//IMPL: AddableConcept +template +Piecewise operator+(Piecewise const &a, Piecewise const &b) { + boost::function_requires >(); + + Piecewise pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise ret = Piecewise(); + 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 +Piecewise operator-(Piecewise const &a, Piecewise const &b) { + boost::function_requires >(); + + Piecewise pa = partition(a, b.cuts), pb = partition(b, a.cuts); + Piecewise ret = Piecewise(); + 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 +inline Piecewise operator+=(Piecewise &a, Piecewise const &b) { + a = a+b; + return a; +} +template +inline Piecewise operator-=(Piecewise &a, Piecewise const &b) { + a = a-b; + return a; +} + +template +Piecewise operator*(Piecewise const &a, Piecewise const &b) { + //function_requires >(); + //function_requires >(); + + Piecewise pa = partition(a, b.cuts); + Piecewise pb = partition(b, a.cuts); + Piecewise ret = Piecewise(); + 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 +inline Piecewise operator*=(Piecewise &a, Piecewise const &b) { + a = a * b; + return a; +} + +Piecewise divide(Piecewise const &a, Piecewise 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 +divide(Piecewise const &a, Piecewise const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise +divide(SBasis const &a, Piecewise const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise +divide(Piecewise const &a, SBasis const &b, double tol, unsigned k, double zero=1.e-3); +Piecewise +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 compose_pullback(std::vector const &cuts, SBasis const &g); +int compose_findSegIdx(std::map::iterator const &cut, + std::map::iterator const &next, + std::vector const &levels, + SBasis const &g); + +//TODO: add concept check +template +Piecewise compose(Piecewise const &f, SBasis const &g){ + Piecewise result; + if (f.empty()) return result; + if (g.isZero()) return Piecewise(f(0)); + if (f.size()==1){ + double t0 = f.cuts[0], width = f.cuts[1] - t0; + return (Piecewise) 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) compose(f.segs[idx],compose(Linear(-t0 / width, (1-t0) / width), g)); + } + + std::vector 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 > instead of a map. + std::map cuts_pb = compose_pullback(levels,g); + + //-- Compose each piece of g with the relevant seg of f. + result.cuts.push_back(0.); + std::map::iterator cut=cuts_pb.begin(); + std::map::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 +Piecewise compose(Piecewise const &f, Piecewise const &g){ + Piecewise result; + for(unsigned i = 0; i < g.segs.size(); i++){ + Piecewise fgi=compose(f, g.segs[i]); + fgi.setDomain(Interval(g.cuts[i], g.cuts[i+1])); + result.concat(fgi); + } + return result; +} + +template +Piecewise Piecewise::operator()(SBasis f){return compose((*this),f);} +template +Piecewise Piecewise::operator()(Piecewisef){return compose((*this),f);} + +template +Piecewise integral(Piecewise const &a) { + Piecewise 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 +Piecewise derivative(Piecewise const &a) { + Piecewise 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 roots(Piecewise 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 +#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 +#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 + +#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 + +/*** implementation of the Durand-Kerner method. seems buggy*/ + +std::complex evalu(Poly const & p, std::complex x) { + std::complex result = 0; + std::complex xx = 1; + + for(unsigned i = 0; i < p.size(); i++) { + result += p[i]*xx; + xx *= x; + } + return result; +} + +std::vector > DK(Poly const & ply, const double tol) { + std::vector > roots; + const int N = ply.degree(); + + std::complex b(0.4, 0.9); + std::complex 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 denom = 1; + std::complex 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 dr = evalu(ply, R)/denom; + error += norm(dr); + roots[r_i] = R - dr; + } + /*std::copy(roots.begin(), roots.end(), std::ostream_iterator >(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 + +std::vector > +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 + +typedef std::complex 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 +laguerre(Poly p, const double tol) { + std::vector 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 +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 + +std::vector > +laguerre(Poly ply, const double tol=1e-10); + +std::vector +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 +#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 > 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 > 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 (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 solve_reals(Poly const & p) { + std::vector > roots = solve(p); + std::vector 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 +#include +#include +#include +#include +#include "utils.h" + +class Poly : public std::vector{ +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 + 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 + T operator()(T t) const { return (T)eval(t);} + + void normalize(); + + void monicify(); + Poly() {} + Poly(const Poly& p) : std::vector(p) {} + Poly(const double a) {push_back(a);} + +public: + template + 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 > 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 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 +#include + +class Quad{ +public: + Quad* children[4]; + std::vector data; + Quad() { + for(int i = 0; i < 4; i++) + children[i] = 0; + } + typedef std::vector::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 specialization to Rect: + + /* Authors of original rect class: + * Lauris Kaplinski + * Nathan Hurst + * bulia byak + * MenTaLguY + */ + +#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 + +namespace Geom { + +typedef D2 Rect; + +Rect unify(const Rect &, const Rect &); + +template<> +class D2 { + private: + Interval f[2]; + D2();// { f[X] = f[Y] = Interval(0, 0); } + + public: + D2(Interval const &a, Interval const &b) { + f[X] = a; + f[Y] = b; + } + + D2(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 intersect(const Rect & a, const Rect & b) { + boost::optional x = intersect(a[X], b[X]); + boost::optional y = intersect(a[Y], b[Y]); + return x && y ? boost::optional(Rect(*x, *y)) : boost::optional(); +} + +} + +#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 const &p) { + D2 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 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 +compose_each(D2 const &fg, D2 const &p) { + return D2(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 +#include +#include +#include "d2.h" +#include "sbasis.h" +#include + +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{ +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(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 const &p); + +SBasis compose(SBasis2d const &fg, D2 const &p); + +D2 compose_each(D2 const &fg, D2 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 (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 +vect_intersect(vector const &a, vector const &b, double tol=0.){ + vector inter; + unsigned i=0,j=0; + while ( ib[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 RescaleForNonVanishingEnds(D2 const &MM, double ZERO=1.e-4){ + D2 M = MM; + //TODO: divide by all the s at once!!! + while (fabs(M[0].at0()) > +Geom::cutAtRoots(Piecewise > const &M, double ZERO){ + vector rts; + for (unsigned i=0; i 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 +Geom::atan2(Piecewise > const &vect, double tol, unsigned order){ + Piecewise result; + Piecewise > v = cutAtRoots(vect); + result.cuts.push_back(v.cuts.front()); + for (unsigned i=0; i vi = RescaleForNonVanishingEnds(v.segs[i]); + SBasis x=vi[0], y=vi[1]; + Piecewise 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 +Geom::atan2(D2 const &vect, double tol, unsigned order){ + return atan2(Piecewise >(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 > +Geom::unitVector(D2 const &V_in, double tol, unsigned order){ + D2 V = RescaleForNonVanishingEnds(V_in); + if (V[0].empty() && V[1].empty()) + return Piecewise >(D2(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 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 > 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 > result; + result=(Piecewise >)unitV; + return result; + } +} + +Piecewise > +Geom::unitVector(Piecewise > const &V, double tol, unsigned order){ + Piecewise > result; + Piecewise > VV = cutAtRoots(V); + result.cuts.push_back(VV.cuts.front()); + for (unsigned i=0; i > 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 +Geom::arcLengthSb(Piecewise > const &M, double tol){ + Piecewise > dM = derivative(M); + Piecewise dMlength = sqrt(dot(dM,dM),tol,3); + Piecewise length = integral(dMlength); + length-=length.segs.front().at0(); + return length; +} +Piecewise +Geom::arcLengthSb(D2 const &M, double tol){ + return arcLengthSb(Piecewise >(M), tol); +} + +double +Geom::length(D2 const &M, + double tol){ + Piecewise length = arcLengthSb(M, tol); + return length.segs.back().at1(); +} +double +Geom::length(Piecewise > const &M, + double tol){ + Piecewise length = arcLengthSb(M, tol); + return length.segs.back().at1(); +} + + +// incomplete. +Piecewise +Geom::curvature(D2 const &M, double tol) { + D2 dM=derivative(M); + Piecewise result; + Piecewise > unitv = unitVector(dM,tol); + Piecewise dMlength = dot(Piecewise >(dM),unitv); + Piecewise k = cross(derivative(unitv),unitv); + k = divide(k,dMlength,tol,3); + return(k); +} + +Piecewise +Geom::curvature(Piecewise > const &V, double tol){ + Piecewise result; + Piecewise > VV = cutAtRoots(V); + result.cuts.push_back(VV.cuts.front()); + for (unsigned i=0; i 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 > +Geom::arc_length_parametrization(D2 const &M, + unsigned order, + double tol){ + Piecewise > u; + u.push_cut(0); + + Piecewise s = arcLengthSb(Piecewise >(M),tol); + for (unsigned i=0; i < s.size();i++){ + double t0=s.cuts[i],t1=s.cuts[i+1]; + D2 sub_M = compose(M,Linear(t0,t1)); + D2 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 > +Geom::arc_length_parametrization(Piecewise > const &M, + unsigned order, + double tol){ + Piecewise > result; + for (unsigned i=0; i > 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 > 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 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 + +/** 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 > +cutAtRoots(Piecewise > const &M, double tol=1e-4); + +Piecewise +atan2(D2 const &vect, + double tol=.01, unsigned order=3); + +Piecewise +atan2(Piecewise >const &vect, + double tol=.01, unsigned order=3); + +Piecewise > +unitVector(D2 const &vect, + double tol=.01, unsigned order=3); +Piecewise > +unitVector(Piecewise > const &vect, + double tol=.01, unsigned order=3); + +// Piecewise > +// uniform_speed(D2 const M, +// double tol=.1); + +Piecewise curvature( D2 const &M, double tol=.01); +Piecewise curvature(Piecewise > const &M, double tol=.01); + +Piecewise arcLengthSb( D2 const &M, double tol=.01); +Piecewise arcLengthSb(Piecewise > const &M, double tol=.01); + +double length( D2 const &M, double tol=.01); +double length(Piecewise > const &M, double tol=.01); + +Piecewise > +arc_length_parametrization(D2 const &M, + unsigned order=3, + double tol=.01); +Piecewise > +arc_length_parametrization(Piecewise > const &M, + unsigned order=3, + double tol=.01); + + +unsigned centroid(Piecewise > 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. +//TODO: in all these functions, compute 'order' according to 'tol'. + +#include "sbasis-math.h" +//#define ZERO 1e-3 + + +namespace Geom { + + +#include +#include + +//-|x|----------------------------------------------------------------------- +Piecewise abs(SBasis const &f){ + return abs(Piecewise(f)); +} +Piecewise abs(Piecewise const &f){ + Piecewise absf=partition(f,roots(f)); + for (unsigned i=0; i maxSb( SBasis const &f, SBasis const &g){ + return maxSb(Piecewise(f),Piecewise(g)); +} +Piecewise maxSb(Piecewise const &f, SBasis const &g){ + return maxSb(f,Piecewise(g)); +} +Piecewise maxSb( SBasis const &f, Piecewise const &g){ + return maxSb(Piecewise(f),g); +} +Piecewise maxSb(Piecewise const &f, Piecewise const &g){ + Piecewise maxSb=partition(f,roots(f-g)); + Piecewise gg =partition(g,maxSb.cuts); + maxSb = partition(maxSb,gg.cuts); + for (unsigned i=0; i +minSb( SBasis const &f, SBasis const &g){ return -maxSb(-f,-g); } +Piecewise +minSb(Piecewise const &f, SBasis const &g){ return -maxSb(-f,-g); } +Piecewise +minSb( SBasis const &f, Piecewise const &g){ return -maxSb(-f,-g); } +Piecewise +minSb(Piecewise const &f, Piecewise const &g){ return -maxSb(-f,-g); } + + +//-sign(x)--------------------------------------------------------------- +Piecewise signSb(SBasis const &f){ + return signSb(Piecewise(f)); +} +Piecewise signSb(Piecewise const &f){ + Piecewise sign=partition(f,roots(f)); + for (unsigned i=0; i sqrt_internal(SBasis const &f, + double tol, + int order){ + SBasis sqrtf; + if(f.isZero() || order == 0){ + return Piecewise(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(sqrtf); + } + + Piecewise 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 sqrt(SBasis const &f, double tol, int order){ + return sqrt(maxSb(f,Linear(tol*tol)),tol,order); +} + +Piecewise sqrt(Piecewise const &f, double tol, int order){ + Piecewise result; + Piecewise ff=maxSb(f,Linear(tol*tol)); + + for (unsigned i=0; i 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 sin( SBasis const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));} +Piecewise sin(Piecewise const &f, double tol, int order){return(cos(-f+M_PI/2,tol,order));} + +Piecewise cos(Piecewise const &f, double tol, int order){ + Piecewise result; + for (unsigned i=0; i cosfi = cos(f.segs[i],tol,order); + cosfi.setDomain(Interval(f.cuts[i],f.cuts[i+1])); + result.concat(cosfi); + } + return result; +} + +Piecewise 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(std::cos(alpha)*c-std::sin(alpha)*s); + } + } + Piecewise 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 &f, int order){ + if (order>=0){ + for (unsigned k=0; k reciprocalOnDomain(Interval range, double tol){ + Piecewise 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 (areciprocal_fn_neg; + //TODO: define reverse(pw); + reciprocal_fn_neg.cuts.push_back(-reciprocal_fn.cuts.back()); + for (unsigned i=0; i0){ + reciprocal_fn_neg.concat(reciprocal_fn); + } + reciprocal_fn=reciprocal_fn_neg; + } + + return(reciprocal_fn); +} + +Piecewise reciprocal(SBasis const &f, double tol, int order){ + Piecewise reciprocal_fn=reciprocalOnDomain(bounds_fast(f), tol); + Piecewise result=compose(reciprocal_fn,f); + truncateResult(result,order); + return(result); +} +Piecewise reciprocal(Piecewise const &f, double tol, int order){ + Piecewise reciprocal_fn=reciprocalOnDomain(bounds_fast(f), tol); + Piecewise 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. +//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 abs( SBasis const &f); +Piecewise abs(Piecewiseconst &f); + +//- max(f,g), min(f,g) ---------------------------------------------- +Piecewise maxSb( SBasis const &f, SBasis const &g); +Piecewise maxSb(Piecewise const &f, SBasis const &g); +Piecewise maxSb( SBasis const &f, Piecewise const &g); +Piecewise maxSb(Piecewise const &f, Piecewise const &g); +Piecewise minSb( SBasis const &f, SBasis const &g); +Piecewise minSb(Piecewise const &f, SBasis const &g); +Piecewise minSb( SBasis const &f, Piecewise const &g); +Piecewise minSb(Piecewise const &f, Piecewise const &g); + +//-sign(x)--------------------------------------------------------------- +Piecewise signSb( SBasis const &f); +Piecewise signSb(Piecewiseconst &f); + +//-Sqrt--------------------------------------------------------------- +Piecewise sqrt( SBasis const &f, double tol=1e-3, int order=3); +Piecewise sqrt(Piecewiseconst &f, double tol=1e-3, int order=3); + +//-sin/cos-------------------------------------------------------------- +Piecewise cos( SBasis const &f, double tol=1e-3, int order=3); +Piecewise cos(Piecewise const &f, double tol=1e-3, int order=3); +Piecewise sin( SBasis const &f, double tol=1e-3, int order=3); +Piecewise sin(Piecewise const &f, double tol=1e-3, int order=3); + +//--1/x------------------------------------------------------------ +//TODO: change this... +Piecewise reciprocalOnDomain(Interval range, double tol=1e-3); +Piecewise reciprocal( SBasis const &f, double tol=1e-3, int order=3); +Piecewise reciprocal(Piecewiseconst &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 +#include + +#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); + vectorextrema = roots(df); + for (unsigned i=0; i=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 || tt1) { + 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 || tt1) { + 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 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 const &levels, + std::vector > &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 no root there. + tb_hi=tb_lo=a-1;//default values => no root there. + + if (idxa<(int)levels.size() && fabs(fa-levels.at(idxa))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))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) > multi_roots(SBasis const &f, + std::vector const &levels, + double htol, + double vtol, + double a, + double b){ + + std::vector > roots(levels.size(), std::vector()); + + 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 & 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 roots(SBasis const & s) { + if(s.size() == 0) return std::vector(); + std::vector 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 + +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(n-2*k-1, j-k) / + choose(n,j); +} + +// this produces a degree 2q bezier from a degree k sbasis +std::vector +sbasis_to_bezier(SBasis const &B, unsigned q) { + std::vector 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 +sbasis_to_bezier(D2 const &B, unsigned qq) { + std::vector 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 +D2 > sbasis_to_bezier(D2 const &B) { + return D2 >(sbasis_to_bezier(B[0]), sbasis_to_bezier(B[1])); +} +*/ + +#if 0 // using old path +//std::vector +// mutating +void +subpath_from_sbasis(Geom::OldPathSetBuilder &pb, D2 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 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 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 Bs = compose(B, Linear(0, a)); + assert(Bs.tail_error(k)); + std::vector 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 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 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 +path_from_piecewise(Geom::Piecewise > const &B, double tol) { + std::vector 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 +sbasis_to_bezier(SBasis const &B, unsigned q = 0); + +std::vector +sbasis_to_bezier(D2 const &B, unsigned q = 0); + +std::vector path_from_piecewise(Piecewise > const &B, double tol); + +void path_from_sbasis(Path &pb, D2 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 + * Michael Sloan + * + * 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 + +#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= 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 << ": ---------" < + * Michael Sloan + * + * 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 +#include +#include + +#include "linear.h" +#include "interval.h" + +namespace Geom { + +/*** An empty SBasis is identically 0. */ +class SBasis : public std::vector{ +public: + SBasis() {} + explicit SBasis(double a) { + push_back(Linear(a,a)); + } + SBasis(SBasis const & a) : + std::vector(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::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 roots(SBasis const & s); +std::vector > multi_roots(SBasis const &f, + std::vector 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 + +/*** 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 +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 &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 &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 + +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 &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 & 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 & 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 & 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{ +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 + * Copyright 2007 Aaron Spike + * + * 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 +#include +#include + +#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 _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 + * Copyright 2007 Aaron Spike + * + * 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 +#include +#include +#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 parse_svg_path(char const *str) throw(SVGPathParseError) { + /*PathBuilder b; + parse_svg_path(str, b); + return b.peek();*/ + std::vector subpaths; + std::back_insert_iterator > iter(subpaths); + SVGPathGenerator > > generator(iter); + parse_svg_path(str, generator); + return subpaths; +} + +inline std::vector 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 + * + * 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 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 +bool output_as(Curve const &curve, SVGPathSink &sink) { + T const *t = dynamic_cast(&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(*iter, sink) || + output_as(*iter, sink) || + output_as(*iter, sink) || + output_as(*iter, sink) || + output_as(*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 + * + * 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 + +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 +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(p); + } + + void curveTo(Point c0, Point c1, Point p) { + _path.appendNew(c0, c1, p); + } + + void quadTo(Point c, Point p) { + _path.appendNew(c, p); + } + + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point p) + { + _path.appendNew(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 > iter; + +class PathBuilder : public SVGPathGenerator { +private: + std::vector _pathset; +public: + PathBuilder() : SVGPathGenerator(iter(_pathset)) {} + std::vector 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 + +namespace Geom { + +template +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 + * + * 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 +#include + +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 inline int sgn(const T& x) {return (x < 0 ? -1 : (x > 0 ? 1 : 0) );} + +template inline T sqr(const T& x) {return x * x;} +template 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 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 + * + * 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 + +#include "live_effects/effect.h" +#include "live_effects/lpeobject.h" +#include "live_effects/parameter/parameter.h" +#include +#include "live_effects/n-art-bpath-2geom.h" +#include "display/curve.h" +#include <2geom/sbasis-to-bezier.h> +#include + +// 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 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 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 orig_pathv = BPath_to_2GeomPath(path_in); + + std::vector result_pathv = doEffect(orig_pathv); + + NArtBpath *new_bpath = BPath_from_2GeomPath(result_pathv); + + return new_bpath; +} + +std::vector +Effect::doEffect (std::vector & path_in) +{ + Geom::Piecewise > pwd2_in; + // FIXME: find standard function to convert std::vector ==> Piecewise< D2 > + for (unsigned int i=0; i < path_in.size(); i++) { + pwd2_in.concat( path_in[i].toPwSb() ); + } + + Geom::Piecewise > pwd2_out = doEffect(pwd2_in); + + std::vector path_out = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + + return path_out; +} + +Geom::Piecewise > +Effect::doEffect (Geom::Piecewise > & 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(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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "display/display-forward.h" +#include +#include +#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 LPETypeData[ENDTYPE_LPE]; +extern const Util::EnumDataConverter 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 ) + virtual NArtBpath * + doEffect (NArtBpath * path_in); + virtual std::vector + doEffect (std::vector & path_in); + virtual Geom::Piecewise > + doEffect (Geom::Piecewise > & pwd2_in); + + void registerParameter(Parameter * param); + + typedef std::map 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-gears.h" + +#include +#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 _involute(double start, double stop) { + D2 B; + D2 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 _arc(double start, double stop, double R) { + D2 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 &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 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 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 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(leading_end); + } + + D2 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(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(&teeth) ); + registerParameter( dynamic_cast(&phi) ); +} + +LPEGears::~LPEGears() +{ + +} + +std::vector +LPEGears::doEffect (std::vector & path_in) +{ + std::vector 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 + * + * 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 doEffect (std::vector & 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 + * + * 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 +#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 +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 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 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(&origin) ); + registerParameter( dynamic_cast(&pattern) ); + registerParameter( dynamic_cast(©type) ); + + pattern.signal_path_pasted.connect(sigc::mem_fun(*this, &LPESkeletalStrokes::on_pattern_pasted)); +} + +LPESkeletalStrokes::~LPESkeletalStrokes() +{ + +} + + +Geom::Piecewise > +LPESkeletalStrokes::doEffect (Geom::Piecewise > & 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 * data = copytype.get_selected_data(); + if (!data) + return pwd2_in; + SkelCopyType type = data->id; + + Piecewise > uskeleton = arc_length_parametrization(Piecewise >(pwd2_in),2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise > n = rot90(derivative(uskeleton)); + n = force_continuity(remove_short_cuts(n,.1)); + + D2 > patternd2 = make_cuts_independant(pattern); + Piecewise x=Piecewise(patternd2[0]-origin[0]); + Piecewise y=Piecewise(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 >output; + for (int i=0; i + * + * 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 > doEffect (Geom::Piecewise > & pwd2_in); + +private: + PathParam pattern; + PointParam origin; + EnumParam 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 + * + * 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 +#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(&factor) ); + registerParameter( dynamic_cast(¢er) ); +} + +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 + * + * 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-test-doEffect-stack.h" + +#include <2geom/piecewise.h> +#include +#include + +namespace Inkscape { +namespace LivePathEffect { + + +LPEdoEffectStackTest::LPEdoEffectStackTest(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + step(_("Stack step"), _(""), "step", &wr, this) +{ + registerParameter( dynamic_cast(&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 +LPEdoEffectStackTest::doEffect (std::vector & path_in) +{ + if (step >= 3) { + return Effect::doEffect(path_in); + } else { + // return here + std::vector path_out = path_in; + return path_out; + } +} + +Geom::Piecewise > +LPEdoEffectStackTest::doEffect (Geom::Piecewise > & pwd2_in) +{ + Geom::Piecewise > 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 + * + * 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 doEffect (std::vector & path_in); + Geom::Piecewise > doEffect (Geom::Piecewise > & 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 +#include +#include + +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 + * + * 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 + +#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 + * + * 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include "live_effects/n-art-bpath-2geom.h" +#include "svg/svg.h" +#include +#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 +#include +#include +#include + +static void curve_to_svgd(std::ostream & f, Geom::Curve const* c) { + if(Geom::LineSegment const *line_segment = dynamic_cast(c)) { + f << boost::format("L %g,%g ") % (*line_segment)[1][0] % (*line_segment)[1][1]; + } + else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast(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(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(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 const &p) { + std::vector::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(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(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(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 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 const &peek() const { return _pathset; } + +private: + std::vector _pathset; + Path *_current_path; + Point _current_point; + Point _initial_point; + double _continuity_tollerance; +}; + +static +std::vector +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 +SVGD_to_2GeomPath (char const *svgd) +{ + std::vector 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 +BPath_to_2GeomPath(NArtBpath const * bpath) +{ + std::vector 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 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 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/path.h> +#include + +std::vector SVGD_to_2GeomPath (char const *svgd); +std::vector BPath_to_2GeomPath (NArtBpath const *bpath); +char * SVGD_from_2GeomPath(std::vector const & path); +NArtBpath * BPath_from_2GeomPath (std::vector 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "ui/widget/registry.h" +#include "ui/widget/registered-enums.h" +#include + +#include "live_effects/parameter/parameter.h" +#include "verbs.h" + +namespace Inkscape { + +namespace LivePathEffect { + +template class EnumParam : public Parameter { +public: + EnumParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + const Util::EnumDataConverter& 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(); + 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 (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* 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 * regenum; + + const Util::EnumDataConverter * 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 + * + * 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 +#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 (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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#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 ¶m_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 + * + * 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 +#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 > newpath; + std::vector temppath = SVGD_to_2GeomPath(strvalue); + for (unsigned int i=0; i < temppath.size(); i++) { + newpath.concat( temppath[i].toPwSb() ); + } + *( dynamic_cast > *> (this) ) = newpath; + return true; + } + + return false; +} + +gchar * +PathParam::param_writeSVGValue() const +{ + const std::vector temppath = + Geom::path_from_piecewise(* dynamic_cast > *> (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(_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(_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(_widget)->pack_start(*pButton, true, true); + _tooltips->set_tip(*pButton, _("Paste path")); + + static_cast(_widget)->show_all_children(); + + } + return dynamic_cast (_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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/path.h> + +#include "ui/widget/registry.h" +#include + +#include "live_effects/parameter/parameter.h" + +#include + +namespace Inkscape { + +namespace LivePathEffect { + +class PathParam : public Geom::Piecewise >, 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 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 + * + * 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 +#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( 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(_widget)->pack_start(*pButton, true, true); + static_cast(_widget)->pack_start(*(pointwdg->getPoint()), true, true); + static_cast(_widget)->show_all_children(); + + _tooltips = new Gtk::Tooltips(); + _tooltips->set_tip(*pButton, _("Edit on-canvas")); + } + return dynamic_cast (_widget); +} + +void +PointParam::param_setValue(Geom::Point newpoint) +{ + *dynamic_cast( 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 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/point.h> + +#include "ui/widget/registry.h" +#include "ui/widget/registered-widget.h" +#include + +#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[] = " \n" " \n" " \n" +" \n" +" \n" " \n" " \n" " \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 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 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 +#include "display/canvas-bpath.h" #include "display/curve.h" #include "display/sp-ctrlline.h" #include "display/sodipodi-ctrl.h" #include #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 #include +#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 #include @@ -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 &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); registerFactory("InkscapePreferences", &create); registerFactory("LayerEditor", &create); + registerFactory("LivePathEffect", &create); registerFactory("Memory", &create); registerFactory("Messages", &create); registerFactory("Script", &create); 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 + * + * Copyright (C) 2007 Author + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#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 +#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(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(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("" + effect->getName() + ""); + 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* 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 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 + * + * 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 +#include +#include +#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 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 #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* 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& _converter; }; + +template class LabelledComboBoxEnum : public Labelled +{ +public: + LabelledComboBoxEnum( Glib::ustring const &label, + Glib::ustring const &tooltip, + const Util::EnumDataConverter& c, + Glib::ustring const &suffix = "", + Glib::ustring const &icon = "", + bool mnemonic = true) + : Labelled(label, tooltip, new ComboBoxEnum(c), suffix, icon, mnemonic) + { } + + ComboBoxEnum* getCombobox() { + return static_cast< ComboBoxEnum* > (_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 + * Carl Hetherington + * Derek P. Moore + * Bryce Harrington + * + * Copyright (C) 2007 Authors + * Copyright (C) 2004 Authors + * + * Released under GNU GPL. Read the file 'COPYING' for more information. + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + + +#include "ui/widget/point.h" +#include "ui/widget/labelled.h" +#include "ui/widget/scalar.h" +#include + +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(_widget)->pack_start(xwidget, true, true); + static_cast(_widget)->pack_start(ywidget, true, true); + static_cast(_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(_widget)->pack_start(xwidget, true, true); + static_cast(_widget)->pack_start(ywidget, true, true); + static_cast(_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(_widget)->pack_start(xwidget, true, true); + static_cast(_widget)->pack_start(ywidget, true, true); + static_cast(_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 +Point::signal_x_value_changed() +{ + return xwidget.signal_value_changed(); +} +Glib::SignalProxy0 +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 + * Carl Hetherington + * Derek P. Moore + * Bryce Harrington + * + * 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 +#include + +#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 signal_x_value_changed(); + Glib::SignalProxy0 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 + * + * 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 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& c, + Registry& wr, + Inkscape::XML::Node* repr_in, + SPDocument *doc_in) + { + init_parent(key, wr, repr_in, doc_in); + + labelled = new LabelledComboBoxEnum (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* get_active_data() { + return combobox()->get_active_data(); + } + + ComboBoxEnum * combobox() { + if (labelled) { + return labelled->getCombobox(); + } else { + return NULL; + } + } + + LabelledComboBoxEnum * labelled; + sigc::connection _changed_connection; + +protected: + void on_changed() { + if (_wr->isUpdating()) + return; + _wr->setUpdating (true); + + const Util::EnumData* 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 * bulia byak * Bryce W. Harrington * Lauris Kaplinski * Jon Phillips * Ralf Stephan (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 = ≀ _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::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::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 = ≀ - _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 = ≀ - - 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 = ≀ _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 = ≀ - - 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 = ≀ _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 #include +#include + +#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 = ≀ + _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,