diff options
Diffstat (limited to 'third_party/spiro/ppedit')
33 files changed, 6183 insertions, 0 deletions
diff --git a/third_party/spiro/ppedit/Makefile b/third_party/spiro/ppedit/Makefile new file mode 100644 index 0000000..589fdc1 --- /dev/null +++ b/third_party/spiro/ppedit/Makefile @@ -0,0 +1,25 @@ +TARGET = gtk + +ifeq ($(TARGET),gtk) + X3_PLAT = X3_GTK + X3_INCL = `pkg-config --cflags gtk+-2.0` + X3_LIBS = `pkg-config --libs gtk+-2.0` +endif + +ifeq ($(TARGET),carbon) + X3_PLAT = X3_CARBON + X3_LIBS = -framework Carbon +endif + +ifeq ($(TARGET),win32) + X3_PLAT = X3_WIN32 + X3_LIBS = -lgdi32 +endif + +CFLAGS = -g -Wall -D$(X3_PLAT) $(X3_INCL) -I../x3/ +LDFLAGS = -g +LDLIBS = $(X3_LIBS) + +all: ppedit + +ppedit: ppedit.o cornu.o bezctx.o bezctx_x3.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o ../x3/x3$(TARGET).o ../x3/x3common.o diff --git a/third_party/spiro/ppedit/Makefile_gtk1 b/third_party/spiro/ppedit/Makefile_gtk1 new file mode 100644 index 0000000..9523375 --- /dev/null +++ b/third_party/spiro/ppedit/Makefile_gtk1 @@ -0,0 +1,5 @@ +CFLAGS = -Wall -g `gtk-config --cflags` `libart2-config --cflags` +LDLIBS = `gtk-config --libs` `libart2-config --libs` +all: ppedit_gtk1 + +ppedit_gtk1: ppedit_gtk1.o cornu.o bezctx.o bezctx_libart.o bezctx_hittest.o plate.o sexp.o image.o bezctx_ps.o spiro.o diff --git a/third_party/spiro/ppedit/README b/third_party/spiro/ppedit/README new file mode 100644 index 0000000..c99037d --- /dev/null +++ b/third_party/spiro/ppedit/README @@ -0,0 +1,112 @@ +README for ppedit + +Raph Levien +4 May 2007 + +ppedit is my prototype application for editing curves using my +curvature-continuous spirals. While I have used this code to draw many +font outlines, it is very rough around the edges, and is far from a +polished tool. + + +== License and patent grant == + +All code in this package is released under the terms of the GNU GPL, +version 2 or later, at your choice. + +Further, there is a provisional patent application filed for the +underlying curve technology. The following patent grant applies to any +patent which may be issued as a result of that application: + +Whereas, Raph Levien (hereinafter "Inventor") has obtained patent +protection for related technology (hereinafter "Patented Technology"), +Inventor wishes to aid the the GNU free software project in achieving +its goals, and Inventor also wishes to increase public awareness of +Patented Technology, Inventor hereby grants a fully paid up, +nonexclusive, irrevocable, royalty free license to practice the +patents listed below ("the Patents") if and only if practiced in +conjunction with software distributed under the terms of any version +of the GNU General Public License as published by the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111. Inventor +reserves all other rights, including without limitation, licensing for +software not distributed under the GNU General Public License. + +== Building == + +The main build supported right now is the Gtk2/cairo one. There's also +a Mac build and a Gtk1 one, but those aren't guaranteed to work. + +1. Make sure you've got ../x3/ in a directory parallel to ppedit. If + you've unpacked from a tarball, this should be the case already. + From darcs, use: darcs get http://levien.com/garden/x3 + +2. make + +3. The binary is ppedit + +== Using == + +The numeric keys 1-6 select the mode. 1 is selection, 2-6 select +different point modes: + +2: Add G4-continuous curve point +3: Add corner point +4: Add left-facing one-way point +5: Add right-facing one-way point +6: Add G2-continuous curve point + +Note: Dave Crossland has a set of alternate keybindings which are +probably faster. + +== Plate files == + +Ctrl-S saves a plate file in a file of the name 'plate'. Additionally, +a plate file can be given as a command line argument. The file uses +simple S-expressions, with a one-character code for each point, then +the X and Y coordinates - 0,0 is top left. + +Here's the cap U from Inconsolata, for example: + +(plate + (v 68 78) + (v 159 78) + (o 158 92) + ([ 148 115) + (] 148 552) + (o 298 744) + ([ 459 549) + (v 459 78) + (v 536 78) + (] 536 547) + (o 295 813) + ([ 68 551) + (z) +) + +v: corner +o: g4 +c: g2 +[: left-facing one-way +]: right-facing one-way + +== Conversion to PostScript == + +Ctrl-P converts to PostScript, saving '/tmp/foo.ps'. Other utilities +can convert that representation into FontForge, and also optimize the +Beziers. + +== Stability == + +The spline solver in this release is _not_ numerically robust. When +you start drawing random points, you'll quickly run into divergence. +However, "sensible" plates based on real fonts usually converge. Some +tips: + +1. Huge changes of angle are likely to diverge. + +2. For the first two or three points, G4 points are likelier to + converge than G2's. For longer segments, G2 is more likely. + +3. Start on a curve point. + +A more numerically robust approach is in the works. diff --git a/third_party/spiro/ppedit/bezctx.c b/third_party/spiro/ppedit/bezctx.c new file mode 100644 index 0000000..722f5db --- /dev/null +++ b/third_party/spiro/ppedit/bezctx.c @@ -0,0 +1,48 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include "bezctx.h" + +void bezctx_moveto(bezctx *bc, double x, double y, int is_open) +{ + bc->moveto(bc, x, y, is_open); +} + +void bezctx_lineto(bezctx *bc, double x, double y) +{ + bc->lineto(bc, x, y); +} + +void bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2) +{ + bc->quadto(bc, x1, y1, x2, y2); +} + +void bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bc->curveto(bc, x1, y1, x2, y2, x3, y3); +} + +void bezctx_mark_knot(bezctx *bc, int knot_idx) +{ + if (bc->mark_knot) + bc->mark_knot(bc, knot_idx); +} diff --git a/third_party/spiro/ppedit/bezctx.h b/third_party/spiro/ppedit/bezctx.h new file mode 100644 index 0000000..057312e --- /dev/null +++ b/third_party/spiro/ppedit/bezctx.h @@ -0,0 +1,10 @@ +#include "bezctx_intf.h" + +struct _bezctx { + void (*moveto)(bezctx *bc, double x, double y, int is_open); + void (*lineto)(bezctx *bc, double x, double y); + void (*quadto)(bezctx *bc, double x1, double y1, double x2, double y2); + void (*curveto)(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3); + void (*mark_knot)(bezctx *bc, int knot_idx); +}; diff --git a/third_party/spiro/ppedit/bezctx_hittest.c b/third_party/spiro/ppedit/bezctx_hittest.c new file mode 100644 index 0000000..5a3d549 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_hittest.c @@ -0,0 +1,261 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_hittest.h" +#include <math.h> + +typedef struct { + bezctx base; + double x, y; + + double x0, y0; + int knot_idx; + + int knot_idx_min; + double r_min; +} bezctx_hittest; + +static void +bezctx_hittest_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_hittest *bc = (bezctx_hittest *)z; + + bc->x0 = x; + bc->y0 = y; +} + +static void +bezctx_hittest_lineto(bezctx *z, double x, double y) { + bezctx_hittest *bc = (bezctx_hittest *)z; + double x0 = bc->x0; + double y0 = bc->y0; + double dx = x - x0; + double dy = y - y0; + double dotp = (bc->x - x0) * dx + (bc->y - y0) * dy; + double lin_dotp = dx * dx + dy * dy; + double r_min, r; + + r = hypot(bc->x - x0, bc->y - y0); + r_min = r; + r = hypot(bc->x - x, bc->y - y); + if (r < r_min) r_min = r; + + if (dotp >= 0 && dotp <= lin_dotp) { + double norm = (bc->x - x0) * dy - (bc->y - y0) * dx; + r = fabs(norm / sqrt(lin_dotp)); + if (r < r_min) r_min = r; + } + + if (r_min < bc->r_min) { + bc->r_min = r_min; + bc->knot_idx_min = bc->knot_idx; + } + + bc->x0 = x; + bc->y0 = y; +} + +#define cube(x) ((x) * (x) * (x)) + +static double +my_cbrt(double x) +{ + if (x >= 0) + return pow(x, 1.0 / 3.0); + else + return -pow(-x, 1.0 / 3.0); +} + +/** + * Give real roots to eqn c0 + c1 * x + c2 * x^2 + c3 * x^3 == 0. + * Return value is number of roots found. + **/ +static int +solve_cubic(double c0, double c1, double c2, double c3, double root[3]) +{ + double p, q, r, a, b, Q, x0; + + p = c2 / c3; + q = c1 / c3; + r = c0 / c3; + a = (3 * q - p * p) / 3; + b = (2 * cube(p) - 9 * p * q + 27 * r) / 27; + Q = b * b / 4 + cube(a) / 27; + x0 = p / 3; + if (Q > 0) { + double sQ = sqrt(Q); + double t1 = my_cbrt(-b/2 + sQ) + my_cbrt(-b/2 - sQ); + root[0] = t1 - x0; + return 1; + } else if (Q == 0) { + double t1 = my_cbrt(b / 2); + double x1 = t1 - x0; + root[0] = x1; + root[1] = x1; + root[2] = -2 * t1 - x0; + return 3; + } else { + double sQ = sqrt(-Q); + double rho = hypot(-b/2, sQ); + double th = atan2(sQ, -b/2); + double cbrho = my_cbrt(rho); + double c = cos(th / 3); + double s = sin(th / 3); + double sqr3 = sqrt(3); + root[0] = 2 * cbrho * c - x0; + root[1] = -cbrho * (c + sqr3 * s) - x0; + root[2] = -cbrho * (c - sqr3 * s) - x0; + return 3; + } +} + + +static double +dist_to_quadratic(double x, double y, + double x0, double y0, + double x1, double y1, + double x2, double y2) +{ + double u0, u1, t0, t1, t2, c0, c1, c2, c3; + double roots[3]; + int n_roots; + double ts[4]; + int n_ts; + int i; + double minerr = 0; + + u0 = x1 - x0; + u1 = x0 - 2 * x1 + x2; + t0 = x0 - x; + t1 = 2 * u0; + t2 = u1; + c0 = t0 * u0; + c1 = t1 * u0 + t0 * u1; + c2 = t2 * u0 + t1 * u1; + c3 = t2 * u1; + + u0 = y1 - y0; + u1 = y0 - 2 * y1 + y2; + t0 = y0 - y; + t1 = 2 * u0; + t2 = u1; + c0 += t0 * u0; + c1 += t1 * u0 + t0 * u1; + c2 += t2 * u0 + t1 * u1; + c3 += t2 * u1; + + n_roots = solve_cubic(c0, c1, c2, c3, roots); + n_ts = 0; + for (i = 0; i < n_roots; i++) { + double t = roots[i]; + if (t > 0 && t < 1) + ts[n_ts++] = t; + } + if (n_ts < n_roots) { + ts[n_ts++] = 0; + ts[n_ts++] = 1; + } + for (i = 0; i < n_ts; i++) { + double t = ts[i]; + double xa = x0 * (1 - t) * (1 - t) + 2 * x1 * (1 - t) * t + x2 * t * t; + double ya = y0 * (1 - t) * (1 - t) + 2 * y1 * (1 - t) * t + y2 * t * t; + double err = hypot(xa - x, ya - y); + if (i == 0 || err < minerr) { + minerr = err; + } + } + return minerr; +} + +static void +bezctx_hittest_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double r = dist_to_quadratic(bc->x, bc->y, + bc->x0, bc->y0, x1, y1, x2, y2); + + if (r < bc->r_min) { + bc->r_min = r; + bc->knot_idx_min = bc->knot_idx; + } + bc->x0 = x2; + bc->y0 = y2; +} + +static void +bezctx_hittest_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double x0 = bc->x0; + double y0 = bc->y0; + int n_subdiv = 32; + int i; + double xq2, yq2; + + /* todo: subdivide to quadratics rather than lines */ + for (i = 0; i < n_subdiv; i++) { + double t = (1. / n_subdiv) * (i + 1); + double mt = 1 - t; + + xq2 = x0 * mt * mt * mt + 3 * x1 * mt * t * t + 3 * x2 * mt * mt * t + + x3 * t * t * t; + yq2 = y0 * mt * mt * mt + 3 * y1 * mt * t * t + 3 * y2 * mt * mt * t + + y3 * t * t * t; + bezctx_hittest_lineto(z, xq2, yq2); + } +} + +static void +bezctx_hittest_mark_knot(bezctx *z, int knot_idx) { + bezctx_hittest *bc = (bezctx_hittest *)z; + + bc->knot_idx = knot_idx; +} + +bezctx * +new_bezctx_hittest(double x, double y) { + bezctx_hittest *result = znew(bezctx_hittest, 1); + + result->base.moveto = bezctx_hittest_moveto; + result->base.lineto = bezctx_hittest_lineto; + result->base.quadto = bezctx_hittest_quadto; + result->base.curveto = bezctx_hittest_curveto; + result->base.mark_knot = bezctx_hittest_mark_knot; + result->x = x; + result->y = y; + result->knot_idx_min = -1; + result->r_min = 1e12; + return &result->base; +} + +double +bezctx_hittest_report(bezctx *z, int *p_knot_idx) +{ + bezctx_hittest *bc = (bezctx_hittest *)z; + double r_min = bc->r_min; + + if (p_knot_idx) + *p_knot_idx = bc->knot_idx_min; + + zfree(z); + return r_min; +} diff --git a/third_party/spiro/ppedit/bezctx_hittest.h b/third_party/spiro/ppedit/bezctx_hittest.h new file mode 100644 index 0000000..79949ae --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_hittest.h @@ -0,0 +1,5 @@ +bezctx * +new_bezctx_hittest(double x, double y); + +double +bezctx_hittest_report(bezctx *z, int *p_knot_idx); diff --git a/third_party/spiro/ppedit/bezctx_intf.h b/third_party/spiro/ppedit/bezctx_intf.h new file mode 100644 index 0000000..a47b8ef --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_intf.h @@ -0,0 +1,20 @@ +typedef struct _bezctx bezctx; + +bezctx * +new_bezctx(void); + +void +bezctx_moveto(bezctx *bc, double x, double y, int is_open); + +void +bezctx_lineto(bezctx *bc, double x, double y); + +void +bezctx_quadto(bezctx *bc, double x1, double y1, double x2, double y2); + +void +bezctx_curveto(bezctx *bc, double x1, double y1, double x2, double y2, + double x3, double y3); + +void +bezctx_mark_knot(bezctx *bc, int knot_idx); diff --git a/third_party/spiro/ppedit/bezctx_libart.c b/third_party/spiro/ppedit/bezctx_libart.c new file mode 100644 index 0000000..c6b268f --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_libart.c @@ -0,0 +1,131 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <libart_lgpl/libart.h> +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_libart.h" + +typedef struct { + bezctx base; + int n_bez; + int n_bez_max; + ArtBpath *bez; +} bezctx_libart; + +static void +bezctx_libart_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = is_open ? ART_MOVETO_OPEN : ART_MOVETO; + bp->x3 = x; + bp->y3 = y; +} + +void +bezctx_libart_lineto(bezctx *z, double x, double y) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = ART_LINETO; + bp->x3 = x; + bp->y3 = y; +} + +void +bezctx_libart_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + double x0, y0; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + x0 = bp[-1].x3; + y0 = bp[-1].y3; + bp->code = ART_CURVETO; + bp->x1 = x1 + (1./3) * (x0 - x1); + bp->y1 = y1 + (1./3) * (y0 - y1); + bp->x2 = x1 + (1./3) * (x2 - x1); + bp->y2 = y1 + (1./3) * (y2 - y1); + bp->x3 = x2; + bp->y3 = y2; +} + +void +bezctx_libart_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *bp; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bp = &bc->bez[bc->n_bez++]; + bp->code = ART_CURVETO; + bp->x1 = x1; + bp->y1 = y1; + bp->x2 = x2; + bp->y2 = y2; + bp->x3 = x3; + bp->y3 = y3; +} + +ArtBpath * +bezctx_to_bpath(bezctx *z) { + bezctx_libart *bc = (bezctx_libart *)z; + ArtBpath *result; + + if (bc->n_bez == bc->n_bez_max) { + bc->bez = zrenew(ArtBpath, bc->bez, bc->n_bez_max <<= 1); + } + bc->bez[bc->n_bez].code = ART_END; + result = bc->bez; + zfree(bc); + return result; +} + +bezctx * +new_bezctx_libart(void) { + bezctx_libart *result = znew(bezctx_libart, 1); + + result->base.moveto = bezctx_libart_moveto; + result->base.lineto = bezctx_libart_lineto; + result->base.quadto = bezctx_libart_quadto; + result->base.curveto = bezctx_libart_curveto; + result->base.mark_knot = NULL; + result->n_bez = 0; + result->n_bez_max = 4; + result->bez = znew(ArtBpath, result->n_bez_max); + return &result->base; +} + diff --git a/third_party/spiro/ppedit/bezctx_libart.h b/third_party/spiro/ppedit/bezctx_libart.h new file mode 100644 index 0000000..c831b98 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_libart.h @@ -0,0 +1,5 @@ + +bezctx *new_bezctx_libart(void); + +ArtBpath * +bezctx_to_bpath(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_ps.c b/third_party/spiro/ppedit/bezctx_ps.c new file mode 100644 index 0000000..05a38ae --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_ps.c @@ -0,0 +1,116 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <stdio.h> + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_ps.h" + +typedef struct { + bezctx base; + int is_open; + double x, y; + FILE *f; +} bezctx_ps; + +const char *ps_prolog = "%!PS\n" +"/m { moveto } bind def\n" +"/l { lineto } bind def\n" +"/c { curveto } bind def\n" +"/z { closepath } bind def\n" +"1 -1 scale\n" +"0 -792 translate\n"; + +const char *ps_postlog = "stroke\n" +"showpage\n"; + +static void +bezctx_ps_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_ps *bc = (bezctx_ps *)z; + + if (!bc->is_open) fprintf(bc->f, "z\n"); + fprintf(bc->f, "%g %g m\n", x, y); + bc->is_open = is_open; + bc->x = x; + bc->y = y; +} + +void +bezctx_ps_lineto(bezctx *z, double x, double y) { + bezctx_ps *bc = (bezctx_ps *)z; + + fprintf(bc->f, "%g %g l\n", x, y); + bc->x = x; + bc->y = y; +} + +void +bezctx_ps_quadto(bezctx *z, double xm, double ym, double x3, double y3) +{ + bezctx_ps *bc = (bezctx_ps *)z; + double x0, y0; + double x1, y1; + double x2, y2; + + x0 = bc->x; + y0 = bc->y; + x1 = xm + (1./3) * (x0 - xm); + y1 = ym + (1./3) * (y0 - ym); + x2 = xm + (1./3) * (x3 - xm); + y2 = ym + (1./3) * (y3 - ym); + fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); + bc->x = x3; + bc->y = y3; +} + +void +bezctx_ps_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_ps *bc = (bezctx_ps *)z; + + fprintf(bc->f, "%g %g %g %g %g %g c\n", x1, y1, x2, y2, x3, y3); + bc->x = x3; + bc->y = y3; +} + +bezctx * +new_bezctx_ps(FILE *f) { + bezctx_ps *result = znew(bezctx_ps, 1); + + result->base.moveto = bezctx_ps_moveto; + result->base.lineto = bezctx_ps_lineto; + result->base.quadto = bezctx_ps_quadto; + result->base.curveto = bezctx_ps_curveto; + result->base.mark_knot = NULL; + result->is_open = 1; + result->f = f; + return &result->base; +} + +void +bezctx_ps_close(bezctx *z) +{ + bezctx_ps *bc = (bezctx_ps *)z; + + if (!bc->is_open) fprintf(bc->f, "z\n"); + zfree(bc); +} diff --git a/third_party/spiro/ppedit/bezctx_ps.h b/third_party/spiro/ppedit/bezctx_ps.h new file mode 100644 index 0000000..5190fd7 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_ps.h @@ -0,0 +1,8 @@ + +const char *ps_prolog; +const char *ps_postlog; + +bezctx *new_bezctx_ps(FILE *f); + +void +bezctx_ps_close(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_quartz.c b/third_party/spiro/ppedit/bezctx_quartz.c new file mode 100644 index 0000000..f04e0bc --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_quartz.c @@ -0,0 +1,78 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <Carbon/Carbon.h> + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_quartz.h" + + +typedef struct { + bezctx base; + CGMutablePathRef pathref; + int is_open; +} bezctx_quartz; + +static void +bezctx_quartz_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_quartz *bc = (bezctx_quartz *)z; + if (!bc->is_open) CGPathCloseSubpath(bc->pathref); + CGPathMoveToPoint(bc->pathref, NULL, x, y); + bc->is_open = is_open; +} + +static void +bezctx_quartz_lineto(bezctx *z, double x, double y) { + bezctx_quartz *bc = (bezctx_quartz *)z; + CGPathAddLineToPoint(bc->pathref, NULL, x, y); +} + +static void +bezctx_quartz_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_quartz *bc = (bezctx_quartz *)z; + CGPathAddQuadCurveToPoint(bc->pathref, NULL, x1, y1, x2, y2); +} + +bezctx * +new_bezctx_quartz(void) { + bezctx_quartz *result = znew(bezctx_quartz, 1); + + result->base.moveto = bezctx_quartz_moveto; + result->base.lineto = bezctx_quartz_lineto; + result->base.quadto = bezctx_quartz_quadto; + result->base.mark_knot = NULL; + result->pathref = CGPathCreateMutable(); + result->is_open = 1; + return &result->base; +} + + +CGMutablePathRef +bezctx_to_quartz(bezctx *z) +{ + bezctx_quartz *bc = (bezctx_quartz *)z; + CGMutablePathRef result = bc->pathref; + + if (!bc->is_open) CGPathCloseSubpath(result); + zfree(bc); + return result; +} diff --git a/third_party/spiro/ppedit/bezctx_quartz.h b/third_party/spiro/ppedit/bezctx_quartz.h new file mode 100644 index 0000000..e234c08 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_quartz.h @@ -0,0 +1,4 @@ +bezctx *new_bezctx_quartz(void); + +CGMutablePathRef +bezctx_to_quartz(bezctx *bc); diff --git a/third_party/spiro/ppedit/bezctx_x3.c b/third_party/spiro/ppedit/bezctx_x3.c new file mode 100644 index 0000000..9278910 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_x3.c @@ -0,0 +1,96 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <x3.h> +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_x3.h" + +typedef struct { + bezctx base; + x3dc *dc; + int is_open; +} bezctx_x3; + +static void +bezctx_x3_moveto(bezctx *z, double x, double y, int is_open) { + bezctx_x3 *bc = (bezctx_x3 *)z; + + if (!bc->is_open) x3closepath(bc->dc); + x3moveto(bc->dc, x, y); + bc->is_open = is_open; +} + +void +bezctx_x3_lineto(bezctx *z, double x, double y) { + bezctx_x3 *bc = (bezctx_x3 *)z; + + x3lineto(bc->dc, x, y); +} + +void +bezctx_x3_quadto(bezctx *z, double x1, double y1, double x2, double y2) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + double x0, y0; + + x3getcurrentpoint(bc->dc, &x0, &y0); + x3curveto(bc->dc, + x1 + (1./3) * (x0 - x1), + y1 + (1./3) * (y0 - y1), + x1 + (1./3) * (x2 - x1), + y1 + (1./3) * (y2 - y1), + x2, + y2); +} + +void +bezctx_x3_curveto(bezctx *z, double x1, double y1, double x2, double y2, + double x3, double y3) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + + x3curveto(bc->dc, x1, y1, x2, y2, x3, y3); +} + +void +bezctx_x3_finish(bezctx *z) +{ + bezctx_x3 *bc = (bezctx_x3 *)z; + + if (!bc->is_open) + x3closepath(bc->dc); + + zfree(bc); +} + +bezctx * +new_bezctx_x3(x3dc *dc) { + bezctx_x3 *result = znew(bezctx_x3, 1); + + result->base.moveto = bezctx_x3_moveto; + result->base.lineto = bezctx_x3_lineto; + result->base.quadto = bezctx_x3_quadto; + result->base.curveto = bezctx_x3_curveto; + result->base.mark_knot = NULL; + result->dc = dc; + result->is_open = 1; + return &result->base; +} diff --git a/third_party/spiro/ppedit/bezctx_x3.h b/third_party/spiro/ppedit/bezctx_x3.h new file mode 100644 index 0000000..c7b1bd4 --- /dev/null +++ b/third_party/spiro/ppedit/bezctx_x3.h @@ -0,0 +1,3 @@ + +bezctx *new_bezctx_x3(x3dc *dc); +void bezctx_x3_finish(bezctx *z); diff --git a/third_party/spiro/ppedit/carbon_main.c b/third_party/spiro/ppedit/carbon_main.c new file mode 100644 index 0000000..5fbd83f --- /dev/null +++ b/third_party/spiro/ppedit/carbon_main.c @@ -0,0 +1,182 @@ +/*
+ppedit - A pattern plate editor for Spiro splines.
+Copyright (C) 2007 Raph Levien
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301, USA.
+
+*/
+#include <Carbon/Carbon.h>
+
+#include "bezctx.h"
+#include "bezctx_quartz.h"
+#include "plate.h"
+#include "pe_view.h"
+
+#define kCommandToggleCorner 'togC'
+
+typedef struct {
+ WindowRef window_ref;
+ plate *p;
+
+ HIViewRef view;
+} plate_edit;
+
+int n_iter = 10;
+
+pascal OSStatus my_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ WindowRef window = pe->window_ref;
+ UInt32 klass = GetEventClass(theEvent);
+ UInt32 kind = GetEventKind(theEvent);
+ OSStatus result = eventNotHandledErr;
+ OSStatus err;
+ Point where;
+ Rect bounds;
+
+ switch (klass) {
+ case kEventClassMouse:
+ switch (kind) {
+ case kEventMouseDown:
+ err = GetEventParameter(theEvent, kEventParamMouseLocation,
+ typeQDPoint, NULL, sizeof(where), NULL, &where);
+ printf("mouse down %d %d\n", where.h, where.v);
+ break;
+ }
+ break;
+ case kEventClassWindow:
+ switch (kind) {
+ case kEventWindowDrawContent:
+ printf("draw content\n");
+ result = noErr;
+ break;
+ case kEventWindowClickContentRgn:
+ printf("click_content region\n");
+ break;
+ case kEventWindowHandleContentClick:
+ err = GetEventParameter(theEvent, kEventParamMouseLocation,
+ typeQDPoint, NULL, sizeof(where), NULL, &where);
+ GetWindowBounds(window, kWindowContentRgn, &bounds);
+ printf("content click %d, %d; %d, %d\n", where.h, where.v,
+ where.h - bounds.left, where.v - bounds.top);
+ break;
+ }
+ }
+ return result;
+}
+
+pascal OSStatus app_handler(EventHandlerCallRef nextHandler, EventRef theEvent, void *data)
+{
+ plate_edit *pe = (plate_edit *)data;
+ HICommand hiCommand;
+ OSStatus result = eventNotHandledErr;
+ GetEventParameter(theEvent, kEventParamDirectObject, typeHICommand,NULL,
+ sizeof(HICommand),NULL,&hiCommand);
+ unsigned int c = hiCommand.commandID;
+ MenuRef menu;
+ MenuItemIndex ix;
+
+ printf("app_handler %c%c%c%c\n",
+ (c >> 24) & 255, (c >> 16) & 255, (c >> 8) & 255, c & 255);
+ switch (c) {
+ case kHICommandUndo:
+ GetIndMenuItemWithCommandID(NULL, kHICommandUndo, 1, &menu, &ix);
+ SetMenuItemTextWithCFString(menu, ix, CFSTR("Undo disabled"));
+ DisableMenuItem(menu, ix);
+ break;
+ case kCommandToggleCorner:
+ pe_view_toggle_corner(pe->view);
+ break;
+ }
+ return result;
+}
+
+void
+add_pe_view(WindowRef window, plate_edit *pe, plate *p)
+{
+ HIRect rect = {{0, 0}, {32767, 32767}};
+ HIViewRef view;
+
+ pe_view_create(window, &rect, &view);
+ pe_view_set_plate(view, p);
+ HIViewSetVisible(view, true);
+ pe->view = view;
+}
+
+void
+init_window(WindowRef window, plate_edit *pe)
+{
+ EventTypeSpec app_event_types[] = {
+ { kEventClassCommand, kEventProcessCommand }
+ };
+ EventTypeSpec event_types[] = {
+ { kEventClassWindow, kEventWindowDrawContent },
+ { kEventClassWindow, kEventWindowHandleContentClick },
+ { kEventClassMouse, kEventMouseDown }
+ };
+
+ InstallApplicationEventHandler(NewEventHandlerUPP(app_handler),
+ GetEventTypeCount(app_event_types),
+ app_event_types, (void *)pe, NULL);
+ InstallWindowEventHandler(window, NewEventHandlerUPP(my_handler),
+ GetEventTypeCount(event_types),
+ event_types, (void *)pe, NULL);
+
+ add_pe_view(window, pe, pe->p);
+}
+
+int main(int argc, char* argv[])
+{
+ IBNibRef nibRef;
+ WindowRef window;
+ plate_edit pe;
+
+ OSStatus err;
+
+ // Create a Nib reference passing the name of the nib file (without the .nib extension)
+ // CreateNibReference only searches into the application bundle.
+ err = CreateNibReference(CFSTR("main"), &nibRef);
+ require_noerr( err, CantGetNibRef );
+
+ // Once the nib reference is created, set the menu bar. "MainMenu" is the name of the menu bar
+ // object. This name is set in InterfaceBuilder when the nib is created.
+ err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
+ require_noerr( err, CantSetMenuBar );
+
+ // Then create a window. "MainWindow" is the name of the window object. This name is set in
+ // InterfaceBuilder when the nib is created.
+ err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
+ require_noerr( err, CantCreateWindow );
+
+ // We don't need the nib reference anymore.
+ DisposeNibReference(nibRef);
+
+ pe.window_ref = window;
+ pe.p = file_read_plate("/Users/raph/golf/ppedit/g.plate");
+ if (pe.p == NULL)
+ pe.p = new_plate();
+ init_window(window, &pe);
+ // The window was created hidden so show it.
+ ShowWindow( window );
+
+ // Call the event loop
+ RunApplicationEventLoop();
+
+CantCreateWindow:
+CantSetMenuBar:
+CantGetNibRef:
+ return err;
+}
diff --git a/third_party/spiro/ppedit/cornu.c b/third_party/spiro/ppedit/cornu.c new file mode 100644 index 0000000..b0eac68 --- /dev/null +++ b/third_party/spiro/ppedit/cornu.c @@ -0,0 +1,615 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <math.h> +#include <stdlib.h> +#include <stdio.h> +#include "bezctx_intf.h" + +/* The computation of fresnel integrals is adapted from: */ + +/* +Cephes Math Library Release 2.1: December, 1988 +Copyright 1984, 1987, 1988 by Stephen L. Moshier +Direct inquiries to 30 Frost Street, Cambridge, MA 02140 +*/ + +double polevl( double x, double coef[], int N ) +{ +double ans; +int i; +double *p; + +p = coef; +ans = *p++; +i = N; + +do + ans = ans * x + *p++; +while( --i ); + +return( ans ); +} + +/* p1evl() */ +/* N + * Evaluate polynomial when coefficient of x is 1.0. + * Otherwise same as polevl. + */ + +double p1evl( double x, double coef[], int N ) +{ +double ans; +double *p; +int i; + +p = coef; +ans = x + *p++; +i = N-1; + +do + ans = ans * x + *p++; +while( --i ); + +return( ans ); +} + +static double sn[6] = { +-2.99181919401019853726E3, + 7.08840045257738576863E5, +-6.29741486205862506537E7, + 2.54890880573376359104E9, +-4.42979518059697779103E10, + 3.18016297876567817986E11, +}; +static double sd[6] = { +/* 1.00000000000000000000E0,*/ + 2.81376268889994315696E2, + 4.55847810806532581675E4, + 5.17343888770096400730E6, + 4.19320245898111231129E8, + 2.24411795645340920940E10, + 6.07366389490084639049E11, +}; + +static double cn[6] = { +-4.98843114573573548651E-8, + 9.50428062829859605134E-6, +-6.45191435683965050962E-4, + 1.88843319396703850064E-2, +-2.05525900955013891793E-1, + 9.99999999999999998822E-1, +}; +static double cd[7] = { + 3.99982968972495980367E-12, + 9.15439215774657478799E-10, + 1.25001862479598821474E-7, + 1.22262789024179030997E-5, + 8.68029542941784300606E-4, + 4.12142090722199792936E-2, + 1.00000000000000000118E0, +}; +static double fn[10] = { + 4.21543555043677546506E-1, + 1.43407919780758885261E-1, + 1.15220955073585758835E-2, + 3.45017939782574027900E-4, + 4.63613749287867322088E-6, + 3.05568983790257605827E-8, + 1.02304514164907233465E-10, + 1.72010743268161828879E-13, + 1.34283276233062758925E-16, + 3.76329711269987889006E-20, +}; +static double fd[10] = { +/* 1.00000000000000000000E0,*/ + 7.51586398353378947175E-1, + 1.16888925859191382142E-1, + 6.44051526508858611005E-3, + 1.55934409164153020873E-4, + 1.84627567348930545870E-6, + 1.12699224763999035261E-8, + 3.60140029589371370404E-11, + 5.88754533621578410010E-14, + 4.52001434074129701496E-17, + 1.25443237090011264384E-20, +}; +static double gn[11] = { + 5.04442073643383265887E-1, + 1.97102833525523411709E-1, + 1.87648584092575249293E-2, + 6.84079380915393090172E-4, + 1.15138826111884280931E-5, + 9.82852443688422223854E-8, + 4.45344415861750144738E-10, + 1.08268041139020870318E-12, + 1.37555460633261799868E-15, + 8.36354435630677421531E-19, + 1.86958710162783235106E-22, +}; +static double gd[11] = { +/* 1.00000000000000000000E0,*/ + 1.47495759925128324529E0, + 3.37748989120019970451E-1, + 2.53603741420338795122E-2, + 8.14679107184306179049E-4, + 1.27545075667729118702E-5, + 1.04314589657571990585E-7, + 4.60680728146520428211E-10, + 1.10273215066240270757E-12, + 1.38796531259578871258E-15, + 8.39158816283118707363E-19, + 1.86958710162783236342E-22, +}; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +int fresnl( xxa, ssa, cca ) +double xxa, *ssa, *cca; +{ +double f, g, cc, ss, c, s, t, u; +double x, x2; + +x = fabs(xxa); +x2 = x * x; +if( x2 < 2.5625 ) + { + t = x2 * x2; + ss = x * x2 * polevl( t, sn, 5)/p1evl( t, sd, 6 ); + cc = x * polevl( t, cn, 5)/polevl(t, cd, 6 ); + goto done; + } + + + + + +#if 0 +/* Note by RLL: the cutoff here seems low to me; perhaps it should be + eliminated altogether. */ +if( x > 36974.0 ) + { + cc = 0.5; + ss = 0.5; + goto done; + } +#endif + +/* Asymptotic power series auxiliary functions + * for large argument + */ + x2 = x * x; + t = M_PI * x2; + u = 1.0/(t * t); + t = 1.0/t; + f = 1.0 - u * polevl( u, fn, 9)/p1evl(u, fd, 10); + g = t * polevl( u, gn, 10)/p1evl(u, gd, 11); + + t = M_PI_2 * x2; + c = cos(t); + s = sin(t); + t = M_PI * x; + cc = 0.5 + (f * s - g * c)/t; + ss = 0.5 - (f * c + g * s)/t; + +done: +if( xxa < 0.0 ) + { + cc = -cc; + ss = -ss; + } + +*cca = cc; +*ssa = ss; +return(0); +} + +/* + End section adapted from Cephes math library. The following code is + by Raph Levien. +*/ + +void eval_cornu(double t, double *ps, double *pc) +{ + double s, c; + double rspio2 = 0.7978845608028653; /* 1 / sqrt(pi/2) */ + double spio2 = 1.2533141373155; /* sqrt(pi/2) */ + + fresnl(t * rspio2, &s, &c); + *ps = s * spio2; + *pc = c * spio2; +} + +double mod_2pi(double th) { + double u = th * (1 / (2 * M_PI)); + return 2 * M_PI * (u - floor(u + 0.5)); +} + +void fit_cornu_half(double th0, double th1, + double *pt0, double *pt1, + double *pk0, double *pk1) +{ + int i; + const int max_iter = 21; + double t0, t1, t_m; + double tl, tr, t_est; + double s0, c0, s1, c1; + /* This implementation uses bisection, which is simple but almost + certainly not the fastest converging. If time is of the essence, + use something like Newton-Raphson. */ + + if (fabs(th0 + th1) < 1e-6) { + th0 += 1e-6; + th1 += 1e-6; + } + t_est = 0.29112 * (th1 + th0) / sqrt(th1 - th0); + tl = t_est * .9; + tr = t_est * 2; + for (i = 0; i < max_iter; i++) { + double dt; + double chord_th; + + t_m = .5 * (tl + tr); + dt = (th0 + th1) / (4 * t_m); + t0 = t_m - dt; + t1 = t_m + dt; + eval_cornu(t0, &s0, &c0); + eval_cornu(t1, &s1, &c1); + chord_th = atan2(s1 - s0, c1 - c0); + if (mod_2pi(chord_th - t0 * t0 - th0) < 0) + tl = t_m; + else + tr = t_m; + } + *pt0 = t0; + *pt1 = t1; + if (pk0 || pk1) { + double chordlen = hypot(s1 - s0, c1 - c0); + if (pk0) *pk0 = t0 * chordlen; + if (pk1) *pk1 = t1 * chordlen; + } +} + +/* Most of the time, this should give a fairly tight, yet conservative, + (meaning it won't underestimate) approximation of the maximum error + between a Cornu spiral segment and its quadratic Bezier fit. +*/ +double +est_cornu_error(double t0, double t1) +{ + double t, u, est; + + if (t0 < 0 || t1 < 0) { + t0 = -t0; + t1 = -t1; + } + if (t1 < 0) { + fprintf(stderr, "unexpected t1 sign\n"); + } + if (t1 < t0) { + double tmp = t0; + t0 = t1; + t1 = tmp; + } + if (fabs(t0) < 1e-9) { + est = t1 * t1 * t1; + est *= .017256 - .0059 - est * t1; + } else { + t = t1 - t0; + t *= t; + t *= t; + est = t * fabs(t0 + t1 - 1.22084) / (t0 + t1); + u = t0 + t1 + .6; + u = u * u * u; + est *= .014 * (.6 * u + 1); + est += t * (t1 - t0) * .004; + } + return est; +} + +void +affine_pt(const double aff[6], double x, double y, double *px, double *py) { + *px = x * aff[0] + y * aff[2] + aff[4]; + *py = x * aff[1] + y * aff[3] + aff[5]; +} + +void +affine_multiply(double dst[6], const double src1[6], const double src2[6]) +{ + double d0, d1, d2, d3, d4, d5; + d0 = src1[0] * src2[0] + src1[1] * src2[2]; + d1 = src1[0] * src2[1] + src1[1] * src2[3]; + d2 = src1[2] * src2[0] + src1[3] * src2[2]; + d3 = src1[2] * src2[1] + src1[3] * src2[3]; + d4 = src1[4] * src2[0] + src1[5] * src2[2] + src2[4]; + d5 = src1[4] * src2[1] + src1[5] * src2[3] + src2[5]; + dst[0] = d0; + dst[1] = d1; + dst[2] = d2; + dst[3] = d3; + dst[4] = d4; + dst[5] = d5; +} + +void +fit_quadratic(double x0, double y0, double th0, + double x1, double y1, double th1, + double quad[6]) { + double th; + double s0, c0, s1, c1; + double det, s, c; + + th = atan2(y1 - y0, x1 - x0); + s0 = sin(th0 - th); + c0 = cos(th0 - th); + s1 = sin(th - th1); + c1 = cos(th - th1); + det = 1 / (s0 * c1 + s1 * c0); + s = s0 * s1 * det; + c = c0 * s1 * det; + quad[0] = x0; + quad[1] = y0; + quad[2] = x0 + (x1 - x0) * c - (y1 - y0) * s; + quad[3] = y0 + (y1 - y0) * c + (x1 - x0) * s; + quad[4] = x1; + quad[5] = y1; +} + +void +cornu_seg_to_quad(double t0, double t1, const double aff[6], bezctx *bc) +{ + double x0, y0; + double x1, y1; + double th0 = t0 * t0; + double th1 = t1 * t1; + double quad[6]; + double qx1, qy1, qx2, qy2; + + eval_cornu(t0, &y0, &x0); + eval_cornu(t1, &y1, &x1); + fit_quadratic(x0, y0, th0, x1, y1, th1, quad); + affine_pt(aff, quad[2], quad[3], &qx1, &qy1); + affine_pt(aff, quad[4], quad[5], &qx2, &qy2); + bezctx_quadto(bc, qx1, qy1, qx2, qy2); +} + +void +cornu_seg_to_bpath(double t0, double t1, const double aff[6], + bezctx *bc, double tol) +{ + double tm; + + if ((t0 < 0 && t1 > 0) || (t1 < 0 && t0 > 0)) + tm = 0; + else { + if (fabs(t0 * t0 - t1 * t1) < 1.5 && + est_cornu_error(t0, t1) < tol) { + cornu_seg_to_quad(t0, t1, aff, bc); + return; + } +#if 0 + if (fabs(t0 - t1) < 1e-6) { + printf("DIVERGENCE!\007\n"); + return; + + } +#endif + tm = (t0 + t1) * .5; + } + + cornu_seg_to_bpath(t0, tm, aff, bc, tol); + cornu_seg_to_bpath(tm, t1, aff, bc, tol); +} + +void +cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n, + bezctx *bc, double tol, int closed, int kt0, int n_kt) +{ + int i; + + for (i = 0; i < n - 1 + closed; i++) { + double x0 = xs[i], y0 = ys[i]; + int ip1 = (i + 1) % n; + double x1 = xs[ip1], y1 = ys[ip1]; + double th = atan2(y1 - y0, x1 - x0); + double th0 = mod_2pi(ths[i] - th); + double th1 = mod_2pi(th - ths[ip1]); + double t0, t1; + double s0, c0, s1, c1; + double chord_th, chordlen, rot, scale; + double aff[6], aff2[6]; + double flip = -1; + + th1 += 1e-6; + if (th1 < th0) { + double tmp = th0; + th0 = th1; + th1 = tmp; + flip = 1; + } + fit_cornu_half(th0, th1, &t0, &t1, NULL, NULL); + if (flip == 1) { + double tmp = t0; + t0 = t1; + t1 = tmp; + } + eval_cornu(t0, &s0, &c0); + s0 *= flip; + eval_cornu(t1, &s1, &c1); + s1 *= flip; + chord_th = atan2(s1 - s0, c1 - c0); + chordlen = hypot(s1 - s0, c1 - c0); + rot = th - chord_th; + scale = hypot(y1 - y0, x1 - x0) / chordlen; + aff[0] = 1; + aff[1] = 0; + aff[2] = 0; + aff[3] = flip; + aff[4] = -c0; + aff[5] = -s0; + aff2[0] = scale * cos(rot); + aff2[1] = scale * sin(rot); + aff2[2] = -aff2[1]; + aff2[3] = aff2[0]; + aff2[4] = x0; + aff2[5] = y0; + affine_multiply(aff, aff, aff2); + bezctx_mark_knot(bc, (kt0 + i) % n_kt); + cornu_seg_to_bpath(t0, t1, aff, bc, tol / scale); + } +} + +/* fit arc to pts (0, 0), (x, y), and (1, 0), return th tangent to + arc at (x, y) */ +double fit_arc(double x, double y) { + return atan2(y - 2 * x * y, y * y + x - x * x); +} + +void +local_ths(const double xs[], const double ys[], double ths[], int n, + int closed) +{ + int i; + + for (i = 1 - closed; i < n - 1 + closed; i++) { + int im1 = (i + n - 1) % n; + double x0 = xs[im1]; + double y0 = ys[im1]; + double x1 = xs[i]; + double y1 = ys[i]; + int ip1 = (i + 1) % n; + double x2 = xs[ip1]; + double y2 = ys[ip1]; + double dx = x2 - x0; + double dy = y2 - y0; + double ir2 = dx * dx + dy * dy; + double x = ((x1 - x0) * dx + (y1 - y0) * dy) / ir2; + double y = ((y1 - y0) * dx - (x1 - x0) * dy) / ir2; + if (dx == 0.0 && dy == 0.0) + ths[i] = 0.0; + else + ths[i] = fit_arc(x, y) + atan2(dy, dx); + } +} + +void +endpoint_ths(const double xs[], const double ys[], double ths[], int n) +{ + ths[0] = 2 * atan2(ys[1] - ys[0], xs[1] - xs[0]) - ths[1]; + ths[n - 1] = 2 * atan2(ys[n - 1] - ys[n - 2], xs[n - 1] - xs[n-2]) - ths[n - 2]; +} + +void +tweak_ths(const double xs[], const double ys[], double ths[], int n, + double delt, int closed) +{ + double *dks = (double *)malloc(sizeof(double) * n); + int i; + double first_k0, last_k1; + + for (i = 0; i < n - 1 + closed; i++) { + double x0 = xs[i]; + double y0 = ys[i]; + int ip1 = (i + 1) % n; + double x1 = xs[ip1]; + double y1 = ys[ip1]; + double th, th0, th1; + double t0, t1, k0, k1; + double s0, c0, s1, c1; + double scale; + double flip = -1; + + if (x0 == x1 && y0 == y1) { +#ifdef VERBOSE + printf("Overlapping points (i=%d)\n", i); +#endif + /* Very naughty, casting off the constness like this. */ + ((double*) xs)[i] = x1 = x1 + 1e-6; + } + + th = atan2(y1 - y0, x1 - x0); + th0 = mod_2pi(ths[i] - th); + th1 = mod_2pi(th - ths[ip1]); + + th1 += 1e-6; + if (th1 < th0) { + double tmp = th0; + th0 = th1; + th1 = tmp; + flip = 1; + } + fit_cornu_half(th0, th1, &t0, &t1, &k0, &k1); + if (flip == 1) { + double tmp = t0; + t0 = t1; + t1 = tmp; + + tmp = k0; + k0 = k1; + k1 = tmp; + } + eval_cornu(t0, &s0, &c0); + eval_cornu(t1, &s1, &c1); + scale = 1 / hypot(y1 - y0, x1 - x0); + k0 *= scale; + k1 *= scale; + if (i > 0) dks[i] = k0 - last_k1; + else first_k0 = k0; + last_k1 = k1; + } + if (closed) + dks[0] = first_k0 - last_k1; + for (i = 1 - closed; i < n - 1 + closed; i++) { + int im1 = (i + n - 1) % n; + double x0 = xs[im1]; + double y0 = ys[im1]; + double x1 = xs[i]; + double y1 = ys[i]; + int ip1 = (i + 1) % n; + double x2 = xs[ip1]; + double y2 = ys[ip1]; + double chord1 = hypot(x1 - x0, y1 - y0); + double chord2 = hypot(x2 - x1, y2 - y1); + ths[i] -= delt * dks[i] * chord1 * chord2 / (chord1 + chord2); + } + free(dks); +} + +void test_cornu(void) +{ +#if 0 + int i; + for (i = -10; i < 100; i++) { + double t = 36974 * 1.2533141373155 + i * .1; + double s, c; + eval_cornu(t, &s, &c); + printf("%g %g %g\n", t, s, c); + } +#else + double t0, t1; + fit_cornu_half(0, 1, &t0, &t1, 0, 0); + printf("%g %g\n", t0, t1); +#endif +} diff --git a/third_party/spiro/ppedit/cornu.h b/third_party/spiro/ppedit/cornu.h new file mode 100644 index 0000000..f4a955a --- /dev/null +++ b/third_party/spiro/ppedit/cornu.h @@ -0,0 +1,13 @@ +void +cornu_to_bpath(const double xs[], const double ys[], const double ths[], int n, + bezctx *bc, double tol, int closed, int kt0, int n_kt); + +void +local_ths(const double xs[], const double ys[], double ths[], int n, int closed); + +void +endpoint_ths(const double xs[], const double ys[], double ths[], int n); + +void +tweak_ths(const double xs[], const double ys[], double ths[], int n, + double delt, int closed); diff --git a/third_party/spiro/ppedit/gpl.txt b/third_party/spiro/ppedit/gpl.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/third_party/spiro/ppedit/gpl.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/third_party/spiro/ppedit/image.c b/third_party/spiro/ppedit/image.c new file mode 100644 index 0000000..215d27d --- /dev/null +++ b/third_party/spiro/ppedit/image.c @@ -0,0 +1,141 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <stdio.h> +#include <string.h> +#include "zmisc.h" +#include "image.h" + +/* An image loaded into memory. */ +struct _image { + unsigned char *buf; + int width; + int height; + int rowstride; +}; + +static image * +load_ppm_file(FILE *f, char **reason) +{ + image *result; + char line[256]; + int xs, ys; + int depth; + int n; + + fseek(f, 0, SEEK_SET); + fgets(line, sizeof(line), f); + do { + fgets(line, sizeof(line), f); + } while (line[0] == '#'); + n = sscanf(line, "%d %d", &xs, &ys); + if (n != 2) { + *reason = "Error reading ppmraw size line"; + fclose(f); + return NULL; + } + do { + fgets(line, sizeof(line), f); + } while (line[0] == '#'); + n = sscanf(line, "%d", &depth); + if (n != 1) { + *reason = "Error reading ppmraw depth line"; + fclose(f); + return NULL; + } + result = znew(image, 1); + result->rowstride = 3 * xs; + result->buf = zalloc(ys * result->rowstride); + result->width = xs; + result->height = ys; + fread(result->buf, 1, ys * result->rowstride, f); + fclose(f); + return result; +} + +image * +load_image_file(const char *fn, char **reason) +{ + FILE *f = fopen(fn, "rb"); + unsigned char buf[256]; + int n; + + if (f == NULL) { + *reason = "Error opening file"; + return NULL; + } + n = fread(buf, 1, sizeof(buf), f); + if (n < 4) { + *reason = "Short file"; + fclose(f); + return NULL; + } + if (buf[0] != 'P' || buf[1] != '6') { + *reason = "Unrecognized magic"; + fclose(f); + return NULL; + } + return load_ppm_file(f, reason); +} + +void +free_image(image *im) +{ + zfree(im->buf); + zfree(im); +} + +void +render_image(image *im, const double affine[6], + unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1) +{ + int y; + unsigned char *dest_line = buf; + int src_x0 = x0; + + for (y = y0; y < y1; y++) { + int src_y = y; + + if (src_y >= 0 && src_y < im->height) { + unsigned char *img_line = im->buf + src_y * im->rowstride; + int left_pad = -src_x0; + int img_run, img_off, right_pad; + + if (left_pad > x1 - x0) left_pad = x1 - x0; + if (left_pad > 0) { + memset(dest_line, 255, 3 * left_pad); + } else left_pad = 0; + img_off = src_x0; + if (img_off < 0) img_off = 0; + img_run = x1 - x0 - left_pad; + if (img_run > im->width - img_off) img_run = im->width - img_off; + if (img_run > 0) { + memcpy(dest_line + 3 * left_pad, img_line + 3 * img_off, 3 * img_run); + } else img_run = 0; + right_pad = x1 - x0 - left_pad - img_run; + if (right_pad > 0) { + memset(dest_line + 3 * (left_pad + img_run), 255, 3 * right_pad); + } + } else { + memset(dest_line, 255, rowstride); + } + dest_line += rowstride; + } +} diff --git a/third_party/spiro/ppedit/image.h b/third_party/spiro/ppedit/image.h new file mode 100644 index 0000000..a472405 --- /dev/null +++ b/third_party/spiro/ppedit/image.h @@ -0,0 +1,11 @@ +typedef struct _image image; + +image * +load_image_file(const char *fn, char **reason); + +void +free_image(image *im); + +void +render_image(image *im, const double affine[6], + unsigned char *buf, int rowstride, int x0, int y0, int x1, int y1); diff --git a/third_party/spiro/ppedit/pe_view.c b/third_party/spiro/ppedit/pe_view.c new file mode 100644 index 0000000..9909680 --- /dev/null +++ b/third_party/spiro/ppedit/pe_view.c @@ -0,0 +1,548 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +/* This module implements a Carbon HIView object for a pattern plate + editor. */ + +#include <Carbon/Carbon.h> + +#include "bezctx.h" +#include "bezctx_quartz.h" +#include "plate.h" +#include "pe_view.h" + +#define kPEViewClassID CFSTR("com.levien.ppedit.PEView") +#define kPEViewPrivate 'PE_v' + +typedef struct +{ + HIViewRef view; + int show_knots; + plate *p; +} pe_view_data; + +static OSStatus +pe_view_construct(EventRef inEvent) +{ + OSStatus err; + pe_view_data *data; + + data = (pe_view_data *)malloc(sizeof(pe_view_data)); + require_action(data != NULL, CantMalloc, err = memFullErr); + err = GetEventParameter(inEvent, kEventParamHIObjectInstance, + typeHIObjectRef, NULL, sizeof(HIObjectRef), NULL, + (HIObjectRef *)&data->view); + require_noerr(err, ParameterMissing); + err = SetEventParameter(inEvent, kEventParamHIObjectInstance, + typeVoidPtr, sizeof(pe_view_data *), &data); + + data->p = NULL; + data->show_knots = 1; + + ParameterMissing: + if (err != noErr) + free(data); + + CantMalloc: + return err; +} + +static OSStatus +pe_view_destruct(EventRef inEvent, pe_view_data *inData) +{ + free(inData); + return noErr; +} + +static OSStatus +pe_view_initialize(EventHandlerCallRef inCallRef, EventRef inEvent, + pe_view_data *inData) +{ + OSStatus err; + HIRect bounds; + + err = CallNextEventHandler(inCallRef, inEvent); + require_noerr(err, TroubleInSuperClass); + + err = GetEventParameter(inEvent, 'Boun', typeHIRect, + NULL, sizeof(HIRect), NULL, &bounds); + require_noerr(err, ParameterMissing); + + HIViewSetFrame(inData->view, &bounds); + + ParameterMissing: + TroubleInSuperClass: + return err; +} + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +static void +cgcontext_set_rgba(CGContextRef ctx, unsigned int rgba) +{ + const double norm = 1.0 / 255; + CGContextSetRGBFillColor(ctx, + ((rgba >> 24) & 0xff) * norm, + ((rgba >> 16) & 0xff) * norm, + ((rgba >> 8) & 0xff) * norm, + (rgba & 0xff) * norm); +} + +static void +draw_dot(CGContextRef ctx, double x, double y, double r, + unsigned int rgba) +{ + cgcontext_set_rgba(ctx, rgba); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddArc(path, NULL, x, y, r, 0, 2 * M_PI, false); + CGContextAddPath(ctx, path); + CGPathRelease(path); + CGContextFillPath(ctx); +} + +static void +draw_raw_rect(CGContextRef ctx, double x0, double y0, double x1, double y1, + unsigned int rgba) +{ + HIRect rect; + + cgcontext_set_rgba(ctx, rgba); + rect.origin.x = x0; + rect.origin.y = y0; + rect.size.width = x1 - x0; + rect.size.height = y1 - y0; + CGContextFillRect(ctx, rect); +} + +static void +draw_rect(CGContextRef ctx, double x, double y, double r, + unsigned int rgba) +{ + draw_raw_rect(ctx, x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_plate(CGContextRef ctx, pe_view_data *pe) +{ + plate *p = pe->p; + int i, j; + bezctx *bc; + subpath *sp; + spiro_seg **ss = znew(spiro_seg *, p->n_sp); + + for (i = 0; i < p->n_sp; i++) { + bc = new_bezctx_quartz(); + ss[i] = draw_subpath(&p->sp[i], bc); + CGMutablePathRef path = bezctx_to_quartz(bc); + CGContextAddPath(ctx, path); + CGPathRelease(path); + CGContextStrokePath(ctx); + } + + for (i = 0; i < p->n_sp; i++) { + if (pe->show_knots) { + sp = &p->sp[i]; + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(ctx, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(ctx, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(ctx, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(ctx, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(ctx, kt->x, kt->y, + 2.5, 0x000080ff); + } else { + draw_dot(ctx, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + spiro_free(ss[i]); + } + zfree(ss); +} + +static void +draw_selection(CGContextRef ctx, pe_view_data *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(ctx, rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +static OSStatus +pe_view_draw(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + CGContextRef ctx; + + err = GetEventParameter(inEvent, kEventParamCGContextRef, typeCGContextRef, + NULL, sizeof(CGContextRef), NULL, &ctx); + require_noerr(err, ParameterMissing); + + if (inData->p) { + draw_plate(ctx, inData); + draw_selection(ctx, inData); + } + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_get_data(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + OSType tag; + Ptr ptr; + Size outSize; + + /* Probably could use a bit more error checking here, for type + and size match. Also, just returning a pe_view_data seems a + little hacky. */ + err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration, + NULL, sizeof(OSType), NULL, &tag); + require_noerr(err, ParameterMissing); + + err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr, + NULL, sizeof(Ptr), NULL, &ptr); + + if (tag == kPEViewPrivate) { + *((pe_view_data **)ptr) = inData; + outSize = sizeof(pe_view_data *); + } else + err = errDataNotSupported; + + if (err == noErr) + err = SetEventParameter(inEvent, kEventParamControlDataBufferSize, typeLongInteger, + sizeof(Size), &outSize); + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_set_data(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + Ptr ptr; + OSType tag; + + err = GetEventParameter(inEvent, kEventParamControlDataTag, typeEnumeration, + NULL, sizeof(OSType), NULL, &tag); + require_noerr(err, ParameterMissing); + + err = GetEventParameter(inEvent, kEventParamControlDataBuffer, typePtr, + NULL, sizeof(Ptr), NULL, &ptr); + require_noerr(err, ParameterMissing); + + if (tag == 'Plat') { + inData->p = *(plate **)ptr; + } else + err = errDataNotSupported; + + ParameterMissing: + return err; +} + +static OSStatus +pe_view_hittest(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + HIPoint where; + HIRect bounds; + ControlPartCode part; + + err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, + NULL, sizeof(HIPoint), NULL, &where); + require_noerr(err, ParameterMissing); + + err = HIViewGetBounds(inData->view, &bounds); + require_noerr(err, ParameterMissing); + + if (CGRectContainsPoint(bounds, where)) + part = 1; + else + part = kControlNoPart; + err = SetEventParameter(inEvent, kEventParamControlPart, + typeControlPartCode, sizeof(ControlPartCode), + &part); + printf("hittest %g, %g!\n", where.x, where.y); + + ParameterMissing: + return err; +} + +static void +pe_view_queue_draw(pe_view_data *pe) +{ + HIViewSetNeedsDisplay(pe->view, true); +} + +static int +pe_view_button_press(pe_view_data *pe, double x, double y, press_mod mods) +{ + pe->p->description = NULL; + plate_press(pe->p, x, y, mods); + pe_view_queue_draw(pe); + return 1; +} + +static int +pe_view_motion(pe_view_data *pe, double x, double y) +{ + if (pe->p->motmode == MOTION_MODE_MOVE) + plate_motion_move(pe->p, x, y); + else if (pe->p->motmode == MOTION_MODE_SELECT) + plate_motion_select(pe->p, x, y); + pe_view_queue_draw(pe); + return 1; +} + +static int +pe_view_button_release(pe_view_data *pe) +{ + int need_redraw; + + need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + if (need_redraw) + pe_view_queue_draw(pe); + return 1; +} + +static OSStatus +pe_view_track(EventRef inEvent, pe_view_data *inData) +{ + OSStatus err; + HIPoint where; + MouseTrackingResult mouseStatus; + HIRect bounds; + Rect windBounds; + Point theQDPoint; + + err = GetEventParameter(inEvent, kEventParamMouseLocation, typeHIPoint, + NULL, sizeof(HIPoint), NULL, &where); + require_noerr(err, ParameterMissing); + + err = HIViewGetBounds(inData->view, &bounds); + require_noerr(err, ParameterMissing); + + GetWindowBounds(GetControlOwner(inData->view), kWindowStructureRgn, &windBounds); + +#ifdef VERBOSE + printf("press: %g, %g!\n", where.x, where.y); +#endif + pe_view_button_press(inData, where.x, where.y, 0); + + mouseStatus = kMouseTrackingMouseDown; + while (mouseStatus != kMouseTrackingMouseUp) { + TrackMouseLocation(NULL, &theQDPoint, &mouseStatus); + where.x = theQDPoint.h - windBounds.left; + where.y = theQDPoint.v - windBounds.top; + HIViewConvertPoint(&where, NULL, inData->view); +#ifdef VERBOSE + printf("track %d: %g, %g!\n", mouseStatus, where.x, where.y); +#endif + if (mouseStatus == kMouseTrackingMouseUp) { + pe_view_button_release(inData); + } else { + pe_view_motion(inData, where.x, where.y); + } + } + + ParameterMissing: + return err; +} + +pascal OSStatus +pe_view_handler(EventHandlerCallRef inCallRef, + EventRef inEvent, + void* inUserData ) +{ + OSStatus err = eventNotHandledErr; + UInt32 eventClass = GetEventClass(inEvent); + UInt32 eventKind = GetEventKind(inEvent); + pe_view_data *data = (pe_view_data *)inUserData; + + switch (eventClass) { + case kEventClassHIObject: + switch (eventKind) { + case kEventHIObjectConstruct: + err = pe_view_construct(inEvent); + break; + case kEventHIObjectInitialize: + err = pe_view_initialize(inCallRef, inEvent, data); + break; + case kEventHIObjectDestruct: + err = pe_view_destruct(inEvent, data); + break; + } + break; + case kEventClassControl: + switch (eventKind) { + case kEventControlInitialize: + err = noErr; + break; + case kEventControlDraw: + err = pe_view_draw(inEvent, data); + break; + case kEventControlGetData: + err = pe_view_get_data(inEvent, data); + break; + case kEventControlSetData: + err = pe_view_set_data(inEvent, data); + break; + case kEventControlTrack: + err = pe_view_track(inEvent, data); + break; + case kEventControlHitTest: + err = pe_view_hittest(inEvent, data); + break; + /*...*/ + } + break; + } + return err; +} + +static OSStatus +pe_view_register(void) +{ + OSStatus err = noErr; + static HIObjectClassRef pe_view_ClassRef = NULL; + + if (pe_view_ClassRef == NULL) { + EventTypeSpec eventList[] = { + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectInitialize }, + { kEventClassHIObject, kEventHIObjectDestruct }, + + { kEventClassControl, kEventControlActivate }, + { kEventClassControl, kEventControlDeactivate }, + { kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlHiliteChanged }, + { kEventClassControl, kEventControlHitTest }, + { kEventClassControl, kEventControlInitialize }, + { kEventClassControl, kEventControlGetData }, + { kEventClassControl, kEventControlSetData }, + { kEventClassControl, kEventControlTrack } + }; + err = HIObjectRegisterSubclass(kPEViewClassID, + kHIViewClassID, + NULL, + pe_view_handler, + GetEventTypeCount(eventList), + eventList, + NULL, + &pe_view_ClassRef); + } + return err; +} + +OSStatus pe_view_create( + WindowRef inWindow, + const HIRect* inBounds, + HIViewRef* outView) +{ + OSStatus err; + EventRef event; + + err = pe_view_register(); + require_noerr(err, CantRegister); + + err = CreateEvent(NULL, kEventClassHIObject, kEventHIObjectInitialize, + GetCurrentEventTime(), 0, &event); + require_noerr(err, CantCreateEvent); + + if (inBounds != NULL) { + err = SetEventParameter(event, 'Boun', typeHIRect, sizeof(HIRect), + inBounds); + require_noerr(err, CantSetParameter); + } + + err = HIObjectCreate(kPEViewClassID, event, (HIObjectRef*)outView); + require_noerr(err, CantCreate); + + if (inWindow != NULL) { + HIViewRef root; + err = GetRootControl(inWindow, &root); + require_noerr(err, CantGetRootView); + err = HIViewAddSubview(root, *outView); + } + CantCreate: + CantGetRootView: + CantSetParameter: + CantCreateEvent: + ReleaseEvent(event); + CantRegister: + return err; +} + +void +pe_view_set_plate(HIViewRef view, plate *p) +{ + OSStatus err; + + err = SetControlData(view, 1, 'Plat', 4, &p); +} + +void +pe_view_toggle_corner(HIViewRef view) +{ + OSStatus err; + pe_view_data *pe; + + err = GetControlData(view, 1, kPEViewPrivate, 4, &pe, NULL); + require_noerr(err, CantGetPrivate); + + plate_toggle_corner(pe->p); + pe_view_queue_draw(pe); + + CantGetPrivate: +} diff --git a/third_party/spiro/ppedit/pe_view.h b/third_party/spiro/ppedit/pe_view.h new file mode 100644 index 0000000..8db4fff --- /dev/null +++ b/third_party/spiro/ppedit/pe_view.h @@ -0,0 +1,11 @@ +OSStatus pe_view_create( + WindowRef inWindow, + const HIRect* inBounds, + HIViewRef* outView); + +void +pe_view_set_plate(HIViewRef view, plate *p); + +void +pe_view_toggle_corner(HIViewRef view); + diff --git a/third_party/spiro/ppedit/plate.c b/third_party/spiro/ppedit/plate.c new file mode 100644 index 0000000..023f95f --- /dev/null +++ b/third_party/spiro/ppedit/plate.c @@ -0,0 +1,526 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <string.h> +#include <math.h> +#include <stdio.h> + +#include "zmisc.h" +#include "sexp.h" +#include "bezctx_intf.h" +#include "bezctx_hittest.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" + +/* This is a global while we're playing with the tangent solver. Once we get that + nailed down, it will go away. */ +extern int n_iter; + +/** + * These are functions for editing a Cornu spline ("plate"), intended + * to be somewhat independent of the UI toolkit specifics. + **/ + +plate * +new_plate(void) +{ + plate *p = znew(plate, 1); + + p->n_sp = 0; + p->n_sp_max = 4; + p->sp = znew(subpath, p->n_sp_max); + p->mmode = MOUSE_MODE_ADD_CURVE; + p->last_curve_mmode = p->mmode; + return p; +} + +void +free_plate(plate *p) +{ + int i; + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + zfree(sp->kt); + } + zfree(p->sp); + zfree(p); +} + +plate * +copy_plate(const plate *p) +{ + int i; + plate *n = znew(plate, 1); + + n->n_sp = p->n_sp; + n->n_sp_max = p->n_sp_max; + n->sp = znew(subpath, n->n_sp_max); + for (i = 0; i < n->n_sp; i++) { + subpath *sp = &p->sp[i]; + subpath *nsp = &n->sp[i]; + + nsp->n_kt = sp->n_kt; + nsp->n_kt_max = sp->n_kt_max; + nsp->kt = znew(knot, nsp->n_kt_max); + memcpy(nsp->kt, sp->kt, nsp->n_kt * sizeof(knot)); + nsp->closed = sp->closed; + } + n->mmode = p->mmode; + return n; +} + +void +plate_select_all(plate *p, int selected) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt->flags &= ~KT_SELECTED; + if (selected) + kt->flags |= KT_SELECTED; + } + } +} + +subpath * +plate_find_selected_sp(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) + return sp; + } + } + return NULL; +} + +subpath * +plate_new_sp(plate *p) +{ + subpath *sp; + if (p->n_sp == p->n_sp_max) + p->sp = zrenew(subpath, p->sp, p->n_sp_max <<= 1); + sp = &p->sp[p->n_sp++]; + sp->n_kt = 0; + sp->n_kt_max = 4; + sp->kt = znew(knot, sp->n_kt_max); + sp->closed = 0; + return sp; +} + +static int +try_close_sp(subpath *sp, int ix, int force) +{ + int n_kt = sp->n_kt; + + if (sp->closed) return 0; + if (n_kt < 3) return 0; + if (!force) { + if (ix != 0 && ix != n_kt - 1) return 0; + if (!(sp->kt[n_kt - 1 - ix].flags & KT_SELECTED)) return 0; + } + sp->closed = 1; + return 1; +} + +void +plate_press(plate *p, double x, double y, press_mod mods) +{ + int i, j; + subpath *sp; + knot *kt; + const double srad = 5; + kt_flags new_kt_flags = KT_SELECTED; + + if (p->mmode == MOUSE_MODE_ADD_CORNER) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_OPEN : KT_CORNER; + else if (p->mmode == MOUSE_MODE_ADD_CORNU) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_CORNU; + else if (p->mmode == MOUSE_MODE_ADD_LEFT) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_LEFT; + else if (p->mmode == MOUSE_MODE_ADD_RIGHT) + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_RIGHT; + else + new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_OPEN; + + p->x0 = x; + p->y0 = y; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + kt = &sp->kt[j]; + if (hypot(kt->x - x, kt->y - y) < srad) { + int was_closed = try_close_sp(sp, j, mods & PRESS_MOD_DOUBLE); + if (mods & PRESS_MOD_SHIFT) { + kt->flags ^= KT_SELECTED; + } else if (!(kt->flags & KT_SELECTED)) { + plate_select_all(p, 0); + kt->flags |= KT_SELECTED; + } + p->description = was_closed ? "Close Path" : NULL; + p->motmode = MOTION_MODE_MOVE; + return; + } + } + } + + if (p->mmode == MOUSE_MODE_ADD_RIGHT || p->mmode == MOUSE_MODE_ADD_LEFT) + p->mmode = p->last_curve_mmode; + + +#if 1 + /* test whether the button press was on a curve; if so, insert point */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_hittest(x, y); + int knot_idx; + + sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + if (bezctx_hittest_report(bc, &knot_idx) < srad) { + knot *kt; + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + plate_select_all(p, 0); + kt = &sp->kt[knot_idx + 1]; + memmove(&kt[1], kt, (sp->n_kt - knot_idx - 1) * sizeof(knot)); + sp->n_kt++; + kt->x = x; + kt->y = y; + kt->flags = new_kt_flags; + p->description = "Insert Point"; + p->motmode = MOTION_MODE_MOVE; + return; + } + } +#endif + + if (p->mmode == MOUSE_MODE_SELECT) { + plate_select_all(p, 0); + p->sel_x0 = x; + p->sel_y0 = y; + p->motmode = MOTION_MODE_SELECT; + return; + } + + sp = plate_find_selected_sp(p); + if (sp == NULL || sp->closed) { + sp = plate_new_sp(p); + p->description = p->n_sp > 1 ? "New Subpath" : "New Path"; + } + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + plate_select_all(p, 0); + kt = &sp->kt[sp->n_kt++]; + kt->x = x; + kt->y = y; + kt->flags = new_kt_flags; + if (p->description == NULL) + p->description = "Add Point"; + p->motmode = MOTION_MODE_MOVE; +} + +void +plate_motion_move(plate *p, double x, double y) +{ + int i, j, n = 0; + double dx, dy; + + dx = x - p->x0; + dy = y - p->y0; + p->x0 = x; + p->y0 = y; + + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + kt->x += dx; + kt->y += dy; + n++; + } + } + } + p->description = n == 1 ? "Move Point" : "Move Points"; +} + +void +plate_motion_select(plate *p, double x1, double y1) +{ + int i, j; + double x0 = p->sel_x0; + double y0 = p->sel_y0; + +#ifdef VERBOSE + printf("plate_motion_select %g %g\n", x1, y1); +#endif + p->x0 = x1; + p->y0 = y1; + + if (x0 > x1) { + double tmp = x1; + x1 = x0; + x0 = tmp; + } + if (y0 > y1) { + double tmp = y1; + y1 = y0; + y0 = tmp; + } + + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + kt->flags &= ~KT_SELECTED; + if (kt->x >= x0 && kt->x <= x1 && + kt->y >= y0 && kt->y <= y1) + kt->flags |= KT_SELECTED; + } + } +} + +void plate_unpress(plate *p) +{ + p->motmode = MOTION_MODE_IDLE; +} + +void +plate_toggle_corner(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + if (kt->flags & KT_CORNER) { + kt->flags |= KT_OPEN; + kt->flags &= ~KT_CORNER; + } else { + kt->flags &= ~KT_OPEN; + kt->flags |= KT_CORNER; + } + } + } + } +} + +void +plate_delete_pt(plate *p) +{ + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + + for (j = 0; j < sp->n_kt; j++) { + knot *kt = &sp->kt[j]; + if (kt->flags & KT_SELECTED) { + memmove(kt, &kt[1], (sp->n_kt - j - 1) * sizeof(knot)); + sp->n_kt--; + if (sp->n_kt < 3) sp->closed = 0; + j--; + } + } + } +} + +/* Note: caller is responsible for freeing returned spiro_seg. */ +spiro_seg * +draw_subpath(const subpath *sp, bezctx *bc) +{ + int n = sp->n_kt; + int i; + spiro_cp *path; + spiro_seg *s = NULL; + + if (n > 1) { + path = znew(spiro_cp, n); + + for (i = 0; i < n; i++) { + kt_flags flags = sp->kt[i].flags; + path[i].x = sp->kt[i].x; + path[i].y = sp->kt[i].y; + path[i].ty = !sp->closed && i == 0 ? '{' : + !sp->closed && i == n - 1 ? '}' : + (flags & KT_OPEN) ? 'o' : + (flags & KT_LEFT) ? '[' : + (flags & KT_RIGHT) ? ']' : + (flags & KT_CORNU) ? 'c' : + 'v'; + } + + s = run_spiro(path, n); + spiro_to_bpath(s, n, bc); + + zfree(path); + } + return s; +} + +int +file_write_plate(const char *fn, const plate *p) +{ + FILE *f = fopen(fn, "w"); + int i, j; + int st; + + if (f == NULL) + return -1; + st = fprintf(f, "(plate\n"); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + for (j = 0; j < sp->n_kt; j++) { + kt_flags kf = sp->kt[j].flags; + const char *cmd; + + if (kf & KT_OPEN) cmd = "o"; + else if (kf & KT_CORNER) cmd = "v"; + else if (kf & KT_CORNU) cmd = "c"; + else if (kf & KT_LEFT) cmd = "["; + else if (kf & KT_RIGHT) cmd = "]"; + st = fprintf(f, " (%s %g %g)\n", cmd, sp->kt[j].x, sp->kt[j].y); + if (st < 0) break; + } + if (st < 0) break; + if (sp->closed) { + st = fprintf(f, " (z)\n"); + } + if (st < 0) break; + } + if (st >= 0) + st = fprintf(f, ")\n"); + if (st >= 0) + st = fclose(f); + return st < 0 ? -1 : 0; +} + +static int +file_read_plate_inner(sexp_reader *sr, plate *p) +{ + subpath *sp = NULL; + + sexp_token(sr); + if (sr->singlechar != '(') return -1; + sexp_token(sr); + if (strcmp(sr->tokbuf, "plate")) return -1; + for (;;) { + sexp_token(sr); + if (sr->singlechar == ')') break; + else if (sr->singlechar == '(') { + int cmd; + + sexp_token(sr); + cmd = sr->singlechar; + if (cmd == 'o' || cmd == 'v' || cmd == '[' || cmd == ']' || + cmd == 'c') { + double x, y; + knot *kt; + + sexp_token(sr); + if (!sr->is_double) return -1; + x = sr->d; + sexp_token(sr); + if (!sr->is_double) return -1; + y = sr->d; + sexp_token(sr); + if (sr->singlechar != ')') return -1; + + if (sp == NULL || sp->closed) + sp = plate_new_sp(p); + + if (sp->n_kt == sp->n_kt_max) + sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1); + kt = &sp->kt[sp->n_kt++]; + kt->x = x; + kt->y = y; + switch (cmd) { + case 'o': + kt->flags = KT_OPEN; + break; + case '[': + kt->flags = KT_LEFT; + break; + case ']': + kt->flags = KT_RIGHT; + break; + case 'c': + kt->flags = KT_CORNU; + break; + default: + kt->flags = KT_CORNER; + break; + } + } else if (cmd == 'z') { + if (sp == NULL) return -1; + sp->closed = 1; + sexp_token(sr); + if (sr->singlechar != ')') return -1; + } else + return -1; + } else return -1; + } + return 0; +} + +plate * +file_read_plate(const char *fn) +{ + FILE *f = fopen(fn, "r"); + plate *p; + sexp_reader sr; + + if (f == NULL) + return NULL; + sr.f = f; + p = new_plate(); + if (file_read_plate_inner(&sr, p)) { + free_plate(p); + p = NULL; + } + fclose(f); + p->mmode = MOUSE_MODE_SELECT; + p->motmode = MOTION_MODE_IDLE; + return p; +} diff --git a/third_party/spiro/ppedit/plate.h b/third_party/spiro/ppedit/plate.h new file mode 100644 index 0000000..a4a1d69 --- /dev/null +++ b/third_party/spiro/ppedit/plate.h @@ -0,0 +1,100 @@ +typedef enum { + KT_OPEN = 1, + KT_CORNER = 2, + KT_LEFT = 4, + KT_RIGHT = 8, + KT_CORNU = 16, + KT_SELECTED = 256 +} kt_flags; + +typedef struct { + double x; + double y; + kt_flags flags; +} knot; + +typedef struct { + int n_kt; + int n_kt_max; + knot *kt; + int closed; +} subpath; + +typedef enum { + MOUSE_MODE_SELECT, + MOUSE_MODE_ADD_CURVE, + MOUSE_MODE_ADD_CORNER, + MOUSE_MODE_ADD_CORNU, + MOUSE_MODE_ADD_LEFT, + MOUSE_MODE_ADD_RIGHT +} mouse_mode; + +typedef enum { + MOTION_MODE_IDLE, + MOTION_MODE_SELECT, + MOTION_MODE_MOVE +} motion_mode; + +typedef struct { + double x0, y0; + const char *description; + + int n_sp; + int n_sp_max; + subpath *sp; + mouse_mode mmode; + mouse_mode last_curve_mmode; + motion_mode motmode; + double sel_x0, sel_y0; +} plate; + +typedef enum { + PRESS_MOD_SHIFT = 1, + PRESS_MOD_CTRL = 2, + PRESS_MOD_DOUBLE = 4, + PRESS_MOD_TRIPLE = 8 +} press_mod; + +plate * +new_plate(void); + +void +free_plate(plate *p); + +plate * +copy_plate(const plate *p); + +void +plate_select_all(plate *p, int selected); + +subpath * +plate_find_selected_sp(plate *p); + +subpath * +plate_new_sp(plate *p); + +void +plate_press(plate *p, double x, double y, press_mod mods); + +void +plate_motion_move(plate *p, double x, double y); + +void +plate_motion_select(plate *p, double x, double y); + +void plate_unpress(plate *p); + +void +plate_toggle_corner(plate *p); + +void +plate_delete_pt(plate *p); + +spiro_seg * +draw_subpath(const subpath *sp, bezctx *bc); + +int +file_write_plate(const char *fn, const plate *p); + +plate * +file_read_plate(const char *fn); diff --git a/third_party/spiro/ppedit/ppedit.c b/third_party/spiro/ppedit/ppedit.c new file mode 100644 index 0000000..6583930 --- /dev/null +++ b/third_party/spiro/ppedit/ppedit.c @@ -0,0 +1,603 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ + +#include "x3.h" + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_x3.h" +#include "bezctx_ps.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" +#include "image.h" + +int n_iter = 10; + +typedef struct { + const char *description; + plate *p; +} undo_record; + +typedef struct { + x3widget *view; + const char *description; + plate *p; + int undo_n; + int undo_i; + undo_record undo_buf[16]; + int undo_xn_state; + + x3widget *undo_me; + x3widget *redo_me; + + x3widget *show_knots_me; + x3widget *show_bg_me; + int show_knots; + int show_bg; + + image *bg_image; +} plate_edit; + +#define C1 0.55228 +static void +draw_dot(x3dc *dc, + double x, double y, double r, guint32 rgba) +{ + x3setrgba(dc, rgba); + x3moveto(dc, x + r, y); + x3curveto(dc, x + r, y - C1 * r, x + C1 * r, y - r, x, y - r); + x3curveto(dc, x - C1 * r, y - r, x - r, y - C1 * r, x - r, y); + x3curveto(dc, x - r, y + C1 * r, x - C1 * r, y + r, x, y + r); + x3curveto(dc, x + C1 * r, y + r, x + r, y + C1 * r, x + r, y); + x3fill(dc); +} + +static void +draw_raw_rect(x3dc *dc, + double rx0, double ry0, double rx1, double ry1, guint32 rgba) +{ + x3setrgba(dc, rgba); + x3rectangle(dc, rx0, ry0, rx1 - rx0, ry1 - ry0); + x3fill(dc); +} + +static void +draw_rect(x3dc *dc, + double x, double y, double r, guint32 rgba) +{ + draw_raw_rect(dc, + x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_half(x3dc *dc, + double x, double y, double r, double th, guint32 rgba) +{ + double c = cos(th); + double s = sin(th); + + x3setrgba(dc, rgba); + x3moveto(dc, x + c * r, y + s * r); + x3curveto(dc, + x + c * r + C1 * s * r, + y + s * r - C1 * c * r, + x + s * r + C1 * c * r, + y - c * r + C1 * s * r, + x + s * r, + y - c * r); + x3curveto(dc, + x + s * r - C1 * c * r, + y - c * r - C1 * s * r, + x - c * r + C1 * s * r, + y - s * r - C1 * c * r, + x - c * r, + y - s * r); + x3closepath(dc); + x3fill(dc); +} + +static void +draw_plate(x3dc *dc, + plate_edit *pe) +{ + plate *p = pe->p; + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_x3(dc); + subpath *sp = &p->sp[i]; + spiro_seg *s = draw_subpath(sp, bc); + + bezctx_x3_finish(bc); + x3setrgba(dc, 0x000000ff); + x3setlinewidth(dc, 1.5); + x3stroke(dc); + + for (j = 0; j < sp->n_kt; j++) { + if (pe->show_knots) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(dc, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(dc, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(dc, kt->x, kt->y, + 2.5, 0x000080ff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(dc, kt->x, kt->y, + 3, 0xc000c0ff); + draw_rect(dc, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(dc, kt->x, kt->y, + 2.5, 0x800080ff); + } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) { + double th = 1.5708 + (s ? get_knot_th(s, j) : 0); + if (kf & KT_LEFT) + th += 3.1415926; + if (kf & KT_SELECTED) { + draw_half(dc, kt->x, kt->y, + 4, th, 0x000000ff); + draw_half(dc, + kt->x + sin(th), kt->y - cos(th), + 2, th, 0xffffffff); + } else { + draw_half(dc, kt->x, kt->y, + 3, th, 0x000080ff); + } + } else { + draw_dot(dc, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + free_spiro(s); + } +} + +static void +draw_selection(x3dc *dc, + plate_edit *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(dc, + rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +/* Make sure there's room for at least one more undo record. */ +static void +makeroom_undo(plate_edit *pe) +{ + const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record); + + if (pe->undo_n == undo_max) { + free_plate(pe->undo_buf[0].p); + memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record)); + pe->undo_i--; + pe->undo_n--; + } +} + +static void +set_undo_menuitem(x3widget *me, const char *name, const char *desc) +{ + char str[256]; + + if (desc) { + sprintf(str, "%s %s", name, desc); + } else { + strcpy(str, name); + } +#if 0 + gtk_container_foreach(GTK_CONTAINER(me), + (GtkCallback)gtk_label_set_text, + str); +#endif + x3setactive(me, desc != NULL); +} + +static void +set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc) +{ + set_undo_menuitem(pe->undo_me, "Undo", undo_desc); + set_undo_menuitem(pe->redo_me, "Redo", redo_desc); +} + +static void +begin_undo_xn(plate_edit *pe) +{ + int i; + + if (pe->undo_xn_state != 1) { + for (i = pe->undo_i; i < pe->undo_n; i++) + free_plate(pe->undo_buf[i].p); + pe->undo_n = pe->undo_i; + makeroom_undo(pe); + i = pe->undo_i; + pe->undo_buf[i].description = pe->description; + pe->undo_buf[i].p = copy_plate(pe->p); + pe->undo_n = i + 1; + pe->undo_xn_state = 1; + } +} + +static void +dirty_undo_xn(plate_edit *pe, const char *description) +{ + if (pe->undo_xn_state == 0) { + g_warning("dirty_undo_xn: not in begin_undo_xn state"); + begin_undo_xn(pe); + } + if (description == NULL) + description = pe->p->description; + if (pe->undo_xn_state == 1) { + pe->undo_i++; + pe->undo_xn_state = 2; + set_undo_state(pe, description, NULL); + } + pe->description = description; +} + +static void +begindirty_undo_xn(plate_edit *pe, const char *description) +{ + begin_undo_xn(pe); + dirty_undo_xn(pe, description); +} + +static void +end_undo_xn(plate_edit *pe) +{ + if (pe->undo_xn_state == 0) { + g_warning("end_undo_xn: not in undo xn"); + } + pe->undo_xn_state = 0; +} + +static int +undo(plate_edit *pe) +{ + if (pe->undo_i == 0) + return 0; + + if (pe->undo_i == pe->undo_n) { + makeroom_undo(pe); + pe->undo_buf[pe->undo_i].description = pe->description; + pe->undo_buf[pe->undo_i].p = pe->p; + pe->undo_n++; + } else { + free_plate(pe->p); + } + pe->undo_i--; + pe->description = pe->undo_buf[pe->undo_i].description; + set_undo_state(pe, + pe->undo_i > 0 ? pe->description : NULL, + pe->undo_buf[pe->undo_i + 1].description); + g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n); + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + return 1; +} + +static int +redo(plate_edit *pe) +{ + if (pe->undo_i >= pe->undo_n - 1) + return 0; + free_plate(pe->p); + pe->undo_i++; + set_undo_state(pe, + pe->undo_buf[pe->undo_i].description, + pe->undo_i < pe->undo_n - 1 ? + pe->undo_buf[pe->undo_i + 1].description : NULL); + pe->description = pe->undo_buf[pe->undo_i].description; + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n); + return 1; +} + +typedef struct { + x3viewclient base; + + plate_edit *pe; +} x3vc_ppe; + +static void +ppedit_viewclient_draw(x3viewclient *self, x3dc *dc) +{ + plate_edit *pe = ((x3vc_ppe *)self)->pe; + +#ifdef VERBOSE + printf("ppedit draw\n"); +#endif +#if 1 + x3setrgba(dc, 0xffffffff); +#else + x3setrgba(dc, rand() << 8 | 0xff); +#endif + x3rectangle(dc, dc->x, dc->y, dc->width, dc->height); + x3fill(dc); + + draw_plate(dc, pe); + draw_selection(dc, pe); +} + +static void +ppedit_viewclient_mouse(x3viewclient *self, + int button, int mods, + double x, double y) +{ + x3vc_ppe *z = (x3vc_ppe *)self; + plate_edit *pe = z->pe; + plate *p = pe->p; + +#ifdef VERBOSE + printf("ppedit mouse: %d %d %g %g\n", button, mods, x, y); +#endif + if (button == 1) { + press_mod pm = 0; + + if (mods & GDK_SHIFT_MASK) pm |= PRESS_MOD_SHIFT; + if (mods & GDK_CONTROL_MASK) pm |= PRESS_MOD_CTRL; + if (mods == GDK_2BUTTON_PRESS) pm |= PRESS_MOD_DOUBLE; + if (mods == GDK_3BUTTON_PRESS) pm |= PRESS_MOD_TRIPLE; + + begin_undo_xn(pe); + p->description = NULL; + plate_press(p, x, y, pm); + if (p->description) dirty_undo_xn(pe, NULL); + x3view_dirty(pe->view); + } else if (button == -1) { + int need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + if (need_redraw) x3view_dirty(pe->view); + } else if (button == 0 && (mods & GDK_BUTTON1_MASK)) { + if (p->motmode == MOTION_MODE_MOVE) { + plate_motion_move(p, x, y); + dirty_undo_xn(pe, NULL); + } else if (p->motmode == MOTION_MODE_SELECT) { + plate_motion_select(p, x, y); + } + x3view_dirty(pe->view); + } +} + +static int +ppedit_viewclient_key(x3viewclient *self, char *keyname, int mods, int key) +{ + x3vc_ppe *z = (x3vc_ppe *)self; + plate_edit *pe = z->pe; + double dx = 0, dy = 0; + int did_something = 0; + + if (!strcmp(keyname, "Left")) + dx = -1; + else if (!strcmp(keyname, "Right")) + dx = 1; + else if (!strcmp(keyname, "Up")) + dy = -1; + else if (!strcmp(keyname, "Down")) + dy = 1; + if (mods & X3_SHIFT_MASK) { + dx *= 10; + dy *= 10; + } else if (mods & X3_CONTROL_MASK) { + dx *= .1; + dy *= .1; + } + if (dx != 0 || dy != 0) { + begindirty_undo_xn(pe, "Keyboard move"); + plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy); + end_undo_xn(pe); + did_something = TRUE; + } + if (did_something) { + x3view_dirty(pe->view); + return 1; + } else + return 0; +} + +x3viewclient *ppedit_viewclient(plate_edit *pe) +{ + x3vc_ppe *result = (x3vc_ppe *)malloc(sizeof(x3vc_ppe)); + + x3viewclient_init(&result->base); + result->base.draw = ppedit_viewclient_draw; + result->base.mouse = ppedit_viewclient_mouse; + result->base.key = ppedit_viewclient_key; + result->pe = pe; + + return &result->base; +} + +int +print_func(plate_edit *pe) +{ + plate *p = pe->p; + int i; + FILE *f = fopen("/tmp/foo.ps", "w"); + bezctx *bc = new_bezctx_ps(f); + + fputs(ps_prolog, f); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + } + bezctx_ps_close(bc); + fputs(ps_postlog, f); + fclose(f); + return TRUE; +} + +int +ppedit_callback(x3widget *w, void *data, + char *cmd, char *what, char *arg, void *more) +{ + plate_edit *pe = (plate_edit *)data; + printf("my callback: cmd=\"%s\", what=\"%s\", arg=\"%s\"\n", + cmd, what ? what : "(null)", arg ? arg : "(null)"); + + if (!strcmp(cmd, "quit")) { + gtk_main_quit(); + } else if (!strcmp(cmd, "save")) { + file_write_plate("plate", pe->p); + } else if (!strcmp(cmd, "khid")) { + pe->show_knots = !pe->show_knots; + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "bghd")) { + pe->show_bg = !pe->show_bg; + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "prin")) { + return print_func(pe); + } else if (!strcmp(cmd, "undo")) { + undo(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "redo")) { + redo(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "togc")) { + begindirty_undo_xn(pe, "Toggle Corner"); + plate_toggle_corner(pe->p); + end_undo_xn(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "delp")) { + begindirty_undo_xn(pe, "Delete Point"); + plate_delete_pt(pe->p); + end_undo_xn(pe); + x3view_dirty(pe->view); + } else if (!strcmp(cmd, "mods")) { + pe->p->mmode = MOUSE_MODE_SELECT; + } else if (!strcmp(cmd, "modo")) { + pe->p->mmode = MOUSE_MODE_ADD_CURVE; + } else if (!strcmp(cmd, "modv")) { + pe->p->mmode = MOUSE_MODE_ADD_CORNER; + } else if (!strcmp(cmd, "modl")) { + pe->p->mmode = MOUSE_MODE_ADD_LEFT; + } else if (!strcmp(cmd, "modr")) { + pe->p->mmode = MOUSE_MODE_ADD_RIGHT; + } else if (!strcmp(cmd, "modc")) { + pe->p->mmode = MOUSE_MODE_ADD_CORNU; + } + return 1; +} + +static void +create_mainwin(plate_edit *pe) +{ + x3widget *mainwin; + x3widget *view; + x3widget *menu; + void *data = pe; + x3viewflags viewflags = x3view_click | x3view_hover | x3view_key | + x3view_2d | x3view_scroll; + + mainwin = x3window(x3window_main, "ppedit", ppedit_callback, data); + + menu = x3menu(mainwin, "File"); + + x3menuitem(menu, "Save", "save", "<cmd>s"); + x3menuitem(menu, "Print", "prin", "<cmd>p"); + x3menuitem(menu, "Quit", "quit", "<cmd>q"); + + menu = x3menu(mainwin, "Edit"); + + pe->undo_me = x3menuitem(menu, "Undo", "undo", "<cmd>z"); + pe->redo_me = x3menuitem(menu, "Redo", "redo", "<cmd>y"); + + //set_undo_state(p, NULL, NULL); + x3menuitem(menu, "Toggle Corner", "togc", "<cmd>t"); + x3menuitem(menu, "Delete Point", "delp", "<cmd>d"); + x3menuitem(menu, "Selection Mode", "mods", "1"); + x3menuitem(menu, "Add Curve Mode", "modo", "2"); + x3menuitem(menu, "Add Corner Mode", "modv", "3"); + x3menuitem(menu, "Add Left Mode", "modl", "4"); + x3menuitem(menu, "Add Right Mode", "modr", "5"); + x3menuitem(menu, "Add G2 point Mode", "modc", "6"); + + menu = x3menu(mainwin, "View"); + + pe->show_knots_me = x3menuitem(menu, "Hide Knots", "khid", "<cmd>k"); + pe->show_bg_me = x3menuitem(menu, "Hide BG", "bghd", "<cmd>b"); + + pe->view = x3view(mainwin, viewflags, ppedit_viewclient(pe)); + +#ifdef X3_GTK + gtk_window_set_default_size(GTK_WINDOW(mainwin->widget), 512, 512); +#endif + + x3_window_show(mainwin); +} + +int main(int argc, char **argv) +{ + plate_edit pe; + plate *p = NULL; + x3init(&argc, &argv); + char *reason; + + if (argc > 1) + p = file_read_plate(argv[1]); + if (p == NULL) + p = new_plate(); + pe.p = p; + pe.undo_n = 0; + pe.undo_i = 0; + pe.undo_xn_state = 0; + pe.show_knots = 1; + pe.show_bg = 1; + pe.bg_image = load_image_file("/tmp/foo.ppm", &reason); + create_mainwin(&pe); + x3main(); + return 0; +} diff --git a/third_party/spiro/ppedit/ppedit_gtk1.c b/third_party/spiro/ppedit/ppedit_gtk1.c new file mode 100644 index 0000000..b81299e --- /dev/null +++ b/third_party/spiro/ppedit/ppedit_gtk1.c @@ -0,0 +1,930 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <libart_lgpl/libart.h> +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include "zmisc.h" +#include "bezctx.h" +#include "bezctx_libart.h" +#include "bezctx_ps.h" +#include "cornu.h" +#include "spiro.h" +#include "plate.h" +#include "image.h" + +int n_iter = 10; + +typedef struct { + const char *description; + plate *p; +} undo_record; + +typedef struct { + GtkWidget *da; + const char *description; + plate *p; + int undo_n; + int undo_i; + undo_record undo_buf[16]; + int undo_xn_state; + + GtkWidget *undo_me; + GtkWidget *redo_me; + + GtkWidget *show_knots_me; + GtkWidget *show_bg_me; + int show_knots; + int show_bg; + + image *bg_image; +} plate_edit; + +int +quit_func(GtkWidget *widget, gpointer dummy) +{ + gtk_main_quit(); + return TRUE; +} + +#define C1 0.55228 +static void +draw_dot(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, guint32 rgba) +{ + ArtBpath bp[6]; + ArtVpath *vp; + ArtSVP *svp; + + bp[0].code = ART_MOVETO; + bp[0].x3 = x + r; + bp[0].y3 = y; + bp[1].code = ART_CURVETO; + bp[1].x1 = x + r; + bp[1].y1 = y - C1 * r; + bp[1].x2 = x + C1 * r; + bp[1].y2 = y - r; + bp[1].x3 = x; + bp[1].y3 = y - r; + bp[2].code = ART_CURVETO; + bp[2].x1 = x - C1 * r; + bp[2].y1 = y - r; + bp[2].x2 = x - r; + bp[2].y2 = y - C1 * r; + bp[2].x3 = x - r; + bp[2].y3 = y; + bp[3].code = ART_CURVETO; + bp[3].x1 = x - r; + bp[3].y1 = y + C1 * r; + bp[3].x2 = x - C1 * r; + bp[3].y2 = y + r; + bp[3].x3 = x; + bp[3].y3 = y + r; + bp[4].code = ART_CURVETO; + bp[4].x1 = x + C1 * r; + bp[4].y1 = y + r; + bp[4].x2 = x + r; + bp[4].y2 = y + C1 * r; + bp[4].x3 = x + r; + bp[4].y3 = y; + bp[5].code = ART_END; + + vp = art_bez_path_to_vec(bp, 0.25); + svp = art_svp_from_vpath(vp); + art_free(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static void +draw_raw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double rx0, double ry0, double rx1, double ry1, guint32 rgba) +{ + ArtVpath vp[6]; + ArtSVP *svp; + + vp[0].code = ART_MOVETO; + vp[0].x = rx0; + vp[0].y = ry1; + vp[1].code = ART_LINETO; + vp[1].x = rx1; + vp[1].y = ry1; + vp[2].code = ART_LINETO; + vp[2].x = rx1; + vp[2].y = ry0; + vp[3].code = ART_LINETO; + vp[3].x = rx0; + vp[3].y = ry0; + vp[4].code = ART_LINETO; + vp[4].x = rx0; + vp[4].y = ry1; + vp[5].code = ART_END; + + svp = art_svp_from_vpath(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static void +draw_rect(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, guint32 rgba) +{ + draw_raw_rect(buf, x0, y0, x1, y1, rowstride, + x - r, y - r, x + r, y + r, rgba); +} + +static void +draw_half(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + double x, double y, double r, double th, guint32 rgba) +{ + ArtBpath bp[6]; + ArtVpath *vp; + ArtSVP *svp; + double c = cos(th); + double s = sin(th); + + bp[0].code = ART_MOVETO; + bp[0].x3 = x + c * r; + bp[0].y3 = y + s * r; + bp[1].code = ART_CURVETO; + bp[1].x1 = x + c * r + C1 * s * r; + bp[1].y1 = y + s * r - C1 * c * r; + bp[1].x2 = x + s * r + C1 * c * r; + bp[1].y2 = y - c * r + C1 * s * r; + bp[1].x3 = x + s * r; + bp[1].y3 = y - c * r; + bp[2].code = ART_CURVETO; + bp[2].x1 = x + s * r - C1 * c * r; + bp[2].y1 = y - c * r - C1 * s * r; + bp[2].x2 = x - c * r + C1 * s * r; + bp[2].y2 = y - s * r - C1 * c * r; + bp[2].x3 = x - c * r; + bp[2].y3 = y - s * r; + bp[3].code = ART_LINETO; + bp[3].x3 = x + c * r; + bp[3].y3 = y + s * r; + bp[4].code = ART_END; + + vp = art_bez_path_to_vec(bp, 0.25); + svp = art_svp_from_vpath(vp); + art_free(vp); + + art_rgb_svp_alpha(svp, x0, y0, x1, y1, rgba, buf, rowstride, NULL); + art_svp_free(svp); +} + +static ArtVpath * +bezctx_to_vpath(bezctx *bc) +{ + ArtBpath *bp = bezctx_to_bpath(bc); + ArtVpath *vp = art_bez_path_to_vec(bp, .25); + + g_free(bp); + if (vp[0].code == ART_END || vp[1].code == ART_END) { + g_free(vp); + vp = NULL; + } + return vp; +} + +static void +draw_plate(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + plate_edit *pe) +{ + plate *p = pe->p; + int i, j; + + /* find an existing point to select, if any */ + for (i = 0; i < p->n_sp; i++) { + bezctx *bc = new_bezctx_libart(); + subpath *sp = &p->sp[i]; + spiro_seg *s = draw_subpath(sp, bc); + ArtVpath *vp = bezctx_to_vpath(bc); + + if (vp != NULL) { + ArtSVP *svp = art_svp_vpath_stroke(vp, ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_CAP_BUTT, + 1.5, 4.0, 0.25); + + art_free(vp); + art_rgb_svp_alpha(svp, x0, y0, x1, y1, 0x000000ff, buf, rowstride, + NULL); + art_svp_free(svp); + } + + for (j = 0; j < sp->n_kt; j++) { + if (pe->show_knots) { + knot *kt = &sp->kt[j]; + kt_flags kf = kt->flags; + if ((kf & KT_SELECTED) && (kf & KT_OPEN)) { + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0x000000ff); + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0x000000ff); + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNER)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2.5, 0x000080ff); + } else if ((kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, 0xc000c0ff); + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 1.5, 0xffffffff); + } else if (!(kf & KT_SELECTED) && (kf & KT_CORNU)) { + draw_rect(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2.5, 0x800080ff); + } else if ((kf & KT_LEFT) || (kf & KT_RIGHT)) { + double th = 1.5708 + (s ? get_knot_th(s, j) : 0); + if (kf & KT_LEFT) + th += 3.1415926; + if (kf & KT_SELECTED) { + draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 4, th, 0x000000ff); + draw_half(buf, x0, y0, x1, y1, rowstride, + kt->x + sin(th), kt->y - cos(th), + 2, th, 0xffffffff); + } else { + draw_half(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 3, th, 0x000080ff); + } + } else { + draw_dot(buf, x0, y0, x1, y1, rowstride, kt->x, kt->y, + 2, 0x000080ff); + } + } + } + free_spiro(s); + } +} + +static void +draw_selection(art_u8 *buf, int x0, int y0, int x1, int y1, int rowstride, + plate_edit *pe) +{ + plate *p = pe->p; + + if (p->motmode == MOTION_MODE_SELECT) { + double rx0 = p->sel_x0; + double ry0 = p->sel_y0; + double rx1 = p->x0; + double ry1 = p->y0; + if (rx0 > rx1) { + double tmp = rx1; + rx1 = rx0; + rx0 = tmp; + } + if (ry0 > ry1) { + double tmp = ry1; + ry1 = ry0; + ry0 = tmp; + } + if (rx1 > rx0 && ry1 > ry0) + draw_raw_rect(buf, x0, y0, x1, y1, rowstride, + rx0, ry0, rx1, ry1, 0x0000ff20); + } +} + +static void +render_bg_layer(guchar *buf, int rowstride, int x0, int y0, int x1, int y1, + plate_edit *pe) +{ + const double affine[6] = { 1, 0, 0, 1, 0, 0 }; + + if (pe->show_bg && pe->bg_image) + render_image(pe->bg_image, affine, + buf, rowstride, x0, y0, x1, y1); + else + memset(buf, 255, (y1 - y0) * rowstride); +} + +static gint +data_expose (GtkWidget *widget, GdkEventExpose *event, void *data) +{ + plate_edit *pe = (plate_edit *)data; + int x0 = event->area.x; + int y0 = event->area.y; + int width = event->area.width; + int height = event->area.height; + guchar *rgb; + int rowstride = (width * 3 + 3) & -4; + + rgb = g_new (guchar, event->area.height * rowstride); + + render_bg_layer(rgb, rowstride, x0, y0, x0 + width, y0 + height, pe); + + draw_plate(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe); + + draw_selection(rgb, x0, y0, x0 + width, y0 + height, rowstride, pe); + + gdk_draw_rgb_image(widget->window, + widget->style->black_gc, + x0, y0, width, height, + GDK_RGB_DITHER_NONE, rgb, + rowstride); + g_free(rgb); + return FALSE; +} + +/* Make sure there's room for at least one more undo record. */ +static void +makeroom_undo(plate_edit *pe) +{ + const int undo_max = sizeof(pe->undo_buf) / sizeof(undo_record); + + if (pe->undo_n == undo_max) { + free_plate(pe->undo_buf[0].p); + memmove(pe->undo_buf, pe->undo_buf + 1, (undo_max - 1) * sizeof(undo_record)); + pe->undo_i--; + pe->undo_n--; + } +} + +static void +set_undo_menuitem(GtkWidget *me, const char *name, const char *desc) +{ + char str[256]; + + if (desc) { + sprintf(str, "%s %s", name, desc); + } else { + strcpy(str, name); + } + gtk_container_foreach(GTK_CONTAINER(me), + (GtkCallback)gtk_label_set_text, + str); + gtk_widget_set_sensitive(me, desc != NULL); +} + +static void +set_undo_state(plate_edit *pe, const char *undo_desc, const char *redo_desc) +{ + set_undo_menuitem(pe->undo_me, "Undo", undo_desc); + set_undo_menuitem(pe->redo_me, "Redo", redo_desc); +} + +static void +begin_undo_xn(plate_edit *pe) +{ + int i; + + if (pe->undo_xn_state != 1) { + for (i = pe->undo_i; i < pe->undo_n; i++) + free_plate(pe->undo_buf[i].p); + pe->undo_n = pe->undo_i; + makeroom_undo(pe); + i = pe->undo_i; + pe->undo_buf[i].description = pe->description; + pe->undo_buf[i].p = copy_plate(pe->p); + pe->undo_n = i + 1; + pe->undo_xn_state = 1; + } +} + +static void +dirty_undo_xn(plate_edit *pe, const char *description) +{ + if (pe->undo_xn_state == 0) { + g_warning("dirty_undo_xn: not in begin_undo_xn state"); + begin_undo_xn(pe); + } + if (description == NULL) + description = pe->p->description; + if (pe->undo_xn_state == 1) { + pe->undo_i++; + pe->undo_xn_state = 2; + set_undo_state(pe, description, NULL); + } + pe->description = description; +} + +static void +begindirty_undo_xn(plate_edit *pe, const char *description) +{ + begin_undo_xn(pe); + dirty_undo_xn(pe, description); +} + +static void +end_undo_xn(plate_edit *pe) +{ + if (pe->undo_xn_state == 0) { + g_warning("end_undo_xn: not in undo xn"); + } + pe->undo_xn_state = 0; +} + +static int +undo(plate_edit *pe) +{ + if (pe->undo_i == 0) + return 0; + + if (pe->undo_i == pe->undo_n) { + makeroom_undo(pe); + pe->undo_buf[pe->undo_i].description = pe->description; + pe->undo_buf[pe->undo_i].p = pe->p; + pe->undo_n++; + } else { + free_plate(pe->p); + } + pe->undo_i--; + pe->description = pe->undo_buf[pe->undo_i].description; + set_undo_state(pe, + pe->undo_i > 0 ? pe->description : NULL, + pe->undo_buf[pe->undo_i + 1].description); + g_print("undo: %d of %d\n", pe->undo_i, pe->undo_n); + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + return 1; +} + +static int +redo(plate_edit *pe) +{ + if (pe->undo_i >= pe->undo_n - 1) + return 0; + free_plate(pe->p); + pe->undo_i++; + set_undo_state(pe, + pe->undo_buf[pe->undo_i].description, + pe->undo_i < pe->undo_n - 1 ? + pe->undo_buf[pe->undo_i + 1].description : NULL); + pe->description = pe->undo_buf[pe->undo_i].description; + pe->p = copy_plate(pe->undo_buf[pe->undo_i].p); + g_print("redo: %d of %d\n", pe->undo_i, pe->undo_n); + return 1; +} + +static gint +data_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + plate *p = pe->p; + double x, y; + press_mod mods = 0; + +#define noVERBOSE +#ifdef VERBOSE + g_print ("button press %f %f %f %d\n", + event->x, event->y, event->pressure, event->type); + +#endif + x = event->x; + y = event->y; + if (event->state & GDK_SHIFT_MASK) mods |= PRESS_MOD_SHIFT; + if (event->state & GDK_CONTROL_MASK) mods |= PRESS_MOD_CTRL; + if (event->type == GDK_2BUTTON_PRESS) mods |= PRESS_MOD_DOUBLE; + if (event->type == GDK_3BUTTON_PRESS) mods |= PRESS_MOD_TRIPLE; + + begin_undo_xn(pe); + p->description = NULL; + plate_press(p, x, y, mods); + if (p->description) dirty_undo_xn(pe, NULL); + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion_move (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe) +{ + double x, y; + x = event->x; + y = event->y; + + plate_motion_move(pe->p, x, y); + dirty_undo_xn(pe, NULL); + + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion_select (GtkWidget *widget, GdkEventMotion *event, plate_edit *pe) +{ + double x, y; + + x = event->x; + y = event->y; + + plate_motion_select(pe->p, x, y); + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gint +data_motion (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + +#ifdef VERBOSE + g_print ("motion %f %f %f\n", event->x, event->y, event->pressure); + +#endif + if (pe->p->motmode == MOTION_MODE_MOVE) + return data_motion_move(widget, event, pe); + else if (pe->p->motmode == MOTION_MODE_SELECT) + return data_motion_select(widget, event, pe); + return TRUE; +} + +static gint +data_button_release (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + int need_redraw; + + need_redraw = (pe->p->motmode == MOTION_MODE_SELECT); + + plate_unpress(pe->p); + + gtk_widget_queue_draw(widget); + + return TRUE; +} + +static gboolean +key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + int delta = event->state & 4 ? 10 : 1; + int old_n_iter = n_iter; + double dx = 0, dy = 0; + gboolean did_something = FALSE; + + g_print("key press %d %s %d\n", event->keyval, event->string, event->state); + + if (event->keyval == '<') { + did_something = TRUE; + n_iter -= delta; + } else if (event->keyval == '>') { + n_iter += delta; + } + if (n_iter < 0) n_iter = 0; + if (n_iter != old_n_iter) + g_print("n_iter = %d\n", n_iter); + + if (event->keyval == GDK_Left) + dx = -1; + else if (event->keyval == GDK_Right) + dx = 1; + else if (event->keyval == GDK_Up) + dy = -1; + else if (event->keyval == GDK_Down) + dy = 1; + if (event->state & GDK_SHIFT_MASK) { + dx *= 10; + dy *= 10; + } else if (event->state & GDK_CONTROL_MASK) { + dx *= .1; + dy *= .1; + } + if (dx != 0 || dy != 0) { + begindirty_undo_xn(pe, "Keyboard move"); + plate_motion_move(pe->p, pe->p->x0 + dx, pe->p->y0 + dy); + end_undo_xn(pe); + did_something = TRUE; + } + + if (did_something) { + gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key-press-event"); + gtk_widget_queue_draw(widget); + } + + return did_something; +} + +static gint +toggle_corner_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + begindirty_undo_xn(pe, "Toggle Corner"); + plate_toggle_corner(pe->p); + end_undo_xn(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +delete_pt_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + begindirty_undo_xn(pe, "Delete Point"); + plate_delete_pt(pe->p); + end_undo_xn(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +set_select_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_SELECT; + return TRUE; +} + +static gint +set_curve_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CURVE; + pe->p->last_curve_mmode = pe->p->mmode; + return TRUE; +} + +static gint +set_corner_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CORNER; + return TRUE; +} + +static gint +set_left_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_LEFT; + return TRUE; +} + +static gint +set_right_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_RIGHT; + return TRUE; +} + +static gint +set_cornu_mode_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->p->mmode = MOUSE_MODE_ADD_CORNU; + pe->p->last_curve_mmode = pe->p->mmode; + return TRUE; +} + +static gint +undo_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + undo(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +redo_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + redo(pe); + gtk_widget_queue_draw(pe->da); + + return TRUE; +} + +static gint +save_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + file_write_plate("plate", pe->p); + + return TRUE; +} + +static gint +toggle_show_knots_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->show_knots = !pe->show_knots; + gtk_container_foreach(GTK_CONTAINER(pe->show_knots_me), + (GtkCallback)gtk_label_set_text, + pe->show_knots ? "Hide Knots" : "Show Knots"); + gtk_widget_queue_draw(pe->da); + return TRUE; +} + +static gint +toggle_show_bg_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + + pe->show_bg = !pe->show_bg; + gtk_container_foreach(GTK_CONTAINER(pe->show_bg_me), + (GtkCallback)gtk_label_set_text, + pe->show_bg ? "Hide BG" : "Show BG"); + gtk_widget_queue_draw(pe->da); + return TRUE; +} + +static gint +print_func(GtkWidget *widget, gpointer data) +{ + plate_edit *pe = (plate_edit *)data; + plate *p = pe->p; + int i; + FILE *f = fopen("/tmp/foo.ps", "w"); + bezctx *bc = new_bezctx_ps(f); + + fputs(ps_prolog, f); + for (i = 0; i < p->n_sp; i++) { + subpath *sp = &p->sp[i]; + free_spiro(draw_subpath(sp, bc)); + } + bezctx_ps_close(bc); + fputs(ps_postlog, f); + fclose(f); + return TRUE; +} + +static GtkWidget * +add_menuitem(GtkWidget *menu, const char *name, GtkSignalFunc callback, + gpointer callback_data, GtkAccelGroup *ag, const char *accel) +{ + GtkWidget *menuitem; + + menuitem = gtk_menu_item_new_with_label(name); + gtk_menu_append(GTK_MENU(menu), menuitem); + gtk_widget_show(menuitem); + if (accel != NULL) { + guint accel_key, accel_mods; + + gtk_accelerator_parse(accel, &accel_key, &accel_mods); + gtk_widget_add_accelerator(menuitem, "activate", ag, + accel_key, accel_mods, GTK_ACCEL_VISIBLE); + } + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + (GtkSignalFunc)callback, callback_data); + return (menuitem); +} + +static void +create_mainwin(plate_edit *p) +{ + GtkWidget *mainwin; + GtkWidget *eb; + GtkWidget *da; + GtkWidget *vbox; + GtkWidget *menubar; + GtkWidget *menu; + GtkWidget *menuitem; + GtkAccelGroup *ag; + void *data = p; + + mainwin = gtk_widget_new(gtk_window_get_type(), + "GtkWindow::type", GTK_WINDOW_TOPLEVEL, + "GtkWindow::title", "pattern plate editor", + NULL); + gtk_signal_connect(GTK_OBJECT(mainwin), "destroy", + (GtkSignalFunc)quit_func, NULL); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(mainwin), vbox); + + menubar = gtk_menu_bar_new(); + ag = gtk_accel_group_new(); + gtk_window_add_accel_group(GTK_WINDOW(mainwin), ag); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("File"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + add_menuitem(menu, "Save", (GtkSignalFunc)save_func, data, ag, "<ctrl>S"); + add_menuitem(menu, "Quit", (GtkSignalFunc)quit_func, data, ag, "<ctrl>Q"); + add_menuitem(menu, "Print", (GtkSignalFunc)print_func, data, ag, "<ctrl>P"); + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("Edit"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + p->undo_me = add_menuitem(menu, "Undo", (GtkSignalFunc)undo_func, data, ag, "<ctrl>Z"); + p->redo_me = add_menuitem(menu, "Redo", (GtkSignalFunc)redo_func, data, ag, "<ctrl>Y"); + set_undo_state(p, NULL, NULL); + add_menuitem(menu, "Toggle Corner", (GtkSignalFunc)toggle_corner_func, data, ag, "<ctrl>T"); + add_menuitem(menu, "Delete Point", (GtkSignalFunc)delete_pt_func, data, ag, "<ctrl>D"); + add_menuitem(menu, "Selection Mode", (GtkSignalFunc)set_select_mode_func, data, ag, "1"); + add_menuitem(menu, "Add Curve Mode", (GtkSignalFunc)set_curve_mode_func, data, ag, "2"); + add_menuitem(menu, "Add Corner Mode", (GtkSignalFunc)set_corner_mode_func, data, ag, "3"); + add_menuitem(menu, "Add Left Mode", (GtkSignalFunc)set_left_mode_func, data, ag, "4"); + add_menuitem(menu, "Add Right Mode", (GtkSignalFunc)set_right_mode_func, data, ag, "5"); + add_menuitem(menu, "Add Cornu Mode", (GtkSignalFunc)set_cornu_mode_func, data, ag, "6"); + + + menu = gtk_menu_new(); + menuitem = gtk_menu_item_new_with_label("View"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem); + gtk_widget_show(menuitem); + gtk_menu_set_accel_group(GTK_MENU(menu), ag); + p->show_knots_me = add_menuitem(menu, "Hide Knots", + (GtkSignalFunc)toggle_show_knots_func, + data, ag, "<ctrl>K"); + p->show_bg_me = add_menuitem(menu, "Hide BG", + (GtkSignalFunc)toggle_show_bg_func, + data, ag, "<ctrl>B"); + + eb = gtk_event_box_new (); + GTK_WIDGET_SET_FLAGS(eb, GTK_CAN_FOCUS); + gtk_box_pack_start(GTK_BOX(vbox), eb, TRUE, TRUE, 0); + gtk_widget_set_extension_events (eb, GDK_EXTENSION_EVENTS_ALL); + gtk_signal_connect(GTK_OBJECT (eb), "button-press-event", + (GtkSignalFunc) data_button_press, data); + gtk_signal_connect(GTK_OBJECT (eb), "motion-notify-event", + (GtkSignalFunc) data_motion, data); + gtk_signal_connect(GTK_OBJECT (eb), "button-release-event", + (GtkSignalFunc) data_button_release, data); + gtk_signal_connect(GTK_OBJECT(eb), "key-press-event", + (GtkSignalFunc)key_press, data); + + da = gtk_drawing_area_new(); + p->da = da; + gtk_window_set_default_size(GTK_WINDOW(mainwin), 512, 512); + gtk_container_add(GTK_CONTAINER(eb), da); + gtk_signal_connect(GTK_OBJECT (da), "expose-event", + (GtkSignalFunc) data_expose, data); +#if 0 + gtk_widget_set_double_buffered(da, FALSE); +#endif + + gtk_widget_grab_focus(eb); + gtk_widget_show(da); + gtk_widget_show(eb); + gtk_widget_show(menubar); + gtk_widget_show(vbox); + gtk_widget_show(mainwin); +} + +int main(int argc, char **argv) +{ + plate_edit pe; + plate *p = NULL; + gtk_init(&argc, &argv); + gtk_widget_set_default_colormap(gdk_rgb_get_cmap()); + gtk_widget_set_default_visual(gdk_rgb_get_visual()); + char *reason; + + if (argc > 1) + p = file_read_plate(argv[1]); + if (p == NULL) + p = new_plate(); + pe.p = p; + pe.undo_n = 0; + pe.undo_i = 0; + pe.undo_xn_state = 0; + pe.show_knots = 1; + pe.show_bg = 1; + pe.bg_image = load_image_file("/tmp/foo.ppm", &reason); + create_mainwin(&pe); + gtk_main(); + return 0; +} diff --git a/third_party/spiro/ppedit/sexp.c b/third_party/spiro/ppedit/sexp.c new file mode 100644 index 0000000..b4f0de4 --- /dev/null +++ b/third_party/spiro/ppedit/sexp.c @@ -0,0 +1,127 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +#include <stdio.h> +#include <math.h> + +#include "sexp.h" + +/* This is handcoded to avoid locale problems. */ +static int +parse_double(sexp_reader *sr) +{ + double sign = 1.0, val = 0.0; + int i; + int numstart; + const char * const b = sr->tokbuf; + int is_double = 1; + + i = 0; + if (b[i] == '+') { + i++; + } else if (b[i] == '-') { + sign = -1.0; + i++; + } + numstart = i; + while (b[i] >= '0' && b[i] <= '9') + val = val * 10.0 + b[i++] - '0'; + if (b[i] == '.') { + double frac = 1.0; + + for (i++; b[i] >= '0' && b[i] <= '9'; i++) { + frac *= 0.1; + val += (b[i] - '0') * frac; + } + + /* A '.' without any digits on either side isn't valid. */ + if (i == numstart + 1) + is_double = 0; + } + if (b[i] == 'e' || b[i] == 'E') { + int expsign = 1, exp = 0; + int expstart; + + if (b[i] == '+') { + i++; + } else if (b[i] == '-') { + expsign = -1; + i++; + } + expstart = i; + while (b[i] >= '0' && b[i] <= '9') + exp = exp * 10 + b[i++] - '0'; + + if (i == expstart) + is_double = 0; + val *= pow(10.0, expsign * exp); + } + val *= sign; + sr->d = val; + if (b[i] != 0) is_double = 0; + sr->is_double = is_double; + return is_double; +} + +/* Return values: 0 = EOF, 1 = token but not double, 2 = valid double */ +int +sexp_token(sexp_reader *sr) +{ + int c; + int i; + + sr->singlechar = -1; + if (sr->f == NULL) + return 0; + + for (;;) { + c = getc(sr->f); + if (c == EOF) { + sr->f = NULL; + return 0; + } else if (c == '#') { + do { + c = getc(sr->f); + } while (c != EOF && c != '\r' && c != '\n'); + } else if (c != ' ' && c != '\r' && c != '\n' && c != '\t') + break; + } + sr->tokbuf[0] = c; + i = 1; + if (c != '(' && c != ')') { + for (;;) { + c = getc(sr->f); + if (c == EOF) { + sr->f = NULL; + break; + } else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') { + break; + } else if (c == '(' || c == ')' || c == '#') { + ungetc(c, sr->f); + break; + } else if (i < sizeof(sr->tokbuf) - 1) + sr->tokbuf[i++] = c; + } + } + sr->tokbuf[i] = 0; + if (i == 1) + sr->singlechar = sr->tokbuf[0]; + return 1 + parse_double(sr); +} diff --git a/third_party/spiro/ppedit/sexp.h b/third_party/spiro/ppedit/sexp.h new file mode 100644 index 0000000..162152d --- /dev/null +++ b/third_party/spiro/ppedit/sexp.h @@ -0,0 +1,10 @@ +typedef struct { + FILE *f; + char tokbuf[256]; + int singlechar; + int is_double; + double d; +} sexp_reader; + +int +sexp_token(sexp_reader *sr); diff --git a/third_party/spiro/ppedit/spiro.c b/third_party/spiro/ppedit/spiro.c new file mode 100644 index 0000000..551697c --- /dev/null +++ b/third_party/spiro/ppedit/spiro.c @@ -0,0 +1,1070 @@ +/* +ppedit - A pattern plate editor for Spiro splines. +Copyright (C) 2007 Raph Levien + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +*/ +/* C implementation of third-order polynomial spirals. */ + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +#include "bezctx_intf.h" +#include "spiro.h" + +struct spiro_seg_s { + double x; + double y; + char ty; + double bend_th; + double ks[4]; + double seg_ch; + double seg_th; + double l; +}; + +typedef struct { + double a[11]; /* band-diagonal matrix */ + double al[5]; /* lower part of band-diagonal decomposition */ +} bandmat; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +int n = 4; + +#ifndef ORDER +#define ORDER 12 +#endif + +/* Integrate polynomial spiral curve over range -.5 .. .5. */ +void +integrate_spiro(const double ks[4], double xy[2]) +{ +#if 0 + int n = 1024; +#endif + double th1 = ks[0]; + double th2 = .5 * ks[1]; + double th3 = (1./6) * ks[2]; + double th4 = (1./24) * ks[3]; + double x, y; + double ds = 1. / n; + double ds2 = ds * ds; + double ds3 = ds2 * ds; + double k0 = ks[0] * ds; + double k1 = ks[1] * ds; + double k2 = ks[2] * ds; + double k3 = ks[3] * ds; + int i; + double s = .5 * ds - .5; + + x = 0; + y = 0; + + for (i = 0; i < n; i++) { + +#if ORDER > 2 + double u, v; + double km0, km1, km2, km3; + + if (n == 1) { + km0 = k0; + km1 = k1 * ds; + km2 = k2 * ds2; + } else { + km0 = (((1./6) * k3 * s + .5 * k2) * s + k1) * s + k0; + km1 = ((.5 * k3 * s + k2) * s + k1) * ds; + km2 = (k3 * s + k2) * ds2; + } + km3 = k3 * ds3; +#endif + + { + +#if ORDER == 4 + double km0_2 = km0 * km0; + u = 24 - km0_2; + v = km1; +#endif + +#if ORDER == 6 + double km0_2 = km0 * km0; + double km0_4 = km0_2 * km0_2; + u = 24 - km0_2 + (km0_4 - 4 * km0 * km2 - 3 * km1 * km1) * (1./80); + v = km1 + (km3 - 6 * km0_2 * km1) * (1./80); +#endif + +#if ORDER == 8 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t6_6 = t4_4 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6; + v -= (1./480) * t3_4 + (1./2688) * t3_6; + u += (1./1920) * t4_4 + (1./10752) * t4_6; + v += (1./53760) * t5_6; + u -= (1./322560) * t6_6; +#endif + +#if ORDER == 10 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t8_8 = t6_6 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8; + v += (1./53760) * t5_6 + (1./276480) * t5_8; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8; + v -= (1./1.16122e+07) * t7_8; + u += (1./9.28973e+07) * t8_8; +#endif + +#if ORDER == 12 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t10_10 = t8_8 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10; + v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10; + v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10; + u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10; + v += (1./4.08748e+09) * t9_10; + u -= (1./4.08748e+10) * t10_10; +#endif + +#if ORDER == 14 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t3_12 = t2_8 * t1_4; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6); + double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2; + double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2; + double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1; + double t10_10 = t8_8 * t2_2; + double t10_11 = t8_8 * t2_3 + t8_9 * t2_2; + double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2; + double t11_12 = t10_10 * t1_2 + t10_11 * t1_1; + double t12_12 = t10_10 * t2_2; + u = 1; + v = 0; + v += (1./12) * t1_2 + (1./80) * t1_4; + u -= (1./24) * t2_2 + (1./160) * t2_4 + (1./896) * t2_6 + (1./4608) * t2_8; + v -= (1./480) * t3_4 + (1./2688) * t3_6 + (1./13824) * t3_8 + (1./67584) * t3_10 + (1./319488) * t3_12; + u += (1./1920) * t4_4 + (1./10752) * t4_6 + (1./55296) * t4_8 + (1./270336) * t4_10 + (1./1.27795e+06) * t4_12; + v += (1./53760) * t5_6 + (1./276480) * t5_8 + (1./1.35168e+06) * t5_10 + (1./6.38976e+06) * t5_12; + u -= (1./322560) * t6_6 + (1./1.65888e+06) * t6_8 + (1./8.11008e+06) * t6_10 + (1./3.83386e+07) * t6_12; + v -= (1./1.16122e+07) * t7_8 + (1./5.67706e+07) * t7_10 + (1./2.6837e+08) * t7_12; + u += (1./9.28973e+07) * t8_8 + (1./4.54164e+08) * t8_10 + (1./2.14696e+09) * t8_12; + v += (1./4.08748e+09) * t9_10 + (1./1.93226e+10) * t9_12; + u -= (1./4.08748e+10) * t10_10 + (1./1.93226e+11) * t10_12; + v -= (1./2.12549e+12) * t11_12; + u += (1./2.55059e+13) * t12_12; +#endif + +#if ORDER == 16 + double t1_1 = km0; + double t1_2 = .5 * km1; + double t1_3 = (1./6) * km2; + double t1_4 = (1./24) * km3; + double t2_2 = t1_1 * t1_1; + double t2_3 = 2 * (t1_1 * t1_2); + double t2_4 = 2 * (t1_1 * t1_3) + t1_2 * t1_2; + double t2_5 = 2 * (t1_1 * t1_4 + t1_2 * t1_3); + double t2_6 = 2 * (t1_2 * t1_4) + t1_3 * t1_3; + double t2_7 = 2 * (t1_3 * t1_4); + double t2_8 = t1_4 * t1_4; + double t3_4 = t2_2 * t1_2 + t2_3 * t1_1; + double t3_6 = t2_2 * t1_4 + t2_3 * t1_3 + t2_4 * t1_2 + t2_5 * t1_1; + double t3_8 = t2_4 * t1_4 + t2_5 * t1_3 + t2_6 * t1_2 + t2_7 * t1_1; + double t3_10 = t2_6 * t1_4 + t2_7 * t1_3 + t2_8 * t1_2; + double t3_12 = t2_8 * t1_4; + double t4_4 = t2_2 * t2_2; + double t4_5 = 2 * (t2_2 * t2_3); + double t4_6 = 2 * (t2_2 * t2_4) + t2_3 * t2_3; + double t4_7 = 2 * (t2_2 * t2_5 + t2_3 * t2_4); + double t4_8 = 2 * (t2_2 * t2_6 + t2_3 * t2_5) + t2_4 * t2_4; + double t4_9 = 2 * (t2_2 * t2_7 + t2_3 * t2_6 + t2_4 * t2_5); + double t4_10 = 2 * (t2_2 * t2_8 + t2_3 * t2_7 + t2_4 * t2_6) + t2_5 * t2_5; + double t4_11 = 2 * (t2_3 * t2_8 + t2_4 * t2_7 + t2_5 * t2_6); + double t4_12 = 2 * (t2_4 * t2_8 + t2_5 * t2_7) + t2_6 * t2_6; + double t4_13 = 2 * (t2_5 * t2_8 + t2_6 * t2_7); + double t4_14 = 2 * (t2_6 * t2_8) + t2_7 * t2_7; + double t5_6 = t4_4 * t1_2 + t4_5 * t1_1; + double t5_8 = t4_4 * t1_4 + t4_5 * t1_3 + t4_6 * t1_2 + t4_7 * t1_1; + double t5_10 = t4_6 * t1_4 + t4_7 * t1_3 + t4_8 * t1_2 + t4_9 * t1_1; + double t5_12 = t4_8 * t1_4 + t4_9 * t1_3 + t4_10 * t1_2 + t4_11 * t1_1; + double t5_14 = t4_10 * t1_4 + t4_11 * t1_3 + t4_12 * t1_2 + t4_13 * t1_1; + double t6_6 = t4_4 * t2_2; + double t6_7 = t4_4 * t2_3 + t4_5 * t2_2; + double t6_8 = t4_4 * t2_4 + t4_5 * t2_3 + t4_6 * t2_2; + double t6_9 = t4_4 * t2_5 + t4_5 * t2_4 + t4_6 * t2_3 + t4_7 * t2_2; + double t6_10 = t4_4 * t2_6 + t4_5 * t2_5 + t4_6 * t2_4 + t4_7 * t2_3 + t4_8 * t2_2; + double t6_11 = t4_4 * t2_7 + t4_5 * t2_6 + t4_6 * t2_5 + t4_7 * t2_4 + t4_8 * t2_3 + t4_9 * t2_2; + double t6_12 = t4_4 * t2_8 + t4_5 * t2_7 + t4_6 * t2_6 + t4_7 * t2_5 + t4_8 * t2_4 + t4_9 * t2_3 + t4_10 * t2_2; + double t6_13 = t4_5 * t2_8 + t4_6 * t2_7 + t4_7 * t2_6 + t4_8 * t2_5 + t4_9 * t2_4 + t4_10 * t2_3 + t4_11 * t2_2; + double t6_14 = t4_6 * t2_8 + t4_7 * t2_7 + t4_8 * t2_6 + t4_9 * t2_5 + t4_10 * t2_4 + t4_11 * t2_3 + t4_12 * t2_2; + double t7_8 = t6_6 * t1_2 + t6_7 * t1_1; + double t7_10 = t6_6 * t1_4 + t6_7 * t1_3 + t6_8 * t1_2 + t6_9 * t1_1; + double t7_12 = t6_8 * t1_4 + t6_9 * t1_3 + t6_10 * t1_2 + t6_11 * t1_1; + double t7_14 = t6_10 * t1_4 + t6_11 * t1_3 + t6_12 * t1_2 + t6_13 * t1_1; + double t8_8 = t6_6 * t2_2; + double t8_9 = t6_6 * t2_3 + t6_7 * t2_2; + double t8_10 = t6_6 * t2_4 + t6_7 * t2_3 + t6_8 * t2_2; + double t8_11 = t6_6 * t2_5 + t6_7 * t2_4 + t6_8 * t2_3 + t6_9 * t2_2; + double t8_12 = t6_6 * t2_6 + t6_7 * t2_5 + t6_8 * t2_4 + t6_9 * t2_3 + t6_10 * t2_2; + double t8_13 = t6_6 * t2_7 + t6_7 * t2_6 + t6_8 * t2_5 + t6_9 * t2_4 + t6_10 * t2_3 + t6_11 * t2_2; + double t8_14 = t6_6 * t2_8 + t6_7 * t2_7 + t6_8 * t2_6 + t6_9 * t2_5 + t6_10 * t2_4 + t6_11 * t2_3 + t6_12 * t2_2; + double t9_10 = t8_8 * t1_2 + t8_9 * t1_1; + double t9_12 = t8_8 * t1_4 + t8_9 * t1_3 + t8_10 * t1_2 + t8_11 * t1_1; + double t9_14 = t8_10 * t1_4 + t8_11 * t1_3 + t8_12 * t1_2 + t8_13 * t1_1; + double t10_10 = t8_8 * t2_2; + double t10_11 = t8_8 * t2_3 + t8_9 * t2_2; + double t10_12 = t8_8 * t2_4 + t8_9 * t2_3 + t8_10 * t2_2; + double t10_13 = t8_8 * t2_5 + t8_9 * t2_4 + t8_10 * t2_3 + t8_11 * t2_2; + double t10_14 = t8_8 * t2_6 + t8_9 * t2_5 + t8_10 * t2_4 + t8_11 * t2_3 + t8_12 * t2_2; + double t11_12 = t10_10 * t1_2 + t10_11 * t1_1; + double t11_14 = t10_10 * t1_4 + t10_11 * t1_3 + t10_12 * t1_2 + t10_13 * t1_1; + double t12_12 = t10_10 * t2_2; + double t12_13 = t10_10 * t2_3 + t10_11 * t2_2; + double t12_14 = t10_10 * t2_4 + t10_11 * t2_3 + t10_12 * t2_2; + double t13_14 = t12_12 * t1_2 + t12_13 * t1_1; + double t14_14 = t12_12 * t2_2; + u = 1; + u -= 1./24 * t2_2 + 1./160 * t2_4 + 1./896 * t2_6 + 1./4608 * t2_8; + u += 1./1920 * t4_4 + 1./10752 * t4_6 + 1./55296 * t4_8 + 1./270336 * t4_10 + 1./1277952 * t4_12 + 1./5898240 * t4_14; + u -= 1./322560 * t6_6 + 1./1658880 * t6_8 + 1./8110080 * t6_10 + 1./38338560 * t6_12 + 1./176947200 * t6_14; + u += 1./92897280 * t8_8 + 1./454164480 * t8_10 + 4.6577500191e-10 * t8_12 + 1.0091791708e-10 * t8_14; + u -= 2.4464949595e-11 * t10_10 + 5.1752777990e-12 * t10_12 + 1.1213101898e-12 * t10_14; + u += 3.9206649992e-14 * t12_12 + 8.4947741650e-15 * t12_14; + u -= 4.6674583324e-17 * t14_14; + v = 0; + v += 1./12 * t1_2 + 1./80 * t1_4; + v -= 1./480 * t3_4 + 1./2688 * t3_6 + 1./13824 * t3_8 + 1./67584 * t3_10 + 1./319488 * t3_12; + v += 1./53760 * t5_6 + 1./276480 * t5_8 + 1./1351680 * t5_10 + 1./6389760 * t5_12 + 1./29491200 * t5_14; + v -= 1./11612160 * t7_8 + 1./56770560 * t7_10 + 1./268369920 * t7_12 + 8.0734333664e-10 * t7_14; + v += 2.4464949595e-10 * t9_10 + 5.1752777990e-11 * t9_12 + 1.1213101898e-11 * t9_14; + v -= 4.7047979991e-13 * t11_12 + 1.0193728998e-13 * t11_14; + v += 6.5344416654e-16 * t13_14; +#endif + + } + + if (n == 1) { +#if ORDER == 2 + x = 1; + y = 0; +#else + x = u; + y = v; +#endif + } else { + double th = (((th4 * s + th3) * s + th2) * s + th1) * s; + double cth = cos(th); + double sth = sin(th); + +#if ORDER == 2 + x += cth; + y += sth; +#else + x += cth * u - sth * v; + y += cth * v + sth * u; +#endif + s += ds; + } + } + +#if ORDER == 4 || ORDER == 6 + xy[0] = x * (1./24 * ds); + xy[1] = y * (1./24 * ds); +#else + xy[0] = x * ds; + xy[1] = y * ds; +#endif +} + +static double +compute_ends(const double ks[4], double ends[2][4], double seg_ch) +{ + double xy[2]; + double ch, th; + double l, l2, l3; + double th_even, th_odd; + double k0_even, k0_odd; + double k1_even, k1_odd; + double k2_even, k2_odd; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + l = ch / seg_ch; + + th_even = .5 * ks[0] + (1./48) * ks[2]; + th_odd = .125 * ks[1] + (1./384) * ks[3] - th; + ends[0][0] = th_even - th_odd; + ends[1][0] = th_even + th_odd; + k0_even = l * (ks[0] + .125 * ks[2]); + k0_odd = l * (.5 * ks[1] + (1./48) * ks[3]); + ends[0][1] = k0_even - k0_odd; + ends[1][1] = k0_even + k0_odd; + l2 = l * l; + k1_even = l2 * (ks[1] + .125 * ks[3]); + k1_odd = l2 * .5 * ks[2]; + ends[0][2] = k1_even - k1_odd; + ends[1][2] = k1_even + k1_odd; + l3 = l2 * l; + k2_even = l3 * ks[2]; + k2_odd = l3 * .5 * ks[3]; + ends[0][3] = k2_even - k2_odd; + ends[1][3] = k2_even + k2_odd; + + return l; +} + +static void +compute_pderivs(const spiro_seg *s, double ends[2][4], double derivs[4][2][4], + int jinc) +{ + double recip_d = 2e6; + double delta = 1./ recip_d; + double try_ks[4]; + double try_ends[2][4]; + int i, j, k; + + compute_ends(s->ks, ends, s->seg_ch); + for (i = 0; i < jinc; i++) { + for (j = 0; j < 4; j++) + try_ks[j] = s->ks[j]; + try_ks[i] += delta; + compute_ends(try_ks, try_ends, s->seg_ch); + for (k = 0; k < 2; k++) + for (j = 0; j < 4; j++) + derivs[j][k][i] = recip_d * (try_ends[k][j] - ends[k][j]); + } +} + +static double +mod_2pi(double th) +{ + double u = th / (2 * M_PI); + return 2 * M_PI * (u - floor(u + 0.5)); +} + +static spiro_seg * +setup_path(const spiro_cp *src, int n) +{ + int n_seg = src[0].ty == '{' ? n - 1 : n; + spiro_seg *r = (spiro_seg *)malloc((n_seg + 1) * sizeof(spiro_seg)); + int i; + int ilast; + + for (i = 0; i < n_seg; i++) { + r[i].x = src[i].x; + r[i].y = src[i].y; + r[i].ty = src[i].ty; + r[i].ks[0] = 0.; + r[i].ks[1] = 0.; + r[i].ks[2] = 0.; + r[i].ks[3] = 0.; + } + r[n_seg].x = src[n_seg % n].x; + r[n_seg].y = src[n_seg % n].y; + r[n_seg].ty = src[n_seg % n].ty; + + for (i = 0; i < n_seg; i++) { + double dx = r[i + 1].x - r[i].x; + double dy = r[i + 1].y - r[i].y; + r[i].seg_ch = hypot(dx, dy); + r[i].seg_th = atan2(dy, dx); + } + + ilast = n_seg - 1; + for (i = 0; i < n_seg; i++) { + if (r[i].ty == '{' || r[i].ty == '}' || r[i].ty == 'v') + r[i].bend_th = 0.; + else + r[i].bend_th = mod_2pi(r[i].seg_th - r[ilast].seg_th); + ilast = i; + } + return r; +} + +static void +bandec11(bandmat *m, int *perm, int n) +{ + int i, j, k; + int l; + + /* pack top triangle to the left. */ + for (i = 0; i < 5; i++) { + for (j = 0; j < i + 6; j++) + m[i].a[j] = m[i].a[j + 5 - i]; + for (; j < 11; j++) + m[i].a[j] = 0.; + } + l = 5; + for (k = 0; k < n; k++) { + int pivot = k; + double pivot_val = m[k].a[0]; + double pivot_scale; + + if (l < n) l++; + + for (j = k + 1; j < l; j++) + if (fabs(m[j].a[0]) > fabs(pivot_val)) { + pivot_val = m[j].a[0]; + pivot = j; + } + + perm[k] = pivot; + if (pivot != k) { + for (j = 0; j < 11; j++) { + double tmp = m[k].a[j]; + m[k].a[j] = m[pivot].a[j]; + m[pivot].a[j] = tmp; + } + } + + if (fabs(pivot_val) < 1e-12) pivot_val = 1e-12; + pivot_scale = 1. / pivot_val; + for (i = k + 1; i < l; i++) { + double x = m[i].a[0] * pivot_scale; + m[k].al[i - k - 1] = x; + for (j = 1; j < 11; j++) + m[i].a[j - 1] = m[i].a[j] - x * m[k].a[j]; + m[i].a[10] = 0.; + } + } +} + +static void +banbks11(const bandmat *m, const int *perm, double *v, int n) +{ + int i, k, l; + + /* forward substitution */ + l = 5; + for (k = 0; k < n; k++) { + i = perm[k]; + if (i != k) { + double tmp = v[k]; + v[k] = v[i]; + v[i] = tmp; + } + if (l < n) l++; + for (i = k + 1; i < l; i++) + v[i] -= m[k].al[i - k - 1] * v[k]; + } + + /* back substitution */ + l = 1; + for (i = n - 1; i >= 0; i--) { + double x = v[i]; + for (k = 1; k < l; k++) + x -= m[i].a[k] * v[k + i]; + v[i] = x / m[i].a[0]; + if (l < 11) l++; + } +} + +int compute_jinc(char ty0, char ty1) +{ + if (ty0 == 'o' || ty1 == 'o' || + ty0 == ']' || ty1 == '[') + return 4; + else if (ty0 == 'c' && ty1 == 'c') + return 2; + else if (((ty0 == '{' || ty0 == 'v' || ty0 == '[') && ty1 == 'c') || + (ty0 == 'c' && (ty1 == '}' || ty1 == 'v' || ty1 == ']'))) + return 1; + else + return 0; +} + +int count_vec(const spiro_seg *s, int nseg) +{ + int i; + int n = 0; + + for (i = 0; i < nseg; i++) + n += compute_jinc(s[i].ty, s[i + 1].ty); + return n; +} + +static void +add_mat_line(bandmat *m, double *v, + double derivs[4], double x, double y, int j, int jj, int jinc, + int nmat) +{ + int k; + + if (jj >= 0) { + int joff = (j + 5 - jj + nmat) % nmat; + v[jj] += x; + for (k = 0; k < jinc; k++) + m[jj].a[joff + k] += y * derivs[k]; + } +} + +static double +spiro_iter(spiro_seg *s, bandmat *m, int *perm, double *v, int n) +{ + int cyclic = s[0].ty != '{' && s[0].ty != 'v'; + int i, j, jj; + int nmat = count_vec(s, n); + double norm; + int n_invert; + + for (i = 0; i < nmat; i++) { + v[i] = 0.; + for (j = 0; j < 11; j++) + m[i].a[j] = 0.; + for (j = 0; j < 5; j++) + m[i].al[j] = 0.; + } + + j = 0; + if (s[0].ty == 'o') + jj = nmat - 2; + else if (s[0].ty == 'c' || s[0].ty == '[' || s[0].ty == ']') + jj = nmat - 1; + else + jj = 0; + for (i = 0; i < n; i++) { + char ty0 = s[i].ty; + char ty1 = s[i + 1].ty; + int jinc = compute_jinc(ty0, ty1); + double th = s[i].bend_th; + double ends[2][4]; + double derivs[4][2][4]; + int jthl = -1, jk0l = -1, jk1l = -1, jk2l = -1; + int jthr = -1, jk0r = -1, jk1r = -1, jk2r = -1; + + compute_pderivs(&s[i], ends, derivs, jinc); + + /* constraints crossing left */ + if (ty0 == 'o' || ty0 == 'c' || ty0 == '[' || ty0 == ']') { + jthl = jj++; + jj %= nmat; + jk0l = jj++; + } + if (ty0 == 'o') { + jj %= nmat; + jk1l = jj++; + jk2l = jj++; + } + + /* constraints on left */ + if ((ty0 == '[' || ty0 == 'v' || ty0 == '{' || ty0 == 'c') && + jinc == 4) { + if (ty0 != 'c') + jk1l = jj++; + jk2l = jj++; + } + + /* constraints on right */ + if ((ty1 == ']' || ty1 == 'v' || ty1 == '}' || ty1 == 'c') && + jinc == 4) { + if (ty1 != 'c') + jk1r = jj++; + jk2r = jj++; + } + + /* constraints crossing right */ + if (ty1 == 'o' || ty1 == 'c' || ty1 == '[' || ty1 == ']') { + jthr = jj; + jk0r = (jj + 1) % nmat; + } + if (ty1 == 'o') { + jk1r = (jj + 2) % nmat; + jk2r = (jj + 3) % nmat; + } + + add_mat_line(m, v, derivs[0][0], th - ends[0][0], 1, j, jthl, jinc, nmat); + add_mat_line(m, v, derivs[1][0], ends[0][1], -1, j, jk0l, jinc, nmat); + add_mat_line(m, v, derivs[2][0], ends[0][2], -1, j, jk1l, jinc, nmat); + add_mat_line(m, v, derivs[3][0], ends[0][3], -1, j, jk2l, jinc, nmat); + add_mat_line(m, v, derivs[0][1], -ends[1][0], 1, j, jthr, jinc, nmat); + add_mat_line(m, v, derivs[1][1], -ends[1][1], 1, j, jk0r, jinc, nmat); + add_mat_line(m, v, derivs[2][1], -ends[1][2], 1, j, jk1r, jinc, nmat); + add_mat_line(m, v, derivs[3][1], -ends[1][3], 1, j, jk2r, jinc, nmat); + j += jinc; + } + if (cyclic) { + memcpy(m + nmat, m, sizeof(bandmat) * nmat); + memcpy(m + 2 * nmat, m, sizeof(bandmat) * nmat); + memcpy(v + nmat, v, sizeof(double) * nmat); + memcpy(v + 2 * nmat, v, sizeof(double) * nmat); + n_invert = 3 * nmat; + j = nmat; + } else { + n_invert = nmat; + j = 0; + } + bandec11(m, perm, n_invert); + banbks11(m, perm, v, n_invert); + norm = 0.; + for (i = 0; i < n; i++) { + char ty0 = s[i].ty; + char ty1 = s[i + 1].ty; + int jinc = compute_jinc(ty0, ty1); + int k; + + for (k = 0; k < jinc; k++) { + double dk = v[j++]; + + s[i].ks[k] += dk; + norm += dk * dk; + } + } + return norm; +} + +int +solve_spiro(spiro_seg *s, int nseg) +{ + bandmat *m; + double *v; + int *perm; + int nmat = count_vec(s, nseg); + int n_alloc = nmat; + double norm; + int i; + + if (nmat == 0) + return 0; + if (s[0].ty != '{' && s[0].ty != 'v') + n_alloc *= 3; + if (n_alloc < 5) + n_alloc = 5; + m = (bandmat *)malloc(sizeof(bandmat) * n_alloc); + v = (double *)malloc(sizeof(double) * n_alloc); + perm = (int *)malloc(sizeof(int) * n_alloc); + + for (i = 0; i < 10; i++) { + norm = spiro_iter(s, m, perm, v, nseg); +#ifdef VERBOSE + printf("%% norm = %g\n", norm); +#endif + if (norm < 1e-12) break; + } + + free(m); + free(v); + free(perm); + return 0; +} + +static void +spiro_seg_to_bpath(const double ks[4], + double x0, double y0, double x1, double y1, + bezctx *bc, int depth) +{ + double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) + + fabs((1./48) * ks[3]); + + if (!bend > 1e-8) { + bezctx_lineto(bc, x1, y1); + } else { + double seg_ch = hypot(x1 - x0, y1 - y0); + double seg_th = atan2(y1 - y0, x1 - x0); + double xy[2]; + double ch, th; + double scale, rot; + double th_even, th_odd; + double ul, vl; + double ur, vr; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + scale = seg_ch / ch; + rot = seg_th - th; + if (depth > 5 || bend < 1.) { + th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot; + th_odd = (1./48) * ks[2] + .5 * ks[0]; + ul = (scale * (1./3)) * cos(th_even - th_odd); + vl = (scale * (1./3)) * sin(th_even - th_odd); + ur = (scale * (1./3)) * cos(th_even + th_odd); + vr = (scale * (1./3)) * sin(th_even + th_odd); + bezctx_curveto(bc, x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1); + } else { + /* subdivide */ + double ksub[4]; + double thsub; + double xysub[2]; + double xmid, ymid; + double cth, sth; + + ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3]; + ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3]; + ksub[2] = .125 * ks[2] - (1./32) * ks[3]; + ksub[3] = (1./16) * ks[3]; + thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3]; + cth = .5 * scale * cos(thsub); + sth = .5 * scale * sin(thsub); + integrate_spiro(ksub, xysub); + xmid = x0 + cth * xysub[0] - sth * xysub[1]; + ymid = y0 + cth * xysub[1] + sth * xysub[0]; + spiro_seg_to_bpath(ksub, x0, y0, xmid, ymid, bc, depth + 1); + ksub[0] += .25 * ks[1] + (1./384) * ks[3]; + ksub[1] += .125 * ks[2]; + ksub[2] += (1./16) * ks[3]; + spiro_seg_to_bpath(ksub, xmid, ymid, x1, y1, bc, depth + 1); + } + } +} + +spiro_seg * +run_spiro(const spiro_cp *src, int n) +{ + int nseg = src[0].ty == '{' ? n - 1 : n; + spiro_seg *s = setup_path(src, n); + if (nseg > 1) + solve_spiro(s, nseg); + return s; +} + +void +free_spiro(spiro_seg *s) +{ + free(s); +} + +void +spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc) +{ + int i; + int nsegs = s[n - 1].ty == '}' ? n - 1 : n; + + for (i = 0; i < nsegs; i++) { + double x0 = s[i].x; + double y0 = s[i].y; + double x1 = s[i + 1].x; + double y1 = s[i + 1].y; + + if (i == 0) + bezctx_moveto(bc, x0, y0, s[0].ty == '{'); + bezctx_mark_knot(bc, i); + spiro_seg_to_bpath(s[i].ks, x0, y0, x1, y1, bc, 0); + } +} + +double +get_knot_th(const spiro_seg *s, int i) +{ + double ends[2][4]; + + if (i == 0) { + compute_ends(s[i].ks, ends, s[i].seg_ch); + return s[i].seg_th - ends[0][0]; + } else { + compute_ends(s[i - 1].ks, ends, s[i - 1].seg_ch); + return s[i - 1].seg_th + ends[1][0]; + } +} + +#ifdef UNIT_TEST +#include <stdio.h> +#include <sys/time.h> /* for gettimeofday */ + +static double +get_time (void) +{ + struct timeval tv; + struct timezone tz; + + gettimeofday (&tv, &tz); + + return tv.tv_sec + 1e-6 * tv.tv_usec; +} + +int +test_integ(void) { + double ks[] = {1, 2, 3, 4}; + double xy[2]; + double xynom[2]; + double ch, th; + int i, j; + int nsubdiv; + + n = ORDER < 6 ? 4096 : 1024; + integrate_spiro(ks, xynom); + nsubdiv = ORDER < 12 ? 8 : 7; + for (i = 0; i < nsubdiv; i++) { + double st, en; + double err; + int n_iter = (1 << (20 - i)); + + n = 1 << i; + st = get_time(); + for (j = 0; j < n_iter; j++) + integrate_spiro(ks, xy); + en = get_time(); + err = hypot(xy[0] - xynom[0], xy[1] - xynom[1]); + printf("%d %d %g %g\n", ORDER, n, (en - st) / n_iter, err); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); +#if 0 + printf("n = %d: integ(%g %g %g %g) = %g %g, ch = %g, th = %g\n", n, + ks[0], ks[1], ks[2], ks[3], xy[0], xy[1], ch, th); + printf("%d: %g %g\n", n, xy[0] - xynom[0], xy[1] - xynom[1]); +#endif + } + return 0; +} + +void +print_seg(const double ks[4], double x0, double y0, double x1, double y1) +{ + double bend = fabs(ks[0]) + fabs(.5 * ks[1]) + fabs(.125 * ks[2]) + + fabs((1./48) * ks[3]); + + if (bend < 1e-8) { + printf("%g %g lineto\n", x1, y1); + } else { + double seg_ch = hypot(x1 - x0, y1 - y0); + double seg_th = atan2(y1 - y0, x1 - x0); + double xy[2]; + double ch, th; + double scale, rot; + double th_even, th_odd; + double ul, vl; + double ur, vr; + + integrate_spiro(ks, xy); + ch = hypot(xy[0], xy[1]); + th = atan2(xy[1], xy[0]); + scale = seg_ch / ch; + rot = seg_th - th; + if (bend < 1.) { + th_even = (1./384) * ks[3] + (1./8) * ks[1] + rot; + th_odd = (1./48) * ks[2] + .5 * ks[0]; + ul = (scale * (1./3)) * cos(th_even - th_odd); + vl = (scale * (1./3)) * sin(th_even - th_odd); + ur = (scale * (1./3)) * cos(th_even + th_odd); + vr = (scale * (1./3)) * sin(th_even + th_odd); + printf("%g %g %g %g %g %g curveto\n", + x0 + ul, y0 + vl, x1 - ur, y1 - vr, x1, y1); + + } else { + /* subdivide */ + double ksub[4]; + double thsub; + double xysub[2]; + double xmid, ymid; + double cth, sth; + + ksub[0] = .5 * ks[0] - .125 * ks[1] + (1./64) * ks[2] - (1./768) * ks[3]; + ksub[1] = .25 * ks[1] - (1./16) * ks[2] + (1./128) * ks[3]; + ksub[2] = .125 * ks[2] - (1./32) * ks[3]; + ksub[3] = (1./16) * ks[3]; + thsub = rot - .25 * ks[0] + (1./32) * ks[1] - (1./384) * ks[2] + (1./6144) * ks[3]; + cth = .5 * scale * cos(thsub); + sth = .5 * scale * sin(thsub); + integrate_spiro(ksub, xysub); + xmid = x0 + cth * xysub[0] - sth * xysub[1]; + ymid = y0 + cth * xysub[1] + sth * xysub[0]; + print_seg(ksub, x0, y0, xmid, ymid); + ksub[0] += .25 * ks[1] + (1./384) * ks[3]; + ksub[1] += .125 * ks[2]; + ksub[2] += (1./16) * ks[3]; + print_seg(ksub, xmid, ymid, x1, y1); + } + } +} + +void +print_segs(const spiro_seg *segs, int nsegs) +{ + int i; + + for (i = 0; i < nsegs; i++) { + double x0 = segs[i].x; + double y0 = segs[i].y; + double x1 = segs[i + 1].x; + double y1 = segs[i + 1].y; + + if (i == 0) + printf("%g %g moveto\n", x0, y0); + printf("%% ks = [ %g %g %g %g ]\n", + segs[i].ks[0], segs[i].ks[1], segs[i].ks[2], segs[i].ks[3]); + print_seg(segs[i].ks, x0, y0, x1, y1); + } + printf("stroke\n"); +} + +int +test_curve(void) +{ + spiro_cp path[] = { + {334, 117, 'v'}, + {305, 176, 'v'}, + {212, 142, 'c'}, + {159, 171, 'c'}, + {224, 237, 'c'}, + {347, 335, 'c'}, + {202, 467, 'c'}, + {81, 429, 'v'}, + {114, 368, 'v'}, + {201, 402, 'c'}, + {276, 369, 'c'}, + {218, 308, 'c'}, + {91, 211, 'c'}, + {124, 111, 'c'}, + {229, 82, 'c'} + }; + spiro_seg *segs; + int i; + + n = 1; + for (i = 0; i < 1000; i++) { + segs = setup_path(path, 15); + solve_spiro(segs, 15); + } + printf("100 800 translate 1 -1 scale 1 setlinewidth\n"); + print_segs(segs, 15); + printf("showpage\n"); + return 0; +} + +int main(int argc, char **argv) +{ + return test_curve(); +} +#endif diff --git a/third_party/spiro/ppedit/spiro.h b/third_party/spiro/ppedit/spiro.h new file mode 100644 index 0000000..54de404 --- /dev/null +++ b/third_party/spiro/ppedit/spiro.h @@ -0,0 +1,18 @@ +typedef struct { + double x; + double y; + char ty; +} spiro_cp; + +typedef struct spiro_seg_s spiro_seg; + +spiro_seg * +run_spiro(const spiro_cp *src, int n); + +void +free_spiro(spiro_seg *s); + +void +spiro_to_bpath(const spiro_seg *s, int n, bezctx *bc); + +double get_knot_th(const spiro_seg *s, int i); diff --git a/third_party/spiro/ppedit/zmisc.h b/third_party/spiro/ppedit/zmisc.h new file mode 100644 index 0000000..ce3d4d3 --- /dev/null +++ b/third_party/spiro/ppedit/zmisc.h @@ -0,0 +1,12 @@ +/** + * Misc portability and convenience macros. + **/ + +#include <stdlib.h> + +#define zalloc malloc +#define zrealloc realloc +#define zfree free + +#define znew(type, n) (type *)zalloc(sizeof(type) * (n)) +#define zrenew(type, p, n) (type *)zrealloc((p), sizeof(type) * (n)) |