summaryrefslogtreecommitdiff
path: root/third_party/spiro/ppedit
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/spiro/ppedit')
-rw-r--r--third_party/spiro/ppedit/Makefile25
-rw-r--r--third_party/spiro/ppedit/Makefile_gtk15
-rw-r--r--third_party/spiro/ppedit/README112
-rw-r--r--third_party/spiro/ppedit/bezctx.c48
-rw-r--r--third_party/spiro/ppedit/bezctx.h10
-rw-r--r--third_party/spiro/ppedit/bezctx_hittest.c261
-rw-r--r--third_party/spiro/ppedit/bezctx_hittest.h5
-rw-r--r--third_party/spiro/ppedit/bezctx_intf.h20
-rw-r--r--third_party/spiro/ppedit/bezctx_libart.c131
-rw-r--r--third_party/spiro/ppedit/bezctx_libart.h5
-rw-r--r--third_party/spiro/ppedit/bezctx_ps.c116
-rw-r--r--third_party/spiro/ppedit/bezctx_ps.h8
-rw-r--r--third_party/spiro/ppedit/bezctx_quartz.c78
-rw-r--r--third_party/spiro/ppedit/bezctx_quartz.h4
-rw-r--r--third_party/spiro/ppedit/bezctx_x3.c96
-rw-r--r--third_party/spiro/ppedit/bezctx_x3.h3
-rw-r--r--third_party/spiro/ppedit/carbon_main.c182
-rw-r--r--third_party/spiro/ppedit/cornu.c615
-rw-r--r--third_party/spiro/ppedit/cornu.h13
-rw-r--r--third_party/spiro/ppedit/gpl.txt339
-rw-r--r--third_party/spiro/ppedit/image.c141
-rw-r--r--third_party/spiro/ppedit/image.h11
-rw-r--r--third_party/spiro/ppedit/pe_view.c548
-rw-r--r--third_party/spiro/ppedit/pe_view.h11
-rw-r--r--third_party/spiro/ppedit/plate.c526
-rw-r--r--third_party/spiro/ppedit/plate.h100
-rw-r--r--third_party/spiro/ppedit/ppedit.c603
-rw-r--r--third_party/spiro/ppedit/ppedit_gtk1.c930
-rw-r--r--third_party/spiro/ppedit/sexp.c127
-rw-r--r--third_party/spiro/ppedit/sexp.h10
-rw-r--r--third_party/spiro/ppedit/spiro.c1070
-rw-r--r--third_party/spiro/ppedit/spiro.h18
-rw-r--r--third_party/spiro/ppedit/zmisc.h12
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))