summaryrefslogtreecommitdiff
path: root/third_party/spiro/ppedit/plate.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/spiro/ppedit/plate.c')
-rw-r--r--third_party/spiro/ppedit/plate.c526
1 files changed, 526 insertions, 0 deletions
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;
+}