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