1 /*
2 ppedit - A pattern plate editor for Spiro splines.
3 Copyright (C) 2007 Raph Levien
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19
20 */
21 #include <string.h>
22 #include <math.h>
23 #include <stdio.h>
24
25 #include "zmisc.h"
26 #include "sexp.h"
27 #include "bezctx_intf.h"
28 #include "bezctx_hittest.h"
29 #include "cornu.h"
30 #include "spiro.h"
31 #include "plate.h"
32
33 /* This is a global while we're playing with the tangent solver. Once we get that
34 nailed down, it will go away. */
35 extern int n_iter;
36
37 /**
38 * These are functions for editing a Cornu spline ("plate"), intended
39 * to be somewhat independent of the UI toolkit specifics.
40 **/
41
42 plate *
new_plate(void)43 new_plate(void)
44 {
45 plate *p = znew(plate, 1);
46
47 p->n_sp = 0;
48 p->n_sp_max = 4;
49 p->sp = znew(subpath, p->n_sp_max);
50 p->mmode = MOUSE_MODE_ADD_CURVE;
51 p->last_curve_mmode = p->mmode;
52 return p;
53 }
54
55 void
free_plate(plate * p)56 free_plate(plate *p)
57 {
58 int i;
59 for (i = 0; i < p->n_sp; i++) {
60 subpath *sp = &p->sp[i];
61 zfree(sp->kt);
62 }
63 zfree(p->sp);
64 zfree(p);
65 }
66
67 plate *
copy_plate(const plate * p)68 copy_plate(const plate *p)
69 {
70 int i;
71 plate *n = znew(plate, 1);
72
73 n->n_sp = p->n_sp;
74 n->n_sp_max = p->n_sp_max;
75 n->sp = znew(subpath, n->n_sp_max);
76 for (i = 0; i < n->n_sp; i++) {
77 subpath *sp = &p->sp[i];
78 subpath *nsp = &n->sp[i];
79
80 nsp->n_kt = sp->n_kt;
81 nsp->n_kt_max = sp->n_kt_max;
82 nsp->kt = znew(knot, nsp->n_kt_max);
83 memcpy(nsp->kt, sp->kt, nsp->n_kt * sizeof(knot));
84 nsp->closed = sp->closed;
85 }
86 n->mmode = p->mmode;
87 return n;
88 }
89
90 void
plate_select_all(plate * p,int selected)91 plate_select_all(plate *p, int selected)
92 {
93 int i, j;
94
95 /* find an existing point to select, if any */
96 for (i = 0; i < p->n_sp; i++) {
97 subpath *sp = &p->sp[i];
98
99 for (j = 0; j < sp->n_kt; j++) {
100 knot *kt = &sp->kt[j];
101 kt->flags &= ~KT_SELECTED;
102 if (selected)
103 kt->flags |= KT_SELECTED;
104 }
105 }
106 }
107
108 subpath *
plate_find_selected_sp(plate * p)109 plate_find_selected_sp(plate *p)
110 {
111 int i, j;
112
113 /* find an existing point to select, if any */
114 for (i = 0; i < p->n_sp; i++) {
115 subpath *sp = &p->sp[i];
116
117 for (j = 0; j < sp->n_kt; j++) {
118 knot *kt = &sp->kt[j];
119 if (kt->flags & KT_SELECTED)
120 return sp;
121 }
122 }
123 return NULL;
124 }
125
126 subpath *
plate_new_sp(plate * p)127 plate_new_sp(plate *p)
128 {
129 subpath *sp;
130 if (p->n_sp == p->n_sp_max)
131 p->sp = zrenew(subpath, p->sp, p->n_sp_max <<= 1);
132 sp = &p->sp[p->n_sp++];
133 sp->n_kt = 0;
134 sp->n_kt_max = 4;
135 sp->kt = znew(knot, sp->n_kt_max);
136 sp->closed = 0;
137 return sp;
138 }
139
140 static int
try_close_sp(subpath * sp,int ix,int force)141 try_close_sp(subpath *sp, int ix, int force)
142 {
143 int n_kt = sp->n_kt;
144
145 if (sp->closed) return 0;
146 if (n_kt < 3) return 0;
147 if (!force) {
148 if (ix != 0 && ix != n_kt - 1) return 0;
149 if (!(sp->kt[n_kt - 1 - ix].flags & KT_SELECTED)) return 0;
150 }
151 sp->closed = 1;
152 return 1;
153 }
154
155 void
plate_press(plate * p,double x,double y,press_mod mods)156 plate_press(plate *p, double x, double y, press_mod mods)
157 {
158 int i, j;
159 subpath *sp;
160 knot *kt;
161 const double srad = 5;
162 kt_flags new_kt_flags = KT_SELECTED;
163
164 if (p->mmode == MOUSE_MODE_ADD_CORNER)
165 new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_OPEN : KT_CORNER;
166 else if (p->mmode == MOUSE_MODE_ADD_CORNU)
167 new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_CORNU;
168 else if (p->mmode == MOUSE_MODE_ADD_LEFT)
169 new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_LEFT;
170 else if (p->mmode == MOUSE_MODE_ADD_RIGHT)
171 new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_RIGHT;
172 else
173 new_kt_flags |= (mods & PRESS_MOD_CTRL) ? KT_CORNER : KT_OPEN;
174
175 p->x0 = x;
176 p->y0 = y;
177
178 /* find an existing point to select, if any */
179 for (i = 0; i < p->n_sp; i++) {
180 sp = &p->sp[i];
181
182 for (j = 0; j < sp->n_kt; j++) {
183 kt = &sp->kt[j];
184 if (hypot(kt->x - x, kt->y - y) < srad) {
185 int was_closed = try_close_sp(sp, j, mods & PRESS_MOD_DOUBLE);
186 if (mods & PRESS_MOD_SHIFT) {
187 kt->flags ^= KT_SELECTED;
188 } else if (!(kt->flags & KT_SELECTED)) {
189 plate_select_all(p, 0);
190 kt->flags |= KT_SELECTED;
191 }
192 p->description = was_closed ? "Close Path" : NULL;
193 p->motmode = MOTION_MODE_MOVE;
194 return;
195 }
196 }
197 }
198
199 if (p->mmode == MOUSE_MODE_ADD_RIGHT || p->mmode == MOUSE_MODE_ADD_LEFT)
200 p->mmode = p->last_curve_mmode;
201
202
203 #if 1
204 /* test whether the button press was on a curve; if so, insert point */
205 for (i = 0; i < p->n_sp; i++) {
206 bezctx *bc = new_bezctx_hittest(x, y);
207 int knot_idx;
208
209 sp = &p->sp[i];
210 free_spiro(draw_subpath(sp, bc));
211 if (bezctx_hittest_report(bc, &knot_idx) < srad) {
212 knot *kt;
213
214 if (sp->n_kt == sp->n_kt_max)
215 sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
216 plate_select_all(p, 0);
217 kt = &sp->kt[knot_idx + 1];
218 memmove(&kt[1], kt, (sp->n_kt - knot_idx - 1) * sizeof(knot));
219 sp->n_kt++;
220 kt->x = x;
221 kt->y = y;
222 kt->flags = new_kt_flags;
223 p->description = "Insert Point";
224 p->motmode = MOTION_MODE_MOVE;
225 return;
226 }
227 }
228 #endif
229
230 if (p->mmode == MOUSE_MODE_SELECT) {
231 plate_select_all(p, 0);
232 p->sel_x0 = x;
233 p->sel_y0 = y;
234 p->motmode = MOTION_MODE_SELECT;
235 return;
236 }
237
238 sp = plate_find_selected_sp(p);
239 if (sp == NULL || sp->closed) {
240 sp = plate_new_sp(p);
241 p->description = p->n_sp > 1 ? "New Subpath" : "New Path";
242 }
243
244 if (sp->n_kt == sp->n_kt_max)
245 sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
246 plate_select_all(p, 0);
247 kt = &sp->kt[sp->n_kt++];
248 kt->x = x;
249 kt->y = y;
250 kt->flags = new_kt_flags;
251 if (p->description == NULL)
252 p->description = "Add Point";
253 p->motmode = MOTION_MODE_MOVE;
254 }
255
256 void
plate_motion_move(plate * p,double x,double y)257 plate_motion_move(plate *p, double x, double y)
258 {
259 int i, j, n = 0;
260 double dx, dy;
261
262 dx = x - p->x0;
263 dy = y - p->y0;
264 p->x0 = x;
265 p->y0 = y;
266
267 for (i = 0; i < p->n_sp; i++) {
268 subpath *sp = &p->sp[i];
269
270 for (j = 0; j < sp->n_kt; j++) {
271 knot *kt = &sp->kt[j];
272 if (kt->flags & KT_SELECTED) {
273 kt->x += dx;
274 kt->y += dy;
275 n++;
276 }
277 }
278 }
279 p->description = n == 1 ? "Move Point" : "Move Points";
280 }
281
282 void
plate_motion_select(plate * p,double x1,double y1)283 plate_motion_select(plate *p, double x1, double y1)
284 {
285 int i, j;
286 double x0 = p->sel_x0;
287 double y0 = p->sel_y0;
288
289 #ifdef VERBOSE
290 printf("plate_motion_select %g %g\n", x1, y1);
291 #endif
292 p->x0 = x1;
293 p->y0 = y1;
294
295 if (x0 > x1) {
296 double tmp = x1;
297 x1 = x0;
298 x0 = tmp;
299 }
300 if (y0 > y1) {
301 double tmp = y1;
302 y1 = y0;
303 y0 = tmp;
304 }
305
306 for (i = 0; i < p->n_sp; i++) {
307 subpath *sp = &p->sp[i];
308
309 for (j = 0; j < sp->n_kt; j++) {
310 knot *kt = &sp->kt[j];
311 kt->flags &= ~KT_SELECTED;
312 if (kt->x >= x0 && kt->x <= x1 &&
313 kt->y >= y0 && kt->y <= y1)
314 kt->flags |= KT_SELECTED;
315 }
316 }
317 }
318
plate_unpress(plate * p)319 void plate_unpress(plate *p)
320 {
321 p->motmode = MOTION_MODE_IDLE;
322 }
323
324 void
plate_toggle_corner(plate * p)325 plate_toggle_corner(plate *p)
326 {
327 int i, j;
328
329 /* find an existing point to select, if any */
330 for (i = 0; i < p->n_sp; i++) {
331 subpath *sp = &p->sp[i];
332
333 for (j = 0; j < sp->n_kt; j++) {
334 knot *kt = &sp->kt[j];
335 if (kt->flags & KT_SELECTED) {
336 if (kt->flags & KT_CORNER) {
337 kt->flags |= KT_OPEN;
338 kt->flags &= ~KT_CORNER;
339 } else {
340 kt->flags &= ~KT_OPEN;
341 kt->flags |= KT_CORNER;
342 }
343 }
344 }
345 }
346 }
347
348 void
plate_delete_pt(plate * p)349 plate_delete_pt(plate *p)
350 {
351 int i, j;
352
353 /* find an existing point to select, if any */
354 for (i = 0; i < p->n_sp; i++) {
355 subpath *sp = &p->sp[i];
356
357 for (j = 0; j < sp->n_kt; j++) {
358 knot *kt = &sp->kt[j];
359 if (kt->flags & KT_SELECTED) {
360 memmove(kt, &kt[1], (sp->n_kt - j - 1) * sizeof(knot));
361 sp->n_kt--;
362 if (sp->n_kt < 3) sp->closed = 0;
363 j--;
364 }
365 }
366 }
367 }
368
369 /* Note: caller is responsible for freeing returned spiro_seg. */
370 spiro_seg *
draw_subpath(const subpath * sp,bezctx * bc)371 draw_subpath(const subpath *sp, bezctx *bc)
372 {
373 int n = sp->n_kt;
374 int i;
375 spiro_cp *path;
376 spiro_seg *s = NULL;
377
378 if (n > 1) {
379 path = znew(spiro_cp, n);
380
381 for (i = 0; i < n; i++) {
382 kt_flags flags = sp->kt[i].flags;
383 path[i].x = sp->kt[i].x;
384 path[i].y = sp->kt[i].y;
385 path[i].ty = !sp->closed && i == 0 ? '{' :
386 !sp->closed && i == n - 1 ? '}' :
387 (flags & KT_OPEN) ? 'o' :
388 (flags & KT_LEFT) ? '[' :
389 (flags & KT_RIGHT) ? ']' :
390 (flags & KT_CORNU) ? 'c' :
391 'v';
392 }
393
394 s = run_spiro(path, n);
395 spiro_to_bpath(s, n, bc);
396
397 zfree(path);
398 }
399 return s;
400 }
401
402 int
file_write_plate(const char * fn,const plate * p)403 file_write_plate(const char *fn, const plate *p)
404 {
405 FILE *f = fopen(fn, "w");
406 int i, j;
407 int st;
408
409 if (f == NULL)
410 return -1;
411 st = fprintf(f, "(plate\n");
412 for (i = 0; i < p->n_sp; i++) {
413 subpath *sp = &p->sp[i];
414 for (j = 0; j < sp->n_kt; j++) {
415 kt_flags kf = sp->kt[j].flags;
416 const char *cmd;
417
418 if (kf & KT_OPEN) cmd = "o";
419 else if (kf & KT_CORNER) cmd = "v";
420 else if (kf & KT_CORNU) cmd = "c";
421 else if (kf & KT_LEFT) cmd = "[";
422 else if (kf & KT_RIGHT) cmd = "]";
423 st = fprintf(f, " (%s %g %g)\n", cmd, sp->kt[j].x, sp->kt[j].y);
424 if (st < 0) break;
425 }
426 if (st < 0) break;
427 if (sp->closed) {
428 st = fprintf(f, " (z)\n");
429 }
430 if (st < 0) break;
431 }
432 if (st >= 0)
433 st = fprintf(f, ")\n");
434 if (st >= 0)
435 st = fclose(f);
436 return st < 0 ? -1 : 0;
437 }
438
439 static int
file_read_plate_inner(sexp_reader * sr,plate * p)440 file_read_plate_inner(sexp_reader *sr, plate *p)
441 {
442 subpath *sp = NULL;
443
444 sexp_token(sr);
445 if (sr->singlechar != '(') return -1;
446 sexp_token(sr);
447 if (strcmp(sr->tokbuf, "plate")) return -1;
448 for (;;) {
449 sexp_token(sr);
450 if (sr->singlechar == ')') break;
451 else if (sr->singlechar == '(') {
452 int cmd;
453
454 sexp_token(sr);
455 cmd = sr->singlechar;
456 if (cmd == 'o' || cmd == 'v' || cmd == '[' || cmd == ']' ||
457 cmd == 'c') {
458 double x, y;
459 knot *kt;
460
461 sexp_token(sr);
462 if (!sr->is_double) return -1;
463 x = sr->d;
464 sexp_token(sr);
465 if (!sr->is_double) return -1;
466 y = sr->d;
467 sexp_token(sr);
468 if (sr->singlechar != ')') return -1;
469
470 if (sp == NULL || sp->closed)
471 sp = plate_new_sp(p);
472
473 if (sp->n_kt == sp->n_kt_max)
474 sp->kt = zrenew(knot, sp->kt, sp->n_kt_max <<= 1);
475 kt = &sp->kt[sp->n_kt++];
476 kt->x = x;
477 kt->y = y;
478 switch (cmd) {
479 case 'o':
480 kt->flags = KT_OPEN;
481 break;
482 case '[':
483 kt->flags = KT_LEFT;
484 break;
485 case ']':
486 kt->flags = KT_RIGHT;
487 break;
488 case 'c':
489 kt->flags = KT_CORNU;
490 break;
491 default:
492 kt->flags = KT_CORNER;
493 break;
494 }
495 } else if (cmd == 'z') {
496 if (sp == NULL) return -1;
497 sp->closed = 1;
498 sexp_token(sr);
499 if (sr->singlechar != ')') return -1;
500 } else
501 return -1;
502 } else return -1;
503 }
504 return 0;
505 }
506
507 plate *
file_read_plate(const char * fn)508 file_read_plate(const char *fn)
509 {
510 FILE *f = fopen(fn, "r");
511 plate *p;
512 sexp_reader sr;
513
514 if (f == NULL)
515 return NULL;
516 sr.f = f;
517 p = new_plate();
518 if (file_read_plate_inner(&sr, p)) {
519 free_plate(p);
520 p = NULL;
521 }
522 fclose(f);
523 p->mmode = MOUSE_MODE_SELECT;
524 p->motmode = MOTION_MODE_IDLE;
525 return p;
526 }
527