graphics_regis.c revision 894e0ac8
1/* $XTermId: graphics_regis.c,v 1.26 2014/05/27 00:18:05 tom Exp $ */
2
3/*
4 * Copyright 2014 by Ross Combs
5 *
6 *                         All Rights Reserved
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included
17 * in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 * Except as contained in this notice, the name(s) of the above copyright
28 * holders shall not be used in advertising or otherwise to promote the
29 * sale, use or other dealings in this Software without prior written
30 * authorization.
31 */
32
33#include <xterm.h>
34
35#include <stdio.h>
36#include <ctype.h>
37#include <math.h>
38#include <stdlib.h>
39
40#include <data.h>
41#include <VTparse.h>
42#include <ptyx.h>
43
44#include <assert.h>
45#include <graphics.h>
46#include <graphics_regis.h>
47
48/* get rid of shadowing warnings (we will not draw Bessel functions) */
49#define y1 my_y1
50#define y0 my_y0
51
52#undef DEBUG_BEZIER
53#undef DEBUG_SPLINE_SEGMENTS
54#undef DEBUG_SPLINE_POINTS
55#undef DEBUG_SPLINE_WITH_ROTATION
56#undef DEBUG_SPLINE_WITH_OVERDRAW
57#undef DEBUG_ARC_CENTER
58#undef DEBUG_ARC_START
59#undef DEBUG_ARC_END
60
61#define ITERATIONS_BEFORE_REFRESH 100U
62/* *INDENT-OFF* */
63typedef struct RegisWriteControls {
64    unsigned        pv_multiplier;
65    unsigned        pattern;
66    unsigned        pattern_multiplier;
67    unsigned        invert_pattern;
68    unsigned        plane_mask;
69    unsigned        write_style;
70    RegisterNum     foreground;
71    unsigned        shading_enabled;
72    char            shading_character;
73    int             shading_reference;
74    unsigned        shading_reference_dim;
75} RegisWriteControls;
76
77typedef struct RegisDataFragment {
78    char const     *start;
79    unsigned        pos;
80    unsigned        len;
81} RegisDataFragment;
82
83typedef enum RegisParseLevel {
84    INPUT,
85    OPTIONSET
86} RegisParseLevel;
87/* *INDENT-ON* */
88
89#define CURVE_POSITION_ARC_EDGE     0U
90#define CURVE_POSITION_ARC_CENTER   1U
91#define CURVE_POSITION_OPEN_CURVE   2U
92#define CURVE_POSITION_CLOSED_CURVE 3U
93
94#define MAX_INPUT_CURVE_POINTS 16U
95#define MAX_CURVE_POINTS (MAX_INPUT_CURVE_POINTS + 4U)
96
97typedef struct RegisParseState {
98    RegisDataFragment input;
99    RegisDataFragment optionset;
100    char *temp;
101    unsigned templen;
102    RegisParseLevel level;
103    char command;
104    char option;
105    /* curve options */
106    int curve_mode;
107    int arclen;
108    int x_points[MAX_CURVE_POINTS];
109    int y_points[MAX_CURVE_POINTS];
110    unsigned num_points;
111} RegisParseState;
112
113typedef struct RegisGraphicsContext {
114    Graphic *graphic;
115    int terminal_id;
116    unsigned all_planes;
117    RegisterNum background;
118    RegisWriteControls persistent_write_controls;
119    RegisWriteControls temporary_write_controls;
120    int graphics_output_cursor_x;
121    int graphics_output_cursor_y;
122    unsigned pattern_count;
123    unsigned pattern_bit;
124} RegisGraphicsContext;
125
126#define MAX_PATTERN_BITS 8U
127
128#define WRITE_STYLE_OVERLAY 1U
129#define WRITE_STYLE_REPLACE 2U
130#define WRITE_STYLE_COMPLEMENT 3U
131#define WRITE_STYLE_ERASE 4U
132
133#define WRITE_SHADING_REF_Y 0U
134#define WRITE_SHADING_REF_X 1U
135
136#define ROT_LEFT_N(V, N) ( (((V) << ((N) & 3U )) & 255U) | ((V) >> (8U - ((N) & 3U))) )
137#define ROT_LEFT(V) ( (((V) << 1U) & 255U) | ((V) >> 7U) )
138
139static int
140ifloor(double d)
141{
142    double dl = floor(d);
143    return (int) dl;
144}
145
146static int
147isqrt(double d)
148{
149    double dl = sqrt(d);
150    return (int) dl;
151}
152
153static void
154draw_regis_pixel(RegisGraphicsContext *context, int x, int y)
155{
156    unsigned color = 0;
157
158    switch (context->temporary_write_controls.write_style) {
159    case WRITE_STYLE_OVERLAY:
160	/* update pixels with foreground when pattern is 1, unchanged when pattern is 0 */
161	if (!(context->temporary_write_controls.pattern & context->pattern_bit)) {
162	    return;
163	}
164
165	if (context->temporary_write_controls.invert_pattern) {
166	    color = context->background;
167	} else {
168	    color = context->temporary_write_controls.foreground;
169	}
170	break;
171
172    case WRITE_STYLE_REPLACE:
173	/* update pixels with foreground when pattern is 1, background when pattern is 0 */
174	{
175	    unsigned fg, bg;
176
177	    if (context->temporary_write_controls.invert_pattern) {
178		fg = context->background;
179		bg = context->temporary_write_controls.foreground;
180	    } else {
181		fg = context->temporary_write_controls.foreground;
182		bg = context->background;
183	    }
184	    color = ((context->temporary_write_controls.pattern &
185		      context->pattern_bit)
186		     ? fg
187		     : bg);
188	}
189	break;
190
191    case WRITE_STYLE_COMPLEMENT:
192	/* update pixels with background when pattern is 1, unchanged when pattern is 0 */
193	color = read_pixel(context->graphic, x, y);
194	if (color == COLOR_HOLE)
195	    color = context->background;
196	color = color ^ context->all_planes;
197	break;
198
199    case WRITE_STYLE_ERASE:
200	/* update pixels with foreground */
201	if (context->temporary_write_controls.invert_pattern) {
202	    color = context->temporary_write_controls.foreground;
203	} else {
204	    color = context->background;
205	}
206	break;
207    }
208
209    draw_solid_pixel(context->graphic, x, y, color);
210}
211
212static void
213fill_to_pixel(RegisGraphicsContext *context, int x, int y)
214{
215    unsigned dim = context->temporary_write_controls.shading_reference_dim;
216    int ref = context->temporary_write_controls.shading_reference;
217
218    if (dim == WRITE_SHADING_REF_X) {
219	int delta = x > ref ? 1 : -1;
220	int curr_x;
221
222	context->pattern_bit = 1U << (((unsigned) y) & 7U);
223	for (curr_x = ref; curr_x != x + delta; curr_x += delta) {
224	    draw_regis_pixel(context, curr_x, y);
225	}
226    } else {
227	int delta = y > ref ? 1 : -1;
228	int curr_y;
229
230	for (curr_y = ref; curr_y != y + delta; curr_y += delta) {
231	    context->pattern_bit = 1U << (((unsigned) curr_y) & 7U);
232	    draw_regis_pixel(context, x, curr_y);
233	}
234    }
235}
236
237static void
238draw_patterned_pixel(RegisGraphicsContext *context, int x, int y)
239{
240    if (context->temporary_write_controls.shading_enabled) {
241	if (context->temporary_write_controls.shading_character != '\0') {
242	    /* FIXME: handle character fills */
243	    TRACE(("pixel shaded with character\n"));
244	} else {
245	    fill_to_pixel(context, x, y);
246	}
247    } else {
248	if (context->pattern_count >= context->temporary_write_controls.pattern_multiplier) {
249	    context->pattern_count = 0U;
250	    context->pattern_bit = ROT_LEFT(context->pattern_bit);
251	}
252	context->pattern_count++;
253
254	draw_regis_pixel(context, x, y);
255    }
256}
257
258static void
259draw_patterned_line(RegisGraphicsContext *context, int x1, int y1, int x2, int y2)
260{
261    int x, y;
262    int dx, dy;
263    int dir, diff;
264
265    dx = abs(x1 - x2);
266    dy = abs(y1 - y2);
267
268    if (dx > dy) {
269	if (x1 > x2) {
270	    int tmp;
271	    EXCHANGE(x1, x2, tmp);
272	    EXCHANGE(y1, y2, tmp);
273	}
274	if (y1 < y2)
275	    dir = 1;
276	else if (y1 > y2)
277	    dir = -1;
278	else
279	    dir = 0;
280
281	diff = 0;
282	y = y1;
283	for (x = x1; x <= x2; x++) {
284	    if (diff >= dx) {
285		diff -= dx;
286		y += dir;
287	    }
288	    diff += dy;
289	    draw_patterned_pixel(context, x, y);
290	}
291    } else {
292	if (y1 > y2) {
293	    int tmp;
294	    EXCHANGE(y1, y2, tmp);
295	    EXCHANGE(x1, x2, tmp);
296	}
297	if (x1 < x2)
298	    dir = 1;
299	else if (x1 > x2)
300	    dir = -1;
301	else
302	    dir = 0;
303
304	diff = 0;
305	x = x1;
306	for (y = y1; y <= y2; y++) {
307	    if (diff >= dy) {
308		diff -= dy;
309		x += dir;
310	    }
311	    diff += dx;
312	    draw_patterned_pixel(context, x, y);
313	}
314    }
315}
316
317typedef struct {
318    int dxx;
319    int dxy;
320    int dyx;
321    int dyy;
322} quadmap_coords;
323
324static void
325draw_patterned_arc(RegisGraphicsContext *context,
326		   int cx, int cy,
327		   int ex, int ey,
328		   int a_start, int a_length,
329		   int *ex_final, int *ey_final)
330{
331    const double third = hypot((double) (cx - ex), (double) (cy - ey));
332    const int radius = (int) third;
333    const int ra = radius;
334    const int rb = radius;
335    const quadmap_coords neg_quadmap[4] =
336    {
337	{-1, 0, 0, +1},
338	{0, -1, -1, 0},
339	{+1, 0, 0, -1},
340	{0, +1, +1, 0},
341    };
342    const quadmap_coords pos_quadmap[4] =
343    {
344	{-1, 0, 0, -1},
345	{0, -1, +1, 0},
346	{+1, 0, 0, +1},
347	{0, +1, -1, 0},
348    };
349    const quadmap_coords *quadmap;
350    int total_points;
351    int points_start, points_stop;
352    int points;
353    unsigned iterations;
354    int quad;
355    long rx, ry;
356    long dx, dy;
357    int x, y;
358    long e2;
359    long error;
360
361    TRACE(("a_length=%d a_start=%d\n", a_length, a_start));
362    if (a_length == 0)
363	return;
364    if (a_length > 0) {
365	quadmap = pos_quadmap;
366    } else {
367	quadmap = neg_quadmap;
368	if (a_start != 0)
369	    a_start = 360 - a_start;
370    }
371
372    rx = -ra;
373    ry = 0;
374    e2 = rb;
375    dx = (2 * rx + 1) * e2 * e2;
376    dy = rx * rx;
377    error = dx + dy;
378    total_points = 0;
379    do {
380	total_points += 4;
381	e2 = 2 * error;
382	if (e2 >= dx) {
383	    rx++;
384	    dx += 2 * rb * rb;
385	    error += dx;
386	}
387	if (e2 <= dy) {
388	    ry++;
389	    dy += 2 * ra * ra;
390	    error += dy;
391	}
392    }
393    while (rx <= 0);
394    points_start = (total_points * a_start) / 360;
395    points_stop = (total_points * a_start +
396		   total_points * abs(a_length) + 359) / 360;
397    TRACE(("drawing arc with %d points from %d angle for %d degrees (from point %d to %d out of %d)\n",
398	   total_points, a_start, a_length, points_start, points_stop, total_points));
399
400    points = 0;
401    for (iterations = 0U; iterations < 8U; iterations++) {
402	quad = iterations & 0x3;
403
404	rx = -ra;
405	ry = 0;
406	e2 = rb;
407	dx = (2 * rx + 1) * e2 * e2;
408	dy = rx * rx;
409	error = dx + dy;
410	do {
411	    if (points >= points_start && points <= points_stop) {
412		x = (int) (cx +
413			   quadmap[quad].dxx * rx +
414			   quadmap[quad].dxy * ry);
415		y = (int) (cy +
416			   quadmap[quad].dyx * rx +
417			   quadmap[quad].dyy * ry);
418		draw_patterned_pixel(context, x, y);
419		if (ex_final)
420		    *ex_final = x;
421		if (ey_final)
422		    *ey_final = y;
423	    }
424	    points++;
425
426	    e2 = 2 * error;
427	    if (e2 >= dx) {
428		rx++;
429		dx += 2 * rb * rb;
430		error += dx;
431	    }
432	    if (e2 <= dy) {
433		ry++;
434		dy += 2 * ra * ra;
435		error += dy;
436	    }
437	}
438	while (rx <= 0);
439    }
440}
441
442/*
443 * The plot* functions are based on optimized rasterization primitves written by Zingl Alois.
444 * See http://members.chello.at/easyfilter/bresenham.html
445 */
446
447/*
448 * FIXME:
449 * This is a terrible temporary hack.  The plot functions below can be adapted
450 * to work like the other rasterization functions but there's no point in doing
451 * that until we know we don't have to write something completely different.
452 */
453static RegisGraphicsContext *global_context;
454static void
455setPixel(int x, int y)
456{
457    draw_patterned_pixel(global_context, x, y);
458}
459
460static void
461plotLine(int x0, int y0, int x1, int y1)
462{
463    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
464    int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
465    int err = dx + dy, e2;	/* error value e_xy */
466
467    for (;;) {			/* loop */
468	setPixel(x0, y0);
469	e2 = 2 * err;
470	if (e2 >= dy) {		/* e_xy+e_x > 0 */
471	    if (x0 == x1)
472		break;
473	    err += dy;
474	    x0 += sx;
475	}
476	if (e2 <= dx) {		/* e_xy+e_y < 0 */
477	    if (y0 == y1)
478		break;
479	    err += dx;
480	    y0 += sy;
481	}
482    }
483}
484
485static void
486plotQuadBezierSeg(int x0, int y0, int x1, int y1, int x2, int y2)
487{				/* plot a limited quadratic Bezier segment */
488    int sx = x2 - x1;
489    int sy = y2 - y1;
490    long xx = (x0 - x1);	/* relative values for checks */
491    long yy = (y0 - y1);
492    long xy;
493    double dx, dy, err;
494    double cur = (double) (xx * sy - yy * sx);	/* curvature */
495
496    assert(xx * sx <= 0 && yy * sy <= 0);	/* sign of gradient must not change */
497
498    if (sx * (long) sx + sy * (long) sy > xx * xx + yy * yy) {	/* begin with longer part */
499	x2 = x0;
500	x0 = sx + x1;
501	y2 = y0;
502	y0 = sy + y1;
503	cur = -cur;		/* swap P0 P2 */
504    }
505    if (cur != 0) {		/* no straight line */
506	xx += sx;
507	xx *= sx = x0 < x2 ? 1 : -1;	/* x step direction */
508	yy += sy;
509	yy *= sy = y0 < y2 ? 1 : -1;	/* y step direction */
510	xy = 2 * xx * yy;
511	xx *= xx;
512	yy *= yy;		/* differences 2nd degree */
513	if (cur * sx * sy < 0) {	/* negated curvature? */
514	    xx = -xx;
515	    yy = -yy;
516	    xy = -xy;
517	    cur = -cur;
518	}
519	/* differences 1st degree */
520	dx = ((4.0 * sy * cur * (x1 - x0)) + (double) xx) - (double) xy;
521	dy = ((4.0 * sx * cur * (y0 - y1)) + (double) yy) - (double) xy;
522	xx += xx;
523	yy += yy;
524	err = dx + dy + (double) xy;	/* error 1st step */
525	do {
526	    setPixel(x0, y0);	/* plot curve */
527	    if (x0 == x2 && y0 == y2)
528		return;		/* last pixel -> curve finished */
529	    y1 = (2 * err) < dx;	/* save value for test of y step */
530	    if ((2 * err) > dy) {
531		x0 += sx;
532		dx -= (double) xy;
533		dy += (double) yy;
534		err += dy;
535	    }			/* x step */
536	    if (y1) {
537		y0 += sy;
538		dy -= (double) xy;
539		dx += (double) xx;
540		err += dx;
541	    }			/* y step */
542	} while (dy < 0 && dx > 0);	/* gradient negates -> algorithm fails */
543    }
544    plotLine(x0, y0, x2, y2);	/* plot remaining part to end */
545}
546
547#if 0
548static void
549plotQuadBezier(int x0, int y0, int x1, int y1, int x2, int y2)
550{				/* plot any quadratic Bezier curve */
551    int x = x0 - x1;
552    int y = y0 - y1;
553    double t = x0 - 2 * x1 + x2;
554    double r;
555
556    if ((long) x * (x2 - x1) > 0) {	/* horizontal cut at P4? */
557	if ((long) y * (y2 - y1) > 0)	/* vertical cut at P6 too? */
558	    if (fabs((y0 - 2 * y1 + y2) / t * x) > abs(y)) {	/* which first? */
559		x0 = x2;
560		x2 = x + x1;
561		y0 = y2;
562		y2 = y + y1;	/* swap points */
563	    }			/* now horizontal cut at P4 comes first */
564	t = (x0 - x1) / t;
565	r = (1 - t) * ((1 - t) * y0 + 2.0 * t * y1) + t * t * y2;	/* By(t=P4) */
566	t = (x0 * x2 - x1 * x1) * t / (x0 - x1);	/* gradient dP4/dx=0 */
567	x = ifloor(t + 0.5);
568	y = ifloor(r + 0.5);
569	r = (y1 - y0) * (t - x0) / (x1 - x0) + y0;	/* intersect P3 | P0 P1 */
570	plotQuadBezierSeg(x0, y0, x, ifloor(r + 0.5), x, y);
571	r = (y1 - y2) * (t - x2) / (x1 - x2) + y2;	/* intersect P4 | P1 P2 */
572	x0 = x1 = x;
573	y0 = y;
574	y1 = ifloor(r + 0.5);	/* P0 = P4, P1 = P8 */
575    }
576    if ((long) (y0 - y1) * (y2 - y1) > 0) {	/* vertical cut at P6? */
577	t = y0 - 2 * y1 + y2;
578	t = (y0 - y1) / t;
579	r = (1 - t) * ((1 - t) * x0 + 2.0 * t * x1) + t * t * x2;	/* Bx(t=P6) */
580	t = (y0 * y2 - y1 * y1) * t / (y0 - y1);	/* gradient dP6/dy=0 */
581	x = ifloor(r + 0.5);
582	y = ifloor(t + 0.5);
583	r = (x1 - x0) * (t - y0) / (y1 - y0) + x0;	/* intersect P6 | P0 P1 */
584	plotQuadBezierSeg(x0, y0, ifloor(r + 0.5), y, x, y);
585	r = (x1 - x2) * (t - y2) / (y1 - y2) + x2;	/* intersect P7 | P1 P2 */
586	x0 = x;
587	x1 = ifloor(r + 0.5);
588	y0 = y1 = y;		/* P0 = P6, P1 = P7 */
589    }
590    plotQuadBezierSeg(x0, y0, x1, y1, x2, y2);	/* remaining part */
591}
592#endif
593
594static void
595plotCubicBezierSeg(int x0, int y0,
596		   double x1, double y1,
597		   double x2, double y2,
598		   int x3, int y3)
599{				/* plot limited cubic Bezier segment */
600    int f, fx, fy, tt;
601    int leg = 1;
602    int sx = x0 < x3 ? 1 : -1;
603    int sy = y0 < y3 ? 1 : -1;	/* step direction */
604    double xc = -fabs(x0 + x1 - x2 - x3);
605    double xa = xc - 4 * sx * (x1 - x2);
606    double xb = sx * (x0 - x1 - x2 + x3);
607    double yc = -fabs(y0 + y1 - y2 - y3);
608    double ya = yc - 4 * sy * (y1 - y2);
609    double yb = sy * (y0 - y1 - y2 + y3);
610    double ab, ac, bc, cb, xx, xy, yy, dx, dy, ex, *pxy;
611    double EP = 0.01;
612    /* check for curve restrains */
613    /* slope P0-P1 == P2-P3    and  (P0-P3 == P1-P2      or   no slope change) */
614    assert((x1 - x0) * (x2 - x3) < EP &&
615	   ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP));
616    assert((y1 - y0) * (y2 - y3) < EP &&
617	   ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP));
618
619    if (xa == 0 && ya == 0) {	/* quadratic Bezier */
620	sx = ifloor((3 * x1 - x0 + 1) / 2);
621	sy = ifloor((3 * y1 - y0 + 1) / 2);	/* new midpoint */
622	plotQuadBezierSeg(x0, y0, sx, sy, x3, y3);
623	return;
624    }
625    x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1;	/* line lengths */
626    x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1;
627    do {			/* loop over both ends */
628	ab = xa * yb - xb * ya;
629	ac = xa * yc - xc * ya;
630	bc = xb * yc - xc * yb;
631	ex = ab * (ab + ac - 3 * bc) + ac * ac;		/* P0 part of self-intersection loop? */
632	f = ((ex > 0.0)
633	     ? 1
634	     : isqrt(1 + 1024 / x1));	/* calculate resolution */
635	ab *= f;
636	ac *= f;
637	bc *= f;
638	ex *= f * f;		/* increase resolution */
639	xy = 9 * (ab + ac + bc) / 8;
640	cb = 8 * (xa - ya);	/* init differences of 1st degree */
641	dx = 27 * (8 * ab * (yb * yb - ya * yc) +
642		   ex * (ya + 2 * yb + yc)) / 64 - ya * ya * (xy - ya);
643	dy = 27 * (8 * ab * (xb * xb - xa * xc) -
644		   ex * (xa + 2 * xb + xc)) / 64 - xa * xa * (xy + xa);
645	/* init differences of 2nd degree */
646	xx = 3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) -
647		  ya * (3 * ac * (ya + yb) + ya * cb)) / 4;
648	yy = 3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) -
649		  xa * (3 * ac * (xa + xb) + xa * cb)) / 4;
650	xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + cb);
651	ac = ya * ya;
652	cb = xa * xa;
653	xy = 3 * (xy + 9 * f * (cb * yb * yc - xb * xc * ac) -
654		  18 * xb * yb * ab) / 8;
655
656	if (ex < 0) {		/* negate values if inside self-intersection loop */
657	    dx = -dx;
658	    dy = -dy;
659	    xx = -xx;
660	    yy = -yy;
661	    xy = -xy;
662	    ac = -ac;
663	    cb = -cb;
664	}			/* init differences of 3rd degree */
665	ab = 6 * ya * ac;
666	ac = -6 * xa * ac;
667	bc = 6 * ya * cb;
668	cb = -6 * xa * cb;
669	dx += xy;
670	ex = dx + dy;
671	dy += xy;		/* error of 1st step */
672
673	for (pxy = &xy, fx = fy = f; x0 != x3 && y0 != y3;) {
674	    setPixel(x0, y0);	/* plot curve */
675	    do {		/* move sub-steps of one pixel */
676		if (dx > *pxy || dy < *pxy)
677		    goto exit;	/* confusing values */
678		y1 = 2 * ex - dy;	/* save value for test of y step */
679		if (2 * ex >= dx) {	/* x sub-step */
680		    fx--;
681		    ex += dx += xx;
682		    dy += xy += ac;
683		    yy += bc;
684		    xx += ab;
685		}
686		if (y1 <= 0) {	/* y sub-step */
687		    fy--;
688		    ex += dy += yy;
689		    dx += xy += bc;
690		    xx += ac;
691		    yy += cb;
692		}
693	    } while (fx > 0 && fy > 0);		/* pixel complete? */
694	    if (2 * fx <= f) {
695		x0 += sx;
696		fx += f;
697	    }			/* x step */
698	    if (2 * fy <= f) {
699		y0 += sy;
700		fy += f;
701	    }			/* y step */
702	    if (pxy == &xy && dx < 0 && dy > 0)
703		pxy = &EP;	/* pixel ahead valid */
704	}
705      exit:
706	EXCHANGE(x0, x3, tt);
707	sx = -sx;
708	xb = -xb;		/* swap legs */
709	EXCHANGE(y0, y3, tt);
710	sy = -sy;
711	yb = -yb;
712	x1 = x2;
713    } while (leg--);		/* try other end */
714    plotLine(x0, y0, x3, y3);	/* remaining part in case of cusp or crunode */
715}
716
717static void
718plotCubicBezier(int x0, int y0, int x1, int y1,
719		int x2, int y2, int x3, int y3)
720{				/* plot any cubic Bezier curve */
721    int n = 0, i = 0;
722    long xc = x0 + x1 - x2 - x3;
723    long xa = xc - 4 * (x1 - x2);
724    long xb = x0 - x1 - x2 + x3;
725    long xd = xb + 4 * (x1 + x2);
726    long yc = y0 + y1 - y2 - y3;
727    long ya = yc - 4 * (y1 - y2);
728    long yb = y0 - y1 - y2 + y3;
729    long yd = yb + 4 * (y1 + y2);
730    double fx0 = x0, fx1, fx2, fx3, fy0 = y0, fy1, fy2, fy3;
731    double t1 = (double) (xb * xb - xa * xc), t2, t[5];
732
733#ifdef DEBUG_BEZIER
734    printf("plotCubicBezier(%d,%d, %d,%d, %d,%d, %d,%d\n",
735	   x0, y0, x1, y1, x2, y2, x3, y3);
736#endif
737    /* sub-divide curve at gradient sign changes */
738    if (xa == 0) {		/* horizontal */
739	if (labs(xc) < 2 * labs(xb))
740	    t[n++] = (double) xc / (2.0 * (double) xb);		/* one change */
741    } else if (t1 > 0.0) {	/* two changes */
742	t2 = sqrt(t1);
743	t1 = ((double) xb - t2) / (double) xa;
744	if (fabs(t1) < 1.0)
745	    t[n++] = t1;
746	t1 = ((double) xb + t2) / (double) xa;
747	if (fabs(t1) < 1.0)
748	    t[n++] = t1;
749    }
750    t1 = (double) (yb * yb - ya * yc);
751    if (ya == 0) {		/* vertical */
752	if (labs(yc) < 2 * labs(yb))
753	    t[n++] = (double) yc / (2.0 * (double) yb);		/* one change */
754    } else if (t1 > 0.0) {	/* two changes */
755	t2 = sqrt(t1);
756	t1 = ((double) yb - t2) / (double) ya;
757	if (fabs(t1) < 1.0)
758	    t[n++] = t1;
759	t1 = ((double) yb + t2) / (double) ya;
760	if (fabs(t1) < 1.0)
761	    t[n++] = t1;
762    }
763    for (i = 1; i < n; i++)	/* bubble sort of 4 points */
764	if ((t1 = t[i - 1]) > t[i]) {
765	    t[i - 1] = t[i];
766	    t[i] = t1;
767	    i = 0;
768	}
769
770    t1 = -1.0;
771    t[n] = 1.0;			/* begin / end point */
772    for (i = 0; i <= n; i++) {	/* plot each segment separately */
773	t2 = t[i];		/* sub-divide at t[i-1], t[i] */
774	fx1 = (t1 * (t1 * (double) xb - (double) (2 * xc)) -
775	       t2 * (t1 * (t1 * (double) xa - (double) (2 * xb)) + (double)
776		     xc) + (double) xd) / 8 - fx0;
777	fy1 = (t1 * (t1 * (double) yb - (double) (2 * yc)) -
778	       t2 * (t1 * (t1 * (double) ya - (double) (2 * yb)) + (double)
779		     yc) + (double) yd) / 8 - fy0;
780	fx2 = (t2 * (t2 * (double) xb - (double) (2 * xc)) -
781	       t1 * (t2 * (t2 * (double) xa - (double) (2 * xb)) + (double)
782		     xc) + (double) xd) / 8 - fx0;
783	fy2 = (t2 * (t2 * (double) yb - (double) (2 * yc)) -
784	       t1 * (t2 * (t2 * (double) ya - (double) (2 * yb)) + (double)
785		     yc) + (double) yd) / 8 - fy0;
786	fx0 -= fx3 = (t2 * (t2 * ((double) (3 * xb) - t2 * (double) xa) -
787			    (double) (3 * xc)) + (double) xd) / 8;
788	fy0 -= fy3 = (t2 * (t2 * ((double) (3 * yb) - t2 * (double) ya) -
789			    (double) (3 * yc)) + (double) yd) / 8;
790	x3 = ifloor(fx3 + 0.5);
791	y3 = ifloor(fy3 + 0.5);	/* scale bounds to int */
792	if (fx0 != 0.0) {
793	    fx1 *= fx0 = (x0 - x3) / fx0;
794	    fx2 *= fx0;
795	}
796	if (fy0 != 0.0) {
797	    fy1 *= fy0 = (y0 - y3) / fy0;
798	    fy2 *= fy0;
799	}
800	if (x0 != x3 || y0 != y3)	/* segment t1 - t2 */
801	    plotCubicBezierSeg(x0, y0,
802			       x0 + fx1, y0 + fy1,
803			       x0 + fx2, y0 + fy2,
804			       x3, y3);
805	x0 = x3;
806	y0 = y3;
807	fx0 = fx3;
808	fy0 = fy3;
809	t1 = t2;
810    }
811}
812
813#if 0
814static void
815plotQuadSpline(int n, int x[], int y[], int skip_segments)
816{				/* plot quadratic spline, destroys input arrays x,y */
817#define M_MAX 12
818    double mi = 1, m[M_MAX];	/* diagonal constants of matrix */
819    int i, x0, y0, x1, y1, x2, y2;
820#ifdef DEBUG_SPLINE_SEGMENTS
821    int color = 0;
822#endif
823
824    assert(n > 1);		/* need at least 3 points P[0]..P[n] */
825
826#ifdef DEBUG_SPLINE_POINTS
827    {
828	int save_pattern;
829
830	i = 0;
831	global_context->temporary_write_controls.foreground = 11;
832	save_pattern = global_context->temporary_write_controls.pattern;
833	global_context->temporary_write_controls.pattern = 0xff;
834	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
835			   360, NULL, NULL);
836	i++;
837	global_context->temporary_write_controls.foreground = 15;
838	for (; i < n; i++) {
839	    draw_patterned_arc(global_context,
840			       x[i], y[i],
841			       x[i] + 2, y[i],
842			       0, 360, NULL, NULL);
843	}
844	global_context->temporary_write_controls.foreground = 10;
845	draw_patterned_arc(global_context, x[i], y[n], x[i] + 2, y[i], 0,
846			   360, NULL, NULL);
847	global_context->temporary_write_controls.pattern = save_pattern;
848    }
849#endif
850
851    x2 = x[n];
852    y2 = y[n];
853
854    x[1] = x0 = 8 * x[1] - 2 * x[0];	/* first row of matrix */
855    y[1] = y0 = 8 * y[1] - 2 * y[0];
856
857    for (i = 2; i < n; i++) {	/* forward sweep */
858	if (i - 2 < M_MAX)
859	    m[i - 2] = mi = 1.0 / (6.0 - mi);
860	x[i] = x0 = ifloor(8 * x[i] - x0 * mi + 0.5);	/* store yi */
861	y[i] = y0 = ifloor(8 * y[i] - y0 * mi + 0.5);
862    }
863    x1 = ifloor((x0 - 2 * x2) / (5.0 - mi) + 0.5);	/* correction last row */
864    y1 = ifloor((y0 - 2 * y2) / (5.0 - mi) + 0.5);
865
866    for (i = n - 2; i > 0; i--) {	/* back substitution */
867	if (i <= M_MAX)
868	    mi = m[i - 1];
869	x0 = ifloor((x[i] - x1) * mi + 0.5);	/* next corner */
870	y0 = ifloor((y[i] - y1) * mi + 0.5);
871#ifdef DEBUG_SPLINE_SEGMENTS
872	color++;
873	global_context->temporary_write_controls.foreground = color;
874#endif
875	if ((n - 2) - i < skip_segments)
876	    plotQuadBezier((x0 + x1) / 2, (y0 + y1) / 2, x1, y1, x2, y2);
877	x2 = (x0 + x1) / 2;
878	x1 = x0;
879	y2 = (y0 + y1) / 2;
880	y1 = y0;
881    }
882#ifdef DEBUG_SPLINE_SEGMENTS
883    color++;
884    global_context->temporary_write_controls.foreground = color;
885#endif
886    if (skip_segments > 0)
887	plotQuadBezier(x[0], y[0], x1, y1, x2, y2);
888}
889#endif
890
891static void
892plotCubicSpline(int n, int x[], int y[], int skip_first_last)
893{
894#define M_MAX 12
895    double mi = 0.25, m[M_MAX];	/* diagonal constants of matrix */
896    int x3, y3, x4, y4;
897    int i, x0, y0, x1, y1, x2, y2;
898#ifdef DEBUG_SPLINE_SEGMENTS
899    int color = 0;
900#endif
901
902    assert(n > 2);		/* need at least 4 points P[0]..P[n] */
903
904#ifdef DEBUG_SPLINE_POINTS
905    {
906	int save_pattern;
907
908	i = 0;
909	global_context->temporary_write_controls.foreground = 11;
910	save_pattern = global_context->temporary_write_controls.pattern;
911	global_context->temporary_write_controls.pattern = 0xff;
912	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
913			   360, NULL, NULL);
914	i++;
915	global_context->temporary_write_controls.foreground = 15;
916	for (; i < n; i++) {
917	    draw_patterned_arc(global_context,
918			       x[i], y[i],
919			       x[i] + 2, y[i],
920			       0, 360, NULL, NULL);
921	}
922	global_context->temporary_write_controls.foreground = 10;
923	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
924			   360, NULL, NULL);
925	global_context->temporary_write_controls.pattern = save_pattern;
926    }
927#endif
928
929    x3 = x[n - 1];
930    y3 = y[n - 1];
931    x4 = x[n];
932    y4 = y[n];
933
934    x[1] = x0 = 12 * x[1] - 3 * x[0];	/* first row of matrix */
935    y[1] = y0 = 12 * y[1] - 3 * y[0];
936
937    for (i = 2; i < n; i++) {	/* forward sweep */
938	if (i - 2 < M_MAX)
939	    m[i - 2] = mi = 0.25 / (2.0 - mi);
940	x[i] = x0 = ifloor(12 * x[i] - 2 * x0 * mi + 0.5);
941	y[i] = y0 = ifloor(12 * y[i] - 2 * y0 * mi + 0.5);
942    }
943    x2 = ifloor((x0 - 3 * x4) / (7 - 4 * mi) + 0.5);	/* correct last row */
944    printf("y0=%d, y4=%d mi=%g\n", y0, y4, mi);
945    y2 = ifloor((y0 - 3 * y4) / (7 - 4 * mi) + 0.5);
946    printf("y2=%d, y3=%d, y4=%d\n", y2, y3, y4);
947#ifdef DEBUG_SPLINE_SEGMENTS
948    color++;
949    global_context->temporary_write_controls.foreground = color;
950#endif
951    if (!skip_first_last)
952	plotCubicBezier(x3, y3, (x2 + x4) / 2, (y2 + y4) / 2, x4, y4, x4, y4);
953
954    if (n - 3 < M_MAX)
955	mi = m[n - 3];
956    x1 = ifloor((x[n - 2] - 2 * x2) * mi + 0.5);
957    y1 = ifloor((y[n - 2] - 2 * y2) * mi + 0.5);
958    for (i = n - 3; i > 0; i--) {	/* back substitution */
959	if (i <= M_MAX)
960	    mi = m[i - 1];
961	x0 = ifloor((x[i] - 2 * x1) * mi + 0.5);
962	y0 = ifloor((y[i] - 2 * y1) * mi + 0.5);
963	x4 = ifloor((x0 + 4 * x1 + x2 + 3) / 6.0);	/* reconstruct P[i] */
964	y4 = ifloor((y0 + 4 * y1 + y2 + 3) / 6.0);
965#ifdef DEBUG_SPLINE_SEGMENTS
966	color++;
967	global_context->temporary_write_controls.foreground = color;
968#endif
969	plotCubicBezier(x4, y4,
970			ifloor((2 * x1 + x2) / 3 + 0.5),
971			ifloor((2 * y1 + y2) / 3 + 0.5),
972			ifloor((x1 + 2 * x2) / 3 + 0.5),
973			ifloor((y1 + 2 * y2) / 3 + 0.5),
974			x3, y3);
975	x3 = x4;
976	y3 = y4;
977	x2 = x1;
978	y2 = y1;
979	x1 = x0;
980	y1 = y0;
981    }
982    x0 = x[0];
983    x4 = ifloor((3 * x0 + 7 * x1 + 2 * x2 + 6) / 12.0);		/* reconstruct P[1] */
984    y0 = y[0];
985    y4 = ifloor((3 * y0 + 7 * y1 + 2 * y2 + 6) / 12.0);
986#ifdef DEBUG_SPLINE_SEGMENTS
987    global_context->temporary_write_controls.foreground = 4;
988#endif
989    plotCubicBezier(x4, y4,
990		    ifloor((2 * x1 + x2) / 3 + 0.5),
991		    ifloor((2 * y1 + y2) / 3 + 0.5),
992		    ifloor((x1 + 2 * x2) / 3 + 0.5),
993		    ifloor((y1 + 2 * y2) / 3 + 0.5),
994		    x3, y3);
995#ifdef DEBUG_SPLINE_SEGMENTS
996    color++;
997    global_context->temporary_write_controls.foreground = color;
998#endif
999    if (!skip_first_last)
1000	plotCubicBezier(x0, y0, x0, y0, (x0 + x1) / 2, (y0 + y1) / 2, x4, y4);
1001}
1002
1003static void
1004init_fragment(RegisDataFragment *fragment, char const *str)
1005{
1006    fragment->start = str;
1007    fragment->len = (unsigned) strlen(str);
1008    fragment->pos = 0U;
1009}
1010
1011static void
1012copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src)
1013{
1014    dst->start = src->start;
1015    dst->len = src->len;
1016    dst->pos = src->pos;
1017}
1018
1019static char
1020peek_fragment(RegisDataFragment const *fragment)
1021{
1022    if (fragment->pos < fragment->len) {
1023	return fragment->start[fragment->pos];
1024    }
1025    return '\0';
1026}
1027
1028static char
1029pop_fragment(RegisDataFragment *fragment)
1030{
1031    if (fragment->pos < fragment->len) {
1032	return fragment->start[fragment->pos++];
1033    }
1034    return '\0';
1035}
1036
1037static size_t
1038fragment_len(RegisDataFragment const *fragment)
1039{
1040    return fragment->len - fragment->pos;
1041}
1042
1043#define MAX_FRAG 1024
1044static char const *
1045fragment_to_tempstr(RegisDataFragment const *fragment)
1046{
1047    static char tempstr[MAX_FRAG + 1];
1048    size_t remaininglen = fragment_len(fragment);
1049    size_t minlen = ((remaininglen < MAX_FRAG)
1050		     ? remaininglen
1051		     : MAX_FRAG);
1052
1053    (void) strncpy(tempstr, &fragment->start[fragment->pos], minlen);
1054    tempstr[minlen] = '\0';
1055    return tempstr;
1056}
1057
1058static int
1059skip_regis_whitespace(RegisDataFragment *input)
1060{
1061    int skipped = 0;
1062    char ch;
1063
1064    assert(input);
1065
1066    for (; input->pos < input->len; input->pos++) {
1067	/* FIXME: the semicolon isn't whitespace -- it also terminates the current command even if inside of an optionset or extent */
1068	ch = input->start[input->pos];
1069	if (ch != ',' && ch != ';' && !IsSpace(ch)) {
1070	    break;
1071	}
1072	if (ch == '\n') {
1073	    TRACE(("end of input line\n\n"));
1074	}
1075	skipped = 1;
1076    }
1077
1078    if (skipped)
1079	return 1;
1080    return 0;
1081}
1082
1083static int
1084extract_regis_extent(RegisDataFragment *input, RegisDataFragment *output)
1085{
1086    char ch;
1087
1088    assert(input);
1089    assert(output);
1090
1091    output->start = &input->start[input->pos];
1092    output->len = 0U;
1093    output->pos = 0U;
1094
1095    if (input->pos >= input->len)
1096	return 0;
1097
1098    ch = input->start[input->pos];
1099    if (ch != '[')
1100	return 0;
1101    input->pos++;
1102    output->start++;
1103
1104    /* FIXME: truncate to 16 bit signed integers */
1105    for (; input->pos < input->len; input->pos++, output->len++) {
1106	ch = input->start[input->pos];
1107	if (ch == ';') {
1108	    TRACE(("DATA_ERROR: end of input before closing bracket\n"));
1109	    break;
1110	}
1111	if (ch == ']')
1112	    break;
1113    }
1114    if (ch == ']')
1115	input->pos++;
1116
1117    return 1;
1118}
1119
1120static int
1121extract_regis_num(RegisDataFragment *input, RegisDataFragment *output)
1122{
1123    char ch = 0;
1124    int has_digits = 0;
1125
1126    assert(input);
1127    assert(output);
1128
1129    output->start = &input->start[input->pos];
1130    output->len = 0U;
1131    output->pos = 0U;
1132
1133    if (input->start[input->pos] == '-' ||
1134	input->start[input->pos] == '+') {
1135	input->pos++;
1136	output->len++;
1137    }
1138
1139    for (; input->pos < input->len; input->pos++, output->len++) {
1140	ch = input->start[input->pos];
1141	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
1142	    ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
1143	    ch != '8' && ch != '9') {
1144	    break;
1145	}
1146	has_digits = 1;
1147    }
1148
1149    /* FIXME: what degenerate forms should be accepted ("E10" "1E" "1e" "1." "1ee10")? */
1150    /* FIXME: the terminal is said to support "floating point values", truncating to int... what do these look like? */
1151    if (has_digits && ch == 'E') {
1152	input->pos++;
1153	output->len++;
1154	for (; input->pos < input->len; input->pos++, output->len++) {
1155	    ch = input->start[input->pos];
1156	    if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
1157		ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
1158		ch != '8' && ch != '9') {
1159		break;
1160	    }
1161	}
1162    }
1163
1164    return has_digits;
1165}
1166
1167static int
1168extract_regis_pixelvector(RegisDataFragment *input, RegisDataFragment *output)
1169{
1170    char ch;
1171    int has_digits;
1172
1173    assert(input);
1174    assert(output);
1175
1176    output->start = &input->start[input->pos];
1177    output->len = 0U;
1178    output->pos = 0U;
1179
1180    if (input->pos < input->len) {
1181	ch = input->start[input->pos];
1182	if (ch == '+' || ch == '-') {
1183	    input->pos++;
1184	    output->len++;
1185	}
1186    }
1187
1188    has_digits = 0;
1189    for (; input->pos < input->len; input->pos++, output->len++) {
1190	ch = input->start[input->pos];
1191	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
1192	    ch != '4' && ch != '5' && ch != '6' && ch != '7') {
1193	    break;
1194	}
1195	has_digits = 1;
1196    }
1197
1198    return has_digits;
1199}
1200
1201static int
1202extract_regis_command(RegisDataFragment *input, char *command)
1203{
1204    char ch;
1205
1206    assert(input);
1207    assert(command);
1208
1209    if (input->pos >= input->len)
1210	return 0;
1211
1212    ch = input->start[input->pos];
1213    if (ch == '\0' || ch == ';') {
1214	return 0;
1215    }
1216    if (!islower(CharOf(ch)) && !isupper(CharOf(ch))) {
1217	return 0;
1218    }
1219    *command = ch;
1220    input->pos++;
1221
1222    return 1;
1223}
1224
1225static int
1226extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen)
1227{
1228    char first_ch;
1229    char ch;
1230    char prev_ch;
1231    unsigned outlen = 0U;
1232
1233    assert(input);
1234    assert(out);
1235
1236    if (input->pos >= input->len)
1237	return 0;
1238
1239    ch = input->start[input->pos];
1240    if (ch != '\'' && ch != '"')
1241	return 0;
1242    first_ch = ch;
1243    input->pos++;
1244
1245    ch = '\0';
1246    for (; input->pos < input->len; input->pos++) {
1247	prev_ch = ch;
1248	ch = input->start[input->pos];
1249	/* ';' (resync) is not recognized in strings */
1250	if (prev_ch == first_ch) {
1251	    if (ch == first_ch) {
1252		if (outlen < maxlen) {
1253		    out[outlen] = ch;
1254		}
1255		outlen++;
1256		ch = '\0';
1257		continue;
1258	    }
1259	    if (outlen < maxlen)
1260		out[outlen] = '\0';
1261	    else
1262		out[maxlen] = '\0';
1263	    return 1;
1264	}
1265	if (ch == '\0')
1266	    break;
1267	if (ch != first_ch) {
1268	    if (outlen < maxlen) {
1269		out[outlen] = ch;
1270	    }
1271	    outlen++;
1272	}
1273    }
1274    if (ch == first_ch) {
1275	if (outlen < maxlen)
1276	    out[outlen] = '\0';
1277	else
1278	    out[maxlen] = '\0';
1279	return 1;
1280    }
1281    /* FIXME: handle multiple strings concatenated with commas */
1282
1283    TRACE(("DATA_ERROR: end of input during before closing quote\n"));
1284    return 0;
1285}
1286
1287static int
1288extract_regis_optionset(RegisDataFragment *input, RegisDataFragment *output)
1289{
1290    char ch;
1291    int nesting;
1292
1293    assert(input);
1294    assert(output);
1295
1296    output->start = &input->start[input->pos];
1297    output->len = 0U;
1298    output->pos = 0U;
1299
1300    if (input->pos >= input->len)
1301	return 0;
1302
1303    ch = input->start[input->pos];
1304    if (ch != '(')
1305	return 0;
1306    input->pos++;
1307    output->start++;
1308    nesting = 1;
1309
1310    /* FIXME: handle strings with parens */
1311    for (; input->pos < input->len; input->pos++, output->len++) {
1312	ch = input->start[input->pos];
1313	if (ch == ';')
1314	    break;
1315	if (ch == '(')
1316	    nesting++;
1317	if (ch == ')') {
1318	    nesting--;
1319	    if (nesting == 0) {
1320		input->pos++;
1321		return 1;
1322	    }
1323	}
1324    }
1325
1326    TRACE(("DATA_ERROR: end of input before closing paren (%d levels deep)\n", nesting));
1327    return 0;
1328}
1329
1330static int
1331extract_regis_option(RegisDataFragment *input,
1332		     char *option,
1333		     RegisDataFragment *output)
1334{
1335    char ch;
1336    int nesting;
1337
1338    assert(input);
1339    assert(option);
1340    assert(output);
1341
1342    /* LETTER suboptions* value? */
1343    /* FIXME: can there be whitespace or commas inside of an option? */
1344    /* FIXME: what are the rules for using separate parens vs. sharing between options? */
1345
1346    output->start = &input->start[input->pos];
1347    output->len = 0U;
1348    output->pos = 0U;
1349
1350    if (input->pos >= input->len) {
1351	return 0;
1352    }
1353
1354    ch = input->start[input->pos];
1355    if (ch == ';' || ch == ',' || ch == '(' || ch == ')' || isdigit(CharOf(ch))) {
1356	return 0;
1357    }
1358    *option = ch;
1359    input->pos++;
1360    output->start++;
1361    nesting = 0;
1362
1363    /* FIXME: handle strings with parens, nested parens, etc. */
1364    for (; input->pos < input->len; input->pos++, output->len++) {
1365	ch = input->start[input->pos];
1366	TRACE(("looking at option char %c\n", ch));
1367	/* FIXME: any special rules for commas?  any need to track parens? */
1368	if (ch == '(') {
1369	    TRACE(("nesting++\n"));
1370	    nesting++;
1371	}
1372	if (ch == ')') {
1373	    TRACE(("nesting--\n"));
1374	    nesting--;
1375	    if (nesting < 0) {
1376		TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n", *option));
1377		return 0;
1378	    }
1379	}
1380	/* top-level commas indicate the end of this option and the start of another */
1381	if (nesting == 0 && ch == ',')
1382	    break;
1383	if (ch == ';')
1384	    break;
1385    }
1386    if (nesting != 0) {
1387	TRACE(("DATA_ERROR: mismatched parens in argument to ReGIS option \"%c\"\n", *option));
1388	return 0;
1389    }
1390
1391    TRACE(("found ReGIS option and value \"%c\" \"%s\"\n",
1392	   *option,
1393	   fragment_to_tempstr(output)));
1394    return 1;
1395}
1396
1397static int
1398regis_num_to_int(RegisDataFragment const *input, int *out)
1399{
1400    char ch;
1401
1402    /* FIXME: handle exponential notation and rounding */
1403    /* FIXME: check for junk after the number */
1404    ch = peek_fragment(input);
1405    if (ch != '0' &&
1406	ch != '1' &&
1407	ch != '2' &&
1408	ch != '3' &&
1409	ch != '4' &&
1410	ch != '5' &&
1411	ch != '6' &&
1412	ch != '7' &&
1413	ch != '8' &&
1414	ch != '9' &&
1415	ch != '+' &&
1416	ch != '-') {
1417	return 0;
1418    }
1419
1420    TRACE(("converting \"%s\" to an int\n", fragment_to_tempstr(input)));
1421    *out = atoi(fragment_to_tempstr(input));
1422    return 1;
1423}
1424
1425static int
1426load_regis_colorspec(Graphic const *graphic, RegisDataFragment const *input, RegisterNum *out)
1427{
1428    int val;
1429    RegisDataFragment colorspec;
1430    RegisDataFragment coloroption;
1431
1432    copy_fragment(&colorspec, input);
1433    TRACE(("looking at colorspec pattern: \"%s\"\n", fragment_to_tempstr(&colorspec)));
1434
1435    if (regis_num_to_int(&colorspec, &val)) {
1436	if (val < 0 || val >= (int) graphic->valid_registers) {		/* FIXME: wrap? */
1437	    TRACE(("DATA_ERROR: colorspec value %d\n", val));
1438	    return 0;
1439	}
1440	TRACE(("colorspec contains index for register %u\n", val));
1441	*out = (RegisterNum) val;
1442	return 1;
1443    }
1444
1445    if (extract_regis_optionset(&colorspec, &coloroption)) {
1446	short r, g, b;
1447	TRACE(("option: \"%s\"\n", fragment_to_tempstr(&coloroption)));
1448
1449	if (fragment_len(&coloroption) == 1) {
1450	    char ch = pop_fragment(&coloroption);
1451
1452	    TRACE(("got regis RGB colorspec pattern: \"%s\"\n",
1453		   fragment_to_tempstr(&coloroption)));
1454	    switch (ch) {
1455	    case 'D':
1456	    case 'd':
1457		r = 0;
1458		g = 0;
1459		b = 0;
1460		break;
1461	    case 'R':
1462	    case 'r':
1463		r = 100;
1464		g = 0;
1465		b = 0;
1466		break;
1467	    case 'G':
1468	    case 'g':
1469		r = 0;
1470		g = 100;
1471		b = 0;
1472		break;
1473	    case 'B':
1474	    case 'b':
1475		r = 0;
1476		g = 0;
1477		b = 100;
1478		break;
1479	    case 'C':
1480	    case 'c':
1481		r = 0;
1482		g = 100;
1483		b = 100;
1484		break;
1485	    case 'Y':
1486	    case 'y':
1487		r = 100;
1488		g = 100;
1489		b = 0;
1490		break;
1491	    case 'M':
1492	    case 'm':
1493		r = 100;
1494		g = 0;
1495		b = 100;
1496		break;
1497	    case 'W':
1498	    case 'w':
1499		r = 100;
1500		g = 100;
1501		b = 100;
1502		break;
1503	    default:
1504		TRACE(("unknown RGB color name: \"%c\"\n", ch));
1505		return 0;
1506	    }
1507	} else {
1508	    short h, l, s;
1509
1510	    if (sscanf(fragment_to_tempstr(&coloroption),
1511		       "%*1[Hh]%hd%*1[Ll]%hd%*1[Ss]%hd",
1512		       &h, &l, &s) != 3) {
1513		TRACE(("unrecognized colorspec format: \"%s\"\n",
1514		       fragment_to_tempstr(&coloroption)));
1515		return 0;
1516	    }
1517	    hls2rgb(h, l, s, &r, &g, &b);
1518	}
1519	/* FIXME: check for trailing junk? */
1520	*out = find_color_register(graphic->color_registers, r, g, b);
1521	TRACE(("colorspec maps to closest register %u\n", *out));
1522	return 1;
1523    }
1524
1525    TRACE(("unrecognized colorspec format: \"%s\"\n", fragment_to_tempstr(&colorspec)));
1526    return 0;
1527}
1528
1529static int
1530load_regis_extent(char const *extent, int origx, int origy, int *xloc, int *yloc)
1531{
1532    int xsign, ysign;
1533    char const *xpart;
1534    char const *ypart;
1535
1536    xpart = extent;
1537    if ((ypart = strchr(extent, ','))) {
1538	ypart++;
1539    } else {
1540	ypart = "";
1541    }
1542
1543    if (xpart[0] == '-') {
1544	xsign = -1;
1545	xpart++;
1546    } else if (xpart[0] == '+') {
1547	xsign = +1;
1548	xpart++;
1549    } else {
1550	xsign = 0;
1551    }
1552    if (ypart[0] == '-') {
1553	ysign = -1;
1554	ypart++;
1555    } else if (ypart[0] == '+') {
1556	ysign = +1;
1557	ypart++;
1558    } else {
1559	ysign = 0;
1560    }
1561
1562    if (xpart[0] == '\0' || xpart[0] == ',') {
1563	*xloc = origx;
1564    } else if (xsign == 0) {
1565	*xloc = atoi(xpart);
1566    } else {
1567	*xloc = origx + xsign * atoi(xpart);
1568    }
1569    if (ypart[0] == '\0') {
1570	*yloc = origy;
1571    } else if (ysign == 0) {
1572	*yloc = atoi(ypart);
1573    } else {
1574	*yloc = origy + ysign * atoi(ypart);
1575    }
1576
1577    return 1;
1578}
1579
1580static int
1581load_regis_pixelvector(char const *pixelvector,
1582		       unsigned mul,
1583		       int origx, int origy,
1584		       int *xloc, int *yloc)
1585{
1586    int dx = 0, dy = 0;
1587    int i;
1588
1589    for (i = 0; pixelvector[i] != '\0'; i++) {
1590	switch (pixelvector[i]) {
1591	case '0':
1592	    dx += 1;
1593	    break;
1594	case '1':
1595	    dx += 1;
1596	    dy -= 1;
1597	    break;
1598	case '2':
1599	    dy -= 1;
1600	    break;
1601	case '3':
1602	    dx -= 1;
1603	    dy -= 1;
1604	    break;
1605	case '4':
1606	    dx -= 1;
1607	    break;
1608	case '5':
1609	    dx -= 1;
1610	    dy += 1;
1611	    break;
1612	case '6':
1613	    dy += 1;
1614	    break;
1615	case '7':
1616	    dx += 1;
1617	    dy += 1;
1618	    break;
1619	default:
1620	    break;
1621	}
1622    }
1623
1624    *xloc = origx + dx * (int) mul;
1625    *yloc = origy + dy * (int) mul;
1626
1627    return 1;
1628}
1629
1630static int
1631load_regis_write_control(RegisParseState *state,
1632			 Graphic const *graphic,
1633			 int cur_x, int cur_y,
1634			 int option,
1635			 RegisDataFragment *arg,
1636			 RegisWriteControls *out)
1637{
1638    TRACE(("checking write control option \"%c\" with arg \"%s\"\n",
1639	   option, fragment_to_tempstr(arg)));
1640    switch (option) {
1641    case 'E':
1642    case 'e':
1643	TRACE(("write control erase writing mode \"%s\"\n",
1644	       fragment_to_tempstr(arg)));
1645	out->write_style = WRITE_STYLE_ERASE;
1646	break;
1647    case 'F':
1648    case 'f':
1649	TRACE(("write control plane write mask \"%s\"\n",
1650	       fragment_to_tempstr(arg)));
1651	{
1652	    int val;
1653	    if (!regis_num_to_int(arg, &val) ||
1654		val < 0 || val >= (int) graphic->valid_registers) {
1655		TRACE(("interpreting out of range value as 0 FIXME\n"));
1656		out->plane_mask = 0U;
1657	    } else {
1658		out->plane_mask = (unsigned) val;
1659	    }
1660	}
1661	break;
1662    case 'I':
1663    case 'i':
1664	TRACE(("write control foreground color \"%s\"\n",
1665	       fragment_to_tempstr(arg)));
1666	if (!load_regis_colorspec(graphic, arg, &out->foreground)) {
1667	    TRACE(("DATA_ERROR: write control foreground color specifier not recognized: \"%s\"\n",
1668		   fragment_to_tempstr(arg)));
1669	    return 0;
1670	}
1671	break;
1672    case 'M':
1673    case 'm':
1674	TRACE(("write control found pixel multiplication factor \"%s\"\n",
1675	       fragment_to_tempstr(arg)));
1676	{
1677	    int val;
1678	    if (!regis_num_to_int(arg, &val) || val <= 0) {
1679		TRACE(("interpreting out of range value %d as 1 FIXME\n", val));
1680		out->pv_multiplier = 1U;
1681	    } else {
1682		out->pv_multiplier = (unsigned) val;
1683	    }
1684	}
1685	break;
1686    case 'N':
1687    case 'n':
1688	TRACE(("write control negative pattern control \"%s\"\n",
1689	       fragment_to_tempstr(arg)));
1690	{
1691	    int val;
1692	    if (!regis_num_to_int(arg, &val)) {
1693		val = -1;
1694	    }
1695	    switch (val) {
1696	    default:
1697		TRACE(("interpreting out of range value %d as 0 FIXME\n", val));
1698		out->invert_pattern = 0U;
1699		break;
1700	    case 0:
1701		out->invert_pattern = 0U;
1702		break;
1703	    case 1:
1704		out->invert_pattern = 1U;
1705		break;
1706	    }
1707	}
1708	break;
1709    case 'P':
1710    case 'p':
1711	TRACE(("write control found pattern control \"%s\"\n",
1712	       fragment_to_tempstr(arg)));
1713	{
1714	    RegisDataFragment suboptionset;
1715	    RegisDataFragment suboptionarg;
1716	    RegisDataFragment item;
1717	    char suboption;
1718
1719	    while (arg->pos < arg->len) {
1720		skip_regis_whitespace(arg);
1721		TRACE(("looking for option in \"%s\"\n", fragment_to_tempstr(arg)));
1722		if (extract_regis_optionset(arg, &suboptionset)) {
1723		    TRACE(("got regis write pattern suboptionset: \"%s\"\n",
1724			   fragment_to_tempstr(&suboptionset)));
1725		    while (suboptionset.pos < suboptionset.len) {
1726			skip_regis_whitespace(&suboptionset);
1727			if (peek_fragment(&suboptionset) == ',') {
1728			    pop_fragment(&suboptionset);
1729			    continue;
1730			}
1731			if (extract_regis_option(&suboptionset, &suboption, &suboptionarg)) {
1732			    skip_regis_whitespace(&suboptionarg);
1733			    TRACE(("inspecting write pattern suboption \"%c\" with value \"%s\"\n",
1734				   suboption, fragment_to_tempstr(&suboptionarg)));
1735			    switch (suboption) {
1736			    case 'M':
1737			    case 'm':
1738				TRACE(("found pattern multiplier \"%s\"\n",
1739				       fragment_to_tempstr(&suboptionarg)));
1740				{
1741				    RegisDataFragment num;
1742				    int val;
1743
1744				    if (extract_regis_num(&suboptionarg, &num)) {
1745					if (!regis_num_to_int(&num, &val)
1746					    || val < 1) {
1747					    TRACE(("interpreting out of range pattern multiplier \"%s\" as 2 FIXME\n",
1748						   fragment_to_tempstr(&num)));
1749					    out->pattern_multiplier = 2U;
1750					} else {
1751					    out->pattern_multiplier =
1752						(unsigned) val;
1753					}
1754				    }
1755				    skip_regis_whitespace(&suboptionarg);
1756				    if (fragment_len(&suboptionarg)) {
1757					TRACE(("DATA_ERROR: unknown content after pattern multiplier \"%s\"\n",
1758					       fragment_to_tempstr(&suboptionarg)));
1759					return 0;
1760				    }
1761				}
1762				break;
1763			    default:
1764				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
1765				       suboption, fragment_to_tempstr(&suboptionarg)));
1766				return 0;
1767			    }
1768			    continue;
1769			}
1770
1771			TRACE(("DATA_ERROR: skipping unknown token in pattern control suboptionset (expecting option): \"%s\"\n",
1772			       fragment_to_tempstr(&suboptionset)));
1773			pop_fragment(&suboptionset);
1774		    }
1775		    continue;
1776		}
1777
1778		TRACE(("looking for int in \"%s\"\n", fragment_to_tempstr(arg)));
1779		if (extract_regis_num(arg, &item)) {
1780		    if (peek_fragment(&item) == '0' ||
1781			peek_fragment(&item) == '1') {
1782			unsigned pattern = 0U;
1783			unsigned bitcount;
1784			char ch;
1785
1786			TRACE(("converting pattern bits \"%s\"\n",
1787			       fragment_to_tempstr(&item)));
1788			for (bitcount = 0;; bitcount++) {
1789			    ch = pop_fragment(&item);
1790			    if (ch == '\0')
1791				break;
1792			    switch (ch) {
1793			    case '0':
1794				if (bitcount < MAX_PATTERN_BITS) {
1795				    pattern <<= 1U;
1796				}
1797				break;
1798			    case '1':
1799				if (bitcount < MAX_PATTERN_BITS) {
1800				    pattern <<= 1U;
1801				    pattern |= 1U;
1802				}
1803				break;
1804			    default:
1805				TRACE(("DATA_ERROR: unknown ReGIS write pattern bit value \"%c\"\n",
1806				       ch));
1807				return 0;
1808			    }
1809			}
1810
1811			if (bitcount > 0U) {
1812			    unsigned extrabits;
1813
1814			    for (extrabits = 0;
1815				 bitcount + extrabits < MAX_PATTERN_BITS;
1816				 extrabits++) {
1817				if (pattern & (1U << (bitcount - 1U))) {
1818				    pattern <<= 1U;
1819				    pattern |= 1U;
1820				} else {
1821				    pattern <<= 1U;
1822				}
1823			    }
1824			}
1825
1826			out->pattern = pattern;
1827		    } else {
1828			int val;
1829
1830			TRACE(("converting pattern id \"%s\"\n",
1831			       fragment_to_tempstr(&item)));
1832			if (!regis_num_to_int(&item, &val))
1833			    val = -1;
1834			switch (val) {	/* FIXME: exponential allowed? */
1835			case 0:
1836			    out->pattern = 0x00;	/* solid bg */
1837			    break;
1838			case 1:
1839			    out->pattern = 0xff;	/* solid fg */
1840			    break;
1841			case 2:
1842			    out->pattern = 0xf0;	/* dash */
1843			    break;
1844			case 3:
1845			    out->pattern = 0xe4;	/* dash dot */
1846			    break;
1847			case 4:
1848			    out->pattern = 0xaa;	/* dot */
1849			    break;
1850			case 5:
1851			    out->pattern = 0xea;	/* dash dot dot */
1852			    break;
1853			case 6:
1854			    out->pattern = 0x88;	/* sparse dot */
1855			    break;
1856			case 7:
1857			    out->pattern = 0x84;	/* asymmetric sparse dot */
1858			    break;
1859			case 8:
1860			    out->pattern = 0xc8;	/* sparse dash dot */
1861			    break;
1862			case 9:
1863			    out->pattern = 0x86;	/* sparse dot dash */
1864			    break;
1865			default:
1866			    TRACE(("DATA_ERROR: unknown ReGIS standard write pattern \"%d\"\n", val));
1867			    return 0;
1868			}
1869		    }
1870
1871		    TRACE(("final pattern is %02x\n", out->pattern));
1872		    continue;
1873		}
1874
1875		TRACE(("DATA_ERROR: skipping unknown token in pattern suboption: \"%s\"\n",
1876		       fragment_to_tempstr(arg)));
1877		pop_fragment(arg);
1878	    }
1879	}
1880	break;
1881    case 'C':
1882    case 'c':
1883	TRACE(("write control compliment writing mode \"%s\"\n",
1884	       fragment_to_tempstr(arg)));
1885	out->write_style = WRITE_STYLE_COMPLEMENT;
1886	break;
1887    case 'R':
1888    case 'r':
1889	TRACE(("write control switch to replacement writing mode \"%s\"\n",
1890	       fragment_to_tempstr(arg)));
1891	out->write_style = WRITE_STYLE_REPLACE;
1892	break;
1893    case 'S':
1894    case 's':
1895	TRACE(("write control shading control \"%s\"\n",
1896	       fragment_to_tempstr(arg)));
1897	{
1898	    RegisDataFragment suboptionset;
1899	    RegisDataFragment suboptionarg;
1900	    RegisDataFragment item;
1901	    char suboption;
1902	    char shading_character = '\0';
1903	    unsigned reference_dim = WRITE_SHADING_REF_Y;
1904	    int ref_x = cur_x, ref_y = cur_y;
1905	    int shading_enabled = 0;
1906
1907	    while (arg->pos < arg->len) {
1908		skip_regis_whitespace(arg);
1909
1910		if (extract_regis_string(arg, state->temp, state->templen)) {
1911		    TRACE(("found fill char \"%s\"\n", state->temp));
1912		    /* FIXME: allow longer strings ignoring extra chars? */
1913		    if (strlen(state->temp) != 1) {
1914			TRACE(("DATA_ERROR: expected exactly one char in fill string FIXME\n"));
1915			return 0;
1916		    }
1917		    shading_character = state->temp[0];
1918		    TRACE(("shading character is: %d\n", (int) shading_character));
1919		    continue;
1920		}
1921
1922		if (extract_regis_optionset(arg, &suboptionset)) {
1923		    skip_regis_whitespace(&suboptionset);
1924		    TRACE(("got regis shading control suboptionset: \"%s\"\n",
1925			   fragment_to_tempstr(&suboptionset)));
1926		    while (suboptionset.pos < suboptionset.len) {
1927			if (skip_regis_whitespace(&suboptionset))
1928			    continue;
1929			if (peek_fragment(&suboptionset) == ',') {
1930			    pop_fragment(&suboptionset);
1931			    continue;
1932			}
1933			if (extract_regis_option(&suboptionset, &suboption, &suboptionarg)) {
1934			    TRACE(("inspecting write shading suboption \"%c\" with value \"%s\"\n",
1935				   suboption, fragment_to_tempstr(&suboptionarg)));
1936			    switch (suboption) {
1937			    case 'X':
1938			    case 'x':
1939				TRACE(("found vertical shading suboption \"%s\"\n",
1940				       fragment_to_tempstr(&suboptionarg)));
1941				if (fragment_len(&suboptionarg)) {
1942				    TRACE(("DATA_ERROR: unexpected value to vertical shading suboption FIXME\n"));
1943				    return 0;
1944				}
1945				reference_dim = WRITE_SHADING_REF_X;
1946				break;
1947			    default:
1948				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
1949				       suboption, fragment_to_tempstr(&suboptionarg)));
1950				return 0;
1951			    }
1952			    continue;
1953			}
1954
1955			TRACE(("DATA_ERROR: skipping unknown token in shading control suboptionset (expecting option): \"%s\"\n",
1956			       fragment_to_tempstr(&suboptionset)));
1957			pop_fragment(&suboptionset);
1958		    }
1959		    continue;
1960		}
1961
1962		if (extract_regis_extent(arg, &item)) {
1963		    if (!load_regis_extent(fragment_to_tempstr(&item),
1964					   ref_x, ref_y,
1965					   &ref_x, &ref_y)) {
1966			TRACE(("DATA_ERROR: unable to parse extent in write shading option '%c': \"%s\"\n",
1967			       option, fragment_to_tempstr(&item)));
1968			return 0;
1969		    }
1970		    TRACE(("shading reference = %d,%d (%s)\n", ref_x, ref_y,
1971			   ((reference_dim == WRITE_SHADING_REF_X)
1972			    ? "X"
1973			    : "Y")));
1974		    continue;
1975		}
1976
1977		if (extract_regis_num(arg, &item)) {
1978		    if (!regis_num_to_int(&item, &shading_enabled)) {
1979			TRACE(("DATA_ERROR: unable to parse int in write shading option '%c': \"%s\"\n",
1980			       option, fragment_to_tempstr(&item)));
1981			return 0;
1982		    }
1983		    if (shading_enabled < 0 || shading_enabled > 1) {
1984			TRACE(("interpreting out of range value %d as 0 FIXME\n", shading_enabled));
1985			shading_enabled = 0;
1986		    }
1987		    TRACE(("shading enabled = %d\n", shading_enabled));
1988		    continue;
1989		}
1990
1991		TRACE(("DATA_ERROR: skipping unknown token in shade suboption: \"%s\"\n",
1992		       fragment_to_tempstr(arg)));
1993		pop_fragment(arg);
1994	    }
1995
1996	    if (shading_enabled) {
1997		out->shading_enabled = 1U;
1998		out->shading_reference_dim = reference_dim;
1999		out->shading_reference = ((reference_dim == WRITE_SHADING_REF_X)
2000					  ? ref_x
2001					  : ref_y);
2002		out->shading_character = shading_character;
2003	    } else {
2004		/* FIXME: confirm there is no effect if shading isn't enabled
2005		 * in the same command
2006		 */
2007		out->shading_enabled = 0U;
2008	    }
2009	}
2010	break;
2011    case 'V':
2012    case 'v':
2013	TRACE(("write control switch to overlay writing mode \"%s\"\n",
2014	       fragment_to_tempstr(arg)));
2015	out->write_style = WRITE_STYLE_OVERLAY;
2016	break;
2017    default:
2018	TRACE(("DATA_ERROR: ignoring unknown ReGIS write option \"%c\" arg \"%s\"\n",
2019	       option, fragment_to_tempstr(arg)));
2020	return 0;
2021    }
2022
2023    return 1;
2024}
2025
2026static int
2027load_regis_write_control_set(RegisParseState *state,
2028			     Graphic const *graphic,
2029			     int cur_x, int cur_y,
2030			     RegisDataFragment *controls,
2031			     RegisWriteControls *out)
2032{
2033    RegisDataFragment optionset;
2034    RegisDataFragment arg;
2035    char option;
2036
2037    while (controls->pos < controls->len) {
2038	skip_regis_whitespace(controls);
2039
2040	if (extract_regis_optionset(controls, &optionset)) {
2041	    TRACE(("got regis write control optionset: \"%s\"\n",
2042		   fragment_to_tempstr(&optionset)));
2043	    while (optionset.pos < optionset.len) {
2044		skip_regis_whitespace(&optionset);
2045		if (peek_fragment(&optionset) == ',') {
2046		    pop_fragment(&optionset);
2047		    continue;
2048		}
2049		if (extract_regis_option(&optionset, &option, &arg)) {
2050		    skip_regis_whitespace(&arg);
2051		    TRACE(("got regis write control option and value: \"%c\" \"%s\"\n",
2052			   option, fragment_to_tempstr(&arg)));
2053		    if (!load_regis_write_control(state, graphic,
2054						  cur_x, cur_y,
2055						  option, &arg, out)) {
2056			return 0;
2057		    }
2058		    continue;
2059		}
2060
2061		TRACE(("DATA_ERROR: skipping unknown token in write control optionset (expecting option): \"%s\"\n",
2062		       fragment_to_tempstr(&optionset)));
2063		pop_fragment(&optionset);
2064	    }
2065	    continue;
2066	}
2067
2068	TRACE(("DATA_ERROR: skipping unknown token in write controls (expecting optionset): \"%s\"\n",
2069	       fragment_to_tempstr(controls)));
2070	pop_fragment(controls);
2071    }
2072
2073    return 1;
2074}
2075
2076static void
2077init_regis_write_controls(int terminal_id, unsigned all_planes, RegisWriteControls *controls)
2078{
2079    controls->pv_multiplier = 1U;
2080    controls->pattern = 0xff;	/* solid */
2081    controls->pattern_multiplier = 2U;
2082    controls->invert_pattern = 0U;
2083    controls->plane_mask = all_planes;
2084    controls->write_style = WRITE_STYLE_OVERLAY;
2085    switch (terminal_id) {
2086    case 125:			/* FIXME */
2087    case 240:			/* FIXME */
2088    case 241:			/* FIXME */
2089    case 330:
2090	controls->foreground = 3U;
2091	break;
2092    case 340:
2093	controls->foreground = 7U;
2094	break;
2095    default:			/* FIXME */
2096	controls->foreground = 63U;
2097	break;
2098    }
2099    controls->shading_enabled = 0U;
2100    controls->shading_character = '\0';
2101    controls->shading_reference = 0;	/* no meaning if shading is disabled */
2102    controls->shading_reference_dim = WRITE_SHADING_REF_Y;
2103    /* FIXME: add the rest */
2104}
2105
2106static void
2107copy_regis_write_controls(RegisWriteControls const *src,
2108			  RegisWriteControls *dst)
2109{
2110    dst->pv_multiplier = src->pv_multiplier;
2111    dst->pattern = src->pattern;
2112    dst->pattern_multiplier = src->pattern_multiplier;
2113    dst->invert_pattern = src->invert_pattern;
2114    dst->foreground = src->foreground;
2115    dst->plane_mask = src->plane_mask;
2116    dst->write_style = src->write_style;
2117    dst->shading_enabled = src->shading_enabled;
2118    dst->shading_character = src->shading_character;
2119    dst->shading_reference = src->shading_reference;
2120    dst->shading_reference_dim = src->shading_reference_dim;
2121}
2122
2123static void
2124init_regis_graphics_context(int terminal_id, RegisGraphicsContext *context)
2125{
2126    context->terminal_id = terminal_id;
2127    /*
2128     * Generate a mask covering all valid color register address bits
2129     * (but don't bother past 2**16).
2130     */
2131    context->all_planes = (unsigned) context->graphic->valid_registers;
2132    context->all_planes--;
2133    context->all_planes |= 1U;
2134    context->all_planes |= context->all_planes >> 1U;
2135    context->all_planes |= context->all_planes >> 2U;
2136    context->all_planes |= context->all_planes >> 4U;
2137    context->all_planes |= context->all_planes >> 8U;
2138
2139    init_regis_write_controls(terminal_id, context->all_planes, &context->persistent_write_controls);
2140    copy_regis_write_controls(&context->persistent_write_controls, &context->temporary_write_controls);
2141
2142    /* FIXME: coordinates */
2143    /* FIXME: scrolling */
2144    /* FIXME: output maps */
2145    context->background = 0U;
2146    /* FIXME: input cursor location */
2147    /* FIXME: input cursor style */
2148    context->graphics_output_cursor_x = 0;
2149    context->graphics_output_cursor_y = 0;
2150    /* FIXME: output cursor style */
2151    /* FIXME: text settings */
2152}
2153
2154static int
2155parse_regis_command(RegisParseState *state)
2156{
2157    char ch = peek_fragment(&state->input);
2158    if (ch == '\0')
2159	return 0;
2160
2161    if (!extract_regis_command(&state->input, &ch))
2162	return 0;
2163
2164    switch (ch) {
2165    case 'C':
2166    case 'c':
2167	/* Curve
2168
2169	 * C
2170	 * (A)  # set the arc length in degrees (+ or nothing for
2171	 *      # counter-clockwise, - for clockwise, rounded to the
2172	 *      # closest integer degree)
2173	 * (B)  # begin closed curve sequence (must have at least two
2174	 *      # values; this option can not be nested)
2175	 * (C)  # position is the center, current location is the
2176	 *      # circumference (stays in effect until next command)
2177	 * (E)  # end curve sequence (drawing is performed here)
2178	 * (S)  # begin open curve sequence
2179	 * (W)  # temporary write options (see write command)
2180	 * [<center, circumference position>]  # center if (C), otherwise point on circumference
2181	 * [<point in curve sequence>]...  # if between (B) and (E)
2182	 * <pv>...  # if between (B) and (E)
2183	 */
2184	TRACE(("found ReGIS command \"%c\" (curve)\n", ch));
2185	state->command = 'c';
2186	state->curve_mode = CURVE_POSITION_ARC_EDGE;
2187	state->arclen = 360;
2188	state->num_points = 0U;
2189	break;
2190    case 'F':
2191    case 'f':
2192	/* Fill
2193
2194	 * F
2195	 * (V)  # polygon (see vector command)
2196	 * (C)  # curve (see curve command)
2197	 * (W)  # temporary write options (see write command)
2198	 */
2199	TRACE(("found ReGIS command \"%c\" (filled polygon)\n", ch));
2200	state->command = 'f';
2201	break;
2202    case 'L':
2203    case 'l':
2204	/* Load
2205
2206	 * L
2207	 * (A)  # set character set number and name
2208	 * "ascii"xx,xx,xx,xx,xx,xx,xx,xx  # pixel values
2209	 */
2210	TRACE(("found ReGIS command \"%c\" (load charset)\n", ch));
2211	state->command = 'l';
2212	break;
2213    case 'P':
2214    case 'p':
2215	/* Position
2216
2217	 * P
2218	 * (B)  # begin bounded position stack (last point returns to first)
2219	 * (E)  # end position stack
2220	 * (P)  # select graphics page for the input and output cursors
2221	 * (S)  # begin unbounded position stack
2222	 * (W)  # temporary write options (see write command)
2223	 * <pv>  # move: 0 == right, 1 == upper right, ..., 7 == lower right
2224	 * [<position>]  # move to position (X, Y, or both)
2225	 *
2226	 * Note the stack does not need to be ended before the next command
2227	 * Note: maximum depth is 16 levels
2228	 */
2229	TRACE(("found ReGIS command \"%c\" (position)\n", ch));
2230	state->command = 'p';
2231	break;
2232    case 'R':
2233    case 'r':
2234	/* Report
2235
2236	 * R
2237	 * (E)  # parse error
2238	 * (I<val>)  # set input mode (0 == oneshot, 1 == multiple) (always returns CR)
2239	 * (L)  # character set
2240	 * (M(<name>)  # macrograph contents
2241	 * (M(=)  # macrograph storage
2242	 * (P)  # cursor position
2243	 * (P(I))  # interactive cursor position
2244	 */
2245	TRACE(("found ReGIS command \"%c\" (report status)\n", ch));
2246	state->command = 'r';
2247	break;
2248    case 'S':
2249    case 's':
2250	/* Screen
2251
2252	 * S
2253	 * (A[<upper left>][<lower right>])
2254	 * (C<setting>  # 0 (cursor output off), 1 (cursor output on)
2255	 * (E  # erase to background color, resets shades, curves, and stacks
2256	 * (H(P<printer offset>)[<print area cornet>][<print area corner>)
2257	 * (I<color register>)  # set the background to a specific register
2258	 * (I(<rgb>))  # set the background to the register closest to an RGB value
2259	 * (I(<hls>))  # set the background to the register closest to an HLS color
2260	 * (M<color index to set>(L<mono level>)...)  # level is 0 ... 100 (sets grayscale registers only)
2261	 * (M<color index to set>(<RGB code>)...)  # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color and grayscale registers)
2262	 * (M<color index to set>(A<RGB code>)...)  # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color registers only)
2263	 * (M<color index to set>(H<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color and grayscale registers)
2264	 * (M<color index to set>(AH<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color registers only)
2265	 * (P<graphics page number>)  # 0 (default) or 1
2266	 * (T(<time delay ticks>)  # 60 ticks per second, up to 32767 ticks
2267	 * (W(M<factor>)  # PV value
2268	 * [scroll offset]  # optional
2269	 */
2270	TRACE(("found ReGIS command \"%c\" (screen)\n", ch));
2271	state->command = 's';
2272	break;
2273    case 'T':
2274    case 't':
2275	/* Text
2276
2277	 * T
2278	 * (A0L"<designator>"))  # specify a built-in set for GL via two-char designator
2279	 * (A0R"<designator>"))  # specify a built-in set for GR via two-char or three-char designator
2280	 * (A<num>R"<designator>"))  # specify a user-loaded (1-3) set for GR via two-char or three-char designator
2281	 * (B)  # begin temporary text control
2282	 * (D<angle>)  # specify a string tilt
2283	 * (E)  # end temporary text control
2284	 * (H<factor>)  # select a height multiplier (1-256)
2285	 * (I<angle>)  # italics: no slant (0), lean back (-1 though -45), lean forward (+1 through +45)
2286	 * (M[width factor,height factor])  # select size multipliers (width 1-16) (height 1-256)
2287	 * (S<size id>)  # select one of the 17 standard cell sizes
2288	 * (S[dimensions])  # set a custom display cell size (char with border)
2289	 * (U[dimensions])  # set a custom unit cell size (char size)
2290	 * (W<write command>)  # temporary write options (see write command)
2291	 * [<char offset>]  # optional offset between characters
2292	 * <PV spacing>  # for subscripts and superscripts
2293	 * '<text>'  # optional
2294	 * "<text>"  # optional
2295	 */
2296	TRACE(("found ReGIS command \"%c\" (text)\n", ch));
2297	state->command = 't';
2298	break;
2299    case 'V':
2300    case 'v':
2301	/* Vector
2302
2303	 * V
2304	 * (B)  # begin bounded position stack (last point returns to first)
2305	 * (E)  # end position stack
2306	 * (S)  # begin unbounded position stack
2307	 * (W)  # temporary write options (see write command)
2308	 * <pv>  # draw a line to the pixel vector
2309	 * []  # draw a dot at the current location
2310	 * [<position>]  # draw a line to position
2311	 */
2312	TRACE(("found ReGIS command \"%c\" (vector)\n", ch));
2313	state->command = 'v';
2314	break;
2315    case 'W':
2316    case 'w':
2317	/* Write
2318
2319	 * W
2320	 * (C)  # complement writing mode
2321	 * (E)  # erase writing mode
2322	 * (F<plane>)  # set the foreground intensity to a specific register
2323	 * (I<color register>)  # set the foreground to a specific register
2324	 * (I(<rgb>))  # set the foreground to the register closest to an RGB value
2325	 * (I(<hls>))  # set the foreground to the register closest to an HLS color
2326	 * (M<pixel vector multiplier>)  # set the multiplication factor
2327	 * (N<setting>)  # 0 == negative patterns disabled, 1 == negative patterns enabled
2328	 * (P<pattern number>)  # 0..9: 0 == none, 1 == solid, 2 == 50% dash, 3 == dash-dot
2329	 * (P<pattern bits>)  # 2 to 8 bits represented as a 0/1 sequence
2330	 * (P<(M<pattern multiplier>))
2331	 * (R)  # replacement writing mode
2332	 * (S'<character>')  # set shading character
2333	 * (S<setting>)  # 0 == disable shding, 1 == enable shading
2334	 * (S[reference point])  # set a horizontal reference line including this point
2335	 * (S(X)[reference point])  # set a vertical reference line including this point
2336	 * (V)  # overlay writing mode
2337	 */
2338	TRACE(("found ReGIS command \"%c\" (write parameters)\n", ch));
2339	state->command = 'w';
2340	break;
2341    case '@':
2342	/* Macrograph */
2343	TRACE(("found ReGIS macrograph command\n"));
2344	ch = pop_fragment(&state->input);
2345	TRACE(("inspecting macrograph character \"%c\"\n", ch));
2346	switch (ch) {
2347	case '.':
2348	    TRACE(("clearing all macrographs FIXME\n"));
2349	    /* FIXME: handle */
2350	    break;
2351	case ':':
2352	    TRACE(("defining macrograph FIXME\n"));
2353	    /* FIXME: parse, handle  :<name> */
2354	    break;
2355	case ';':
2356	    TRACE(("DATA_ERROR: found extraneous terminator for macrograph definition\n"));
2357	    break;
2358	default:
2359	    if ((ch > 'A' && ch < 'Z') || (ch > 'a' && ch < 'z')) {
2360		TRACE(("expanding macrograph \"%c\" FIXME\n", ch));
2361		/* FIXME: handle */
2362	    } else {
2363		TRACE(("DATA_ERROR: unknown macrograph subcommand \"%c\"\n", ch));
2364	    }
2365	    /* FIXME: parse, handle */
2366	    break;
2367	}
2368	break;
2369    default:
2370	TRACE(("DATA_ERROR: unknown ReGIS command %04x (%c)\n",
2371	       (int) ch, ch));
2372	state->command = '_';
2373	state->option = '_';
2374	return 0;
2375    }
2376
2377    state->option = '_';
2378
2379    return 1;
2380}
2381
2382static int
2383parse_regis_optionset(RegisParseState *state)
2384{
2385    if (!extract_regis_optionset(&state->input, &state->optionset))
2386	return 0;
2387
2388    TRACE(("found ReGIS optionset \"%s\"\n", fragment_to_tempstr(&state->optionset)));
2389    state->option = '_';
2390
2391    return 1;
2392}
2393
2394static int
2395parse_regis_option(RegisParseState *state, RegisGraphicsContext *context)
2396{
2397    RegisDataFragment optionarg;
2398
2399    if (!extract_regis_option(&state->optionset, &state->option, &optionarg))
2400	return 0;
2401    skip_regis_whitespace(&optionarg);
2402
2403    TRACE(("found ReGIS option \"%c\": \"%s\"\n",
2404	   state->option, fragment_to_tempstr(&optionarg)));
2405
2406    switch (state->command) {
2407    case 'c':
2408	TRACE(("inspecting curve option \"%c\" with value \"%s\"\n",
2409	       state->option, fragment_to_tempstr(&optionarg)));
2410	switch (state->option) {
2411	case 'A':
2412	case 'a':
2413	    TRACE(("found arc length \"%s\"\n", fragment_to_tempstr(&optionarg)));
2414	    {
2415		RegisDataFragment arclen;
2416
2417		if (!extract_regis_num(&optionarg, &arclen)) {
2418		    TRACE(("DATA_ERROR: expected int in curve arclen option: \"%s\"\n",
2419			   fragment_to_tempstr(&optionarg)));
2420		    break;
2421		}
2422		TRACE(("arc length string %s\n", fragment_to_tempstr(&arclen)));
2423		if (!regis_num_to_int(&arclen, &state->arclen)) {
2424		    TRACE(("DATA_ERROR: unable to parse int in curve arclen option: \"%s\"\n",
2425			   fragment_to_tempstr(&arclen)));
2426		    break;
2427		}
2428		TRACE(("value of arc length is %d\n", state->arclen));
2429		while (state->arclen < -360)
2430		    state->arclen += 360;
2431		while (state->arclen > 360)
2432		    state->arclen -= 360;
2433		TRACE(("using final arc length %d\n", state->arclen));
2434	    }
2435	    break;
2436	case 'B':
2437	case 'b':
2438	    TRACE(("begin closed curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
2439	    if (fragment_len(&optionarg)) {
2440		TRACE(("DATA_ERROR: invalid closed curve option \"%s\"\n",
2441		       fragment_to_tempstr(&optionarg)));
2442		break;
2443	    }
2444	    state->curve_mode = CURVE_POSITION_CLOSED_CURVE;
2445	    state->num_points = 0U;
2446	    state->x_points[state->num_points] = context->graphics_output_cursor_x;
2447	    state->y_points[state->num_points] = context->graphics_output_cursor_y;
2448	    state->num_points++;
2449	    break;
2450	case 'C':
2451	case 'c':
2452	    TRACE(("found center position mode \"%s\"\n",
2453		   fragment_to_tempstr(&optionarg)));
2454	    if (fragment_len(&optionarg)) {
2455		TRACE(("DATA_ERROR: invalid center position option \"%s\"\n",
2456		       fragment_to_tempstr(&optionarg)));
2457		break;
2458	    }
2459	    state->curve_mode = CURVE_POSITION_ARC_CENTER;
2460	    break;
2461	case 'E':
2462	case 'e':
2463	    TRACE(("end curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
2464	    switch (state->curve_mode) {
2465	    case CURVE_POSITION_CLOSED_CURVE:
2466		{
2467		    int i;
2468
2469#ifdef DEBUG_SPLINE_POINTS
2470		    printf("points: \n");
2471		    for (i = 0; i < (int) state->num_points; i++)
2472			printf("  %d,%d\n",
2473			       state->x_points[i], state->y_points[i]);
2474#endif
2475
2476#ifdef DEBUG_SPLINE_WITH_ROTATION
2477		    {
2478			static int shift = 0;
2479			int temp_x[MAX_CURVE_POINTS], temp_y[MAX_CURVE_POINTS];
2480			shift++;
2481			shift = shift % state->num_points;
2482			for (i = 0; i < (int) state->num_points; i++) {
2483			    temp_x[i] = state->x_points[i];
2484			    temp_y[i] = state->y_points[i];
2485			}
2486			for (i = 0; i < (int) state->num_points; i++) {
2487			    state->x_points[i] = temp_x[(i + shift) % state->num_points];
2488			    state->y_points[i] = temp_y[(i + shift) % state->num_points];
2489			}
2490
2491#ifdef DEBUG_SPLINE_POINTS
2492			printf("after shift %d: \n", shift);
2493			for (i = 0; i < (int) state->num_points; i++)
2494			    printf("  %d,%d\n",
2495				   state->x_points[i], state->y_points[i]);
2496#endif
2497		    }
2498#endif
2499
2500		    for (i = (int) state->num_points; i > 0; i--) {
2501			state->x_points[i] = state->x_points[i - 1];
2502			state->y_points[i] = state->y_points[i - 1];
2503		    }
2504		    state->x_points[0] = state->x_points[state->num_points];
2505		    state->y_points[0] = state->y_points[state->num_points];
2506		    state->num_points++;
2507		    for (i = (int) state->num_points; i > 0; i--) {
2508			state->x_points[i] = state->x_points[i - 1];
2509			state->y_points[i] = state->y_points[i - 1];
2510		    }
2511		    state->x_points[0] = state->x_points[state->num_points - 1];
2512		    state->y_points[0] = state->y_points[state->num_points - 1];
2513		    state->num_points++;
2514		    state->x_points[state->num_points] = state->x_points[2];
2515		    state->y_points[state->num_points] = state->y_points[2];
2516		    state->num_points++;
2517#ifdef DEBUG_SPLINE_WITH_OVERDRAW
2518		    state->x_points[state->num_points] = state->x_points[3];
2519		    state->y_points[state->num_points] = state->y_points[3];
2520		    state->num_points++;
2521		    state->x_points[state->num_points] = state->x_points[4];
2522		    state->y_points[state->num_points] = state->y_points[4];
2523		    state->num_points++;
2524#endif
2525#ifdef DEBUG_SPLINE_POINTS
2526		    printf("after points added: \n");
2527		    for (i = 0; i < (int) state->num_points; i++)
2528			printf("  %d,%d\n",
2529			       state->x_points[i], state->y_points[i]);
2530#endif
2531		}
2532		TRACE(("drawing closed spline\n"));
2533		global_context = context;	/* FIXME: remove after updating spline code */
2534		plotCubicSpline((int) state->num_points - 1,
2535				state->x_points, state->y_points,
2536				1);
2537		break;
2538	    case CURVE_POSITION_OPEN_CURVE:
2539		TRACE(("drawing open spline\n"));
2540#ifdef DEBUG_SPLINE_POINTS
2541		{
2542		    int i;
2543
2544		    printf("points: \n");
2545		    for (i = 0; i < (int) state->num_points; i++)
2546			printf("  %d,%d\n",
2547			       state->x_points[i], state->y_points[i]);
2548		}
2549#endif
2550		global_context = context;	/* FIXME: remove after updating spline code */
2551		plotCubicSpline((int) state->num_points - 1,
2552				state->x_points, state->y_points,
2553				1);
2554		break;
2555	    default:
2556		TRACE(("DATA_ERROR: end curve option unexpected \"%s\"\n",
2557		       fragment_to_tempstr(&optionarg)));
2558		break;
2559	    }
2560	    break;
2561	case 'S':
2562	case 's':
2563	    TRACE(("begin open curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
2564	    if (fragment_len(&optionarg)) {
2565		TRACE(("DATA_ERROR: invalid open curve option \"%s\"\n",
2566		       fragment_to_tempstr(&optionarg)));
2567		break;
2568	    }
2569	    state->curve_mode = CURVE_POSITION_OPEN_CURVE;
2570	    state->num_points = 0U;
2571	    state->x_points[state->num_points] = context->graphics_output_cursor_x;
2572	    state->y_points[state->num_points] = context->graphics_output_cursor_y;
2573	    state->num_points++;
2574	    TRACE(("first point on curve with location %d,%d\n",
2575		   context->graphics_output_cursor_x,
2576		   context->graphics_output_cursor_y));
2577	    break;
2578	case 'W':
2579	case 'w':
2580	    TRACE(("found temporary write options \"%s\"\n",
2581		   fragment_to_tempstr(&optionarg)));
2582	    if (!load_regis_write_control_set(state, context->graphic,
2583					      context->graphics_output_cursor_x, context->graphics_output_cursor_y,
2584					      &optionarg, &context->temporary_write_controls)) {
2585		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
2586		       fragment_to_tempstr(&optionarg)));
2587		break;
2588	    }
2589	    break;
2590	default:
2591	    TRACE(("DATA_ERROR: ignoring unknown ReGIS curve command option '%c' arg \"%s\"\n",
2592		   state->option, fragment_to_tempstr(&optionarg)));
2593	    break;
2594	}
2595	break;
2596    case 'f':
2597	TRACE(("inspecting fill option \"%c\" with value \"%s\"\n",
2598	       state->option, fragment_to_tempstr(&optionarg)));
2599	switch (state->option) {
2600	case 'C':
2601	case 'c':
2602	    state->command = 'c';
2603	    state->option = '_';
2604	    break;
2605	case 'V':
2606	case 'v':
2607	    state->command = 'v';
2608	    state->option = '_';
2609	    break;
2610	case 'W':
2611	case 'w':
2612	    TRACE(("found temporary write options \"%s\"\n",
2613		   fragment_to_tempstr(&optionarg)));
2614	    if (!load_regis_write_control_set(state, context->graphic,
2615					      context->graphics_output_cursor_x, context->graphics_output_cursor_y,
2616					      &optionarg, &context->temporary_write_controls)) {
2617		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
2618		       fragment_to_tempstr(&optionarg)));
2619		break;
2620	    }
2621	    break;
2622	default:
2623	    TRACE(("DATA_ERROR: ignoring unknown ReGIS fill command option '%c' arg \"%s\"\n",
2624		   state->option, fragment_to_tempstr(&optionarg)));
2625	    break;
2626	}
2627	break;
2628    case 'l':
2629	TRACE(("inspecting load option \"%c\" with value \"%s\"\n",
2630	       state->option, fragment_to_tempstr(&optionarg)));
2631	/* FIXME: parse options */
2632	switch (state->option) {
2633	case 'A':
2634	case 'a':
2635	    TRACE(("found character specifier option \"%s\" FIXME\n",
2636		   fragment_to_tempstr(&optionarg)));
2637	    /* FIXME: handle */
2638	    break;
2639	default:
2640	    TRACE(("DATA_ERROR: ignoring unknown ReGIS load command option '%c' arg \"%s\"\n",
2641		   state->option, fragment_to_tempstr(&optionarg)));
2642	    break;
2643	}
2644	break;
2645    case 'p':
2646	TRACE(("inspecting position option \"%c\" with value \"%s\"\n",
2647	       state->option, fragment_to_tempstr(&optionarg)));
2648	switch (state->option) {
2649	case 'B':
2650	case 'b':
2651	    TRACE(("found begin bounded position stack \"%s\" FIXME\n",
2652		   fragment_to_tempstr(&optionarg)));
2653	    /* FIXME: handle */
2654	    break;
2655	case 'E':
2656	case 'e':
2657	    TRACE(("found end position stack \"%s\" FIXME\n",
2658		   fragment_to_tempstr(&optionarg)));
2659	    /* FIXME: handle */
2660	    break;
2661	case 'P':
2662	case 'p':
2663	    TRACE(("found graphics page \"%s\" FIXME\n",
2664		   fragment_to_tempstr(&optionarg)));
2665	    /* FIXME: handle */
2666	    break;
2667	case 'S':
2668	case 's':
2669	    TRACE(("found begin unbounded position stack \"%s\" FIXME\n",
2670		   fragment_to_tempstr(&optionarg)));
2671	    /* FIXME: handle */
2672	    break;
2673	case 'W':
2674	case 'w':
2675	    TRACE(("found temporary write options \"%s\"\n",
2676		   fragment_to_tempstr(&optionarg)));
2677	    if (!load_regis_write_control_set(state, context->graphic,
2678					      context->graphics_output_cursor_x, context->graphics_output_cursor_y,
2679					      &optionarg, &context->temporary_write_controls)) {
2680		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
2681		       fragment_to_tempstr(&optionarg)));
2682	    }
2683	    break;
2684	default:
2685	    TRACE(("DATA_ERROR: ignoring unknown ReGIS position command option '%c' arg \"%s\"\n",
2686		   state->option, fragment_to_tempstr(&optionarg)));
2687	    break;
2688	}
2689	break;
2690    case 'r':
2691	TRACE(("inspecting report option \"%c\" with value \"%s\"\n",
2692	       state->option, fragment_to_tempstr(&optionarg)));
2693	switch (state->option) {
2694	case 'E':
2695	case 'e':
2696	    TRACE(("found parse error report \"%s\" FIXME\n",
2697		   fragment_to_tempstr(&optionarg)));
2698	    /* FIXME: handle */
2699	    break;
2700	case 'I':
2701	case 'i':
2702	    TRACE(("found set input mode \"%s\" FIXME\n",
2703		   fragment_to_tempstr(&optionarg)));
2704	    /* FIXME: handle */
2705	    break;
2706	case 'L':
2707	case 'l':
2708	    TRACE(("found character set report \"%s\" FIXME\n",
2709		   fragment_to_tempstr(&optionarg)));
2710	    /* FIXME: handle */
2711	    break;
2712	case 'M':
2713	case 'm':
2714	    TRACE(("found macrograph report \"%s\" FIXME\n",
2715		   fragment_to_tempstr(&optionarg)));
2716	    /* FIXME: handle */
2717	    break;
2718	case 'P':
2719	case 'p':
2720	    TRACE(("found cursor position report \"%s\" FIXME\n",
2721		   fragment_to_tempstr(&optionarg)));
2722	    /* FIXME: handle */
2723	    break;
2724	default:
2725	    TRACE(("DATA_ERROR: ignoring unknown ReGIS report command option '%c' arg \"%s\"\n",
2726		   state->option, fragment_to_tempstr(&optionarg)));
2727	    break;
2728	}
2729	break;
2730    case 's':
2731	TRACE(("inspecting screen option \"%c\" with value \"%s\"\n",
2732	       state->option, fragment_to_tempstr(&optionarg)));
2733	switch (state->option) {
2734	case 'A':
2735	case 'a':
2736	    TRACE(("found address definition \"%s\" FIXME\n",
2737		   fragment_to_tempstr(&optionarg)));
2738	    /* FIXME: handle */
2739	    if (!fragment_len(&optionarg)) {
2740		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition option value \"%s\"\n",
2741		       fragment_to_tempstr(&optionarg)));
2742		return 0;
2743	    }
2744	    break;
2745	case 'C':
2746	case 'c':
2747	    TRACE(("found cursor control \"%s\" FIXME\n",
2748		   fragment_to_tempstr(&optionarg)));
2749	    /* FIXME: handle */
2750	    if (!fragment_len(&optionarg)) {
2751		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen cursor control option value \"%s\"\n",
2752		       fragment_to_tempstr(&optionarg)));
2753		return 0;
2754	    }
2755	    break;
2756	case 'E':
2757	case 'e':
2758	    TRACE(("found erase request \"%s\"\n", fragment_to_tempstr(&optionarg)));
2759	    if (fragment_len(&optionarg)) {
2760		TRACE(("DATA_ERROR: ignoring unexpected argument to ReGIS screen erase option \"%s\"\n",
2761		       fragment_to_tempstr(&optionarg)));
2762		return 0;
2763	    }
2764	    draw_solid_rectangle(context->graphic, 0, 0,
2765				 context->graphic->actual_width - 1,
2766				 context->graphic->actual_height - 1,
2767				 context->background);
2768	    break;
2769	case 'H':
2770	case 'h':
2771	    TRACE(("found hardcopy control \"%s\" FIXME\n",
2772		   fragment_to_tempstr(&optionarg)));
2773	    /* FIXME: handle */
2774	    if (!fragment_len(&optionarg)) {
2775		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen hardcopy control option value \"%s\"\n",
2776		       fragment_to_tempstr(&optionarg)));
2777		return 0;
2778	    }
2779	    break;
2780	case 'I':
2781	case 'i':
2782	    TRACE(("found screen background color index \"%s\"\n",
2783		   fragment_to_tempstr(&optionarg)));
2784	    if (!load_regis_colorspec(context->graphic, &optionarg, &context->background)) {
2785		TRACE(("DATA_ERROR: screen background color specifier not recognized: \"%s\"\n",
2786		       fragment_to_tempstr(&optionarg)));
2787		return 0;
2788	    }
2789	    break;
2790	case 'M':
2791	case 'm':
2792	    TRACE(("found screen color register mapping \"%s\"\n",
2793		   fragment_to_tempstr(&optionarg)));
2794	    {
2795		RegisDataFragment regnum;
2796		RegisDataFragment colorspec;
2797		char ch;
2798
2799		while (fragment_len(&optionarg)) {
2800		    if (skip_regis_whitespace(&optionarg))
2801			continue;
2802		    if (extract_regis_num(&optionarg, &regnum)) {
2803			int register_num;
2804			int color_only;
2805			short r, g, b;
2806
2807			if (!regis_num_to_int(&regnum, &register_num)) {
2808			    TRACE(("DATA_ERROR: unable to parse int in screen color register mapping option: \"%s\"\n",
2809				   fragment_to_tempstr(&regnum)));
2810			    return 0;
2811			}
2812			if (register_num < 0 ||
2813			    register_num > (int) context->graphic->valid_registers) {
2814			    TRACE(("interpreting out of range register number %d as 0 FIXME\n", register_num));
2815			    register_num = 0;
2816			}
2817			skip_regis_whitespace(&optionarg);
2818			if (!extract_regis_optionset(&optionarg, &colorspec)) {
2819			    TRACE(("DATA_ERROR: expected to find optionset after register number: \"%s\"\n",
2820				   fragment_to_tempstr(&optionarg)));
2821			    return 0;
2822			}
2823
2824			switch (peek_fragment(&colorspec)) {
2825			case 'A':
2826			case 'a':
2827			    pop_fragment(&colorspec);
2828			    color_only = 1;
2829			    break;
2830			default:
2831			    color_only = 0;
2832			    break;
2833			}
2834
2835			TRACE(("mapping register %d to color spec: \"%s\"\n",
2836			       register_num, fragment_to_tempstr(&colorspec)));
2837			if (fragment_len(&colorspec) == 1) {
2838			    short l;
2839			    ch = pop_fragment(&colorspec);
2840
2841			    TRACE(("got regis RGB colorspec pattern: \"%s\"\n",
2842				   fragment_to_tempstr(&colorspec)));
2843			    switch (ch) {
2844			    case 'D':
2845			    case 'd':
2846				r = 0;
2847				g = 0;
2848				b = 0;
2849				l = 0;
2850				break;
2851			    case 'R':
2852			    case 'r':
2853				r = 100;
2854				g = 0;
2855				b = 0;
2856				l = 46;
2857				break;
2858			    case 'G':
2859			    case 'g':
2860				r = 0;
2861				g = 100;
2862				b = 0;
2863				l = 50;
2864				break;
2865			    case 'B':
2866			    case 'b':
2867				r = 0;
2868				g = 0;
2869				b = 100;
2870				l = 50;
2871				break;
2872			    case 'C':
2873			    case 'c':
2874				r = 0;
2875				g = 100;
2876				b = 100;
2877				l = 50;
2878				break;
2879			    case 'Y':
2880			    case 'y':
2881				r = 100;
2882				g = 100;
2883				b = 0;
2884				l = 50;
2885				break;
2886			    case 'M':
2887			    case 'm':
2888				r = 100;
2889				g = 0;
2890				b = 100;
2891				l = 50;
2892				break;
2893			    case 'W':
2894			    case 'w':
2895				r = 100;
2896				g = 100;
2897				b = 100;
2898				l = 100;
2899				break;
2900			    default:
2901				TRACE(("unknown RGB color name: \"%c\"\n", ch));
2902				return 0;
2903			    }
2904			    if (context->terminal_id == 240 ||
2905				context->terminal_id == 330) {
2906				/* The VT240 and VT330 models force saturation to zero. */
2907				hls2rgb(0, l, 0, &r, &g, &b);
2908			    }
2909			} else {
2910			    short h, l, s;
2911
2912			    if (sscanf(fragment_to_tempstr(&colorspec),
2913				       "%*1[Hh]%hd%*1[Ll]%hd%*1[Ss]%hd",
2914				       &h, &l, &s) != 3) {
2915				h = 0;
2916				s = 0;
2917				if (sscanf(fragment_to_tempstr(&colorspec),
2918					   "%*1[Ll]%hd", &l) != 1) {
2919				    TRACE(("unrecognized colorspec: \"%s\"\n",
2920					   fragment_to_tempstr(&colorspec)));
2921				    return 0;
2922				}
2923			    }
2924			    if (context->terminal_id == 240 ||
2925				context->terminal_id == 330) {
2926				/* The VT240 and VT330 models force saturation to zero. */
2927				h = 0;
2928				s = 0;
2929			    }
2930			    hls2rgb(h, l, s, &r, &g, &b);
2931			}
2932
2933			if (color_only &&
2934			    (context->terminal_id == 240 ||
2935			     context->terminal_id == 330))
2936			    continue;
2937			update_color_register(context->graphic,
2938					      (RegisterNum) register_num,
2939					      r, g, b);
2940			continue;
2941		    }
2942
2943		    TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS screen color register mapping value \"%c\"\n",
2944			   pop_fragment(&optionarg)));
2945		    return 0;
2946		}
2947	    }
2948	    break;
2949	case 'P':
2950	case 'p':
2951	    TRACE(("found graphics page request \"%s\" FIXME\n",
2952		   fragment_to_tempstr(&optionarg)));
2953	    /* FIXME: handle */
2954	    if (!fragment_len(&optionarg)) {
2955		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen graphics page option value \"%s\"\n",
2956		       fragment_to_tempstr(&optionarg)));
2957		return 0;
2958	    }
2959	    break;
2960	case 'T':
2961	case 't':
2962	    TRACE(("found time delay \"%s\" FIXME\n", fragment_to_tempstr(&optionarg)));
2963	    /* FIXME: handle */
2964	    if (!fragment_len(&optionarg)) {
2965		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen time delay option value \"%s\"\n",
2966		       fragment_to_tempstr(&optionarg)));
2967		return 0;
2968	    }
2969	    break;
2970	case 'W':
2971	case 'w':
2972	    TRACE(("found PV \"%s\" FIXME\n", fragment_to_tempstr(&optionarg)));
2973	    /* FIXME: handle */
2974	    if (!fragment_len(&optionarg)) {
2975		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen PV option value \"%s\"\n",
2976		       fragment_to_tempstr(&optionarg)));
2977		return 0;
2978	    }
2979	    break;
2980	default:
2981	    TRACE(("DATA_ERROR: ignoring unknown ReGIS screen command option '%c' arg \"%s\"\n",
2982		   state->option, fragment_to_tempstr(&optionarg)));
2983	    break;
2984	}
2985	break;
2986    case 't':
2987	TRACE(("inspecting text option \"%c\" with value \"%s\"\n",
2988	       state->option, fragment_to_tempstr(&optionarg)));
2989	if (!fragment_len(&optionarg)) {
2990	    TRACE(("DATA_ERROR: ignoring malformed ReGIS text command option value \"%s\"\n",
2991		   fragment_to_tempstr(&optionarg)));
2992	    return 0;
2993	}
2994	switch (state->option) {
2995	case 'A':
2996	case 'a':
2997	    TRACE(("found character set specifier \"%s\" FIXME\n",
2998		   fragment_to_tempstr(&optionarg)));
2999	    /* FIXME: handle */
3000	    break;
3001	case 'B':
3002	case 'b':
3003	    TRACE(("found beginning of temporary text control \"%s\" FIXME\n",
3004		   fragment_to_tempstr(&optionarg)));
3005	    /* FIXME: handle */
3006	    break;
3007	case 'D':
3008	case 'd':
3009	    TRACE(("found string tilt control \"%s\" FIXME\n",
3010		   fragment_to_tempstr(&optionarg)));
3011	    /* FIXME: handle */
3012	    break;
3013	case 'E':
3014	case 'e':
3015	    TRACE(("found end of temporary text control \"%s\" FIXME\n",
3016		   fragment_to_tempstr(&optionarg)));
3017	    /* FIXME: handle */
3018	    break;
3019	case 'H':
3020	case 'h':
3021	    TRACE(("found height multiplier \"%s\" FIXME\n",
3022		   fragment_to_tempstr(&optionarg)));
3023	    /* FIXME: handle */
3024	    break;
3025	case 'I':
3026	case 'i':
3027	    TRACE(("found italic control \"%s\" FIXME\n",
3028		   fragment_to_tempstr(&optionarg)));
3029	    /* FIXME: handle */
3030	    break;
3031	case 'M':
3032	case 'm':
3033	    TRACE(("found size multiplier \"%s\" FIXME\n",
3034		   fragment_to_tempstr(&optionarg)));
3035	    /* FIXME: handle */
3036	    break;
3037	case 'S':
3038	case 's':
3039	    TRACE(("found custom display cell size \"%s\" FIXME\n",
3040		   fragment_to_tempstr(&optionarg)));
3041	    /* FIXME: handle */
3042	    break;
3043	case 'U':
3044	case 'u':
3045	    TRACE(("found custom display unit size \"%s\" FIXME\n",
3046		   fragment_to_tempstr(&optionarg)));
3047	    /* FIXME: handle */
3048	    break;
3049	default:
3050	    TRACE(("DATA_ERROR: ignoring unknown ReGIS text command option '%c' arg \"%s\"\n",
3051		   state->option, fragment_to_tempstr(&optionarg)));
3052	    break;
3053	}
3054	break;
3055    case 'v':
3056	TRACE(("inspecting vector option \"%c\" with value \"%s\"\n",
3057	       state->option, fragment_to_tempstr(&optionarg)));
3058	switch (state->option) {
3059	case 'B':
3060	case 'b':
3061	    TRACE(("found begin bounded position stack \"%s\" FIXME\n",
3062		   fragment_to_tempstr(&optionarg)));
3063	    /* FIXME: handle */
3064	    break;
3065	case 'E':
3066	case 'e':
3067	    TRACE(("found end position stack \"%s\" FIXME\n",
3068		   fragment_to_tempstr(&optionarg)));
3069	    /* FIXME: handle */
3070	    break;
3071	case 'S':
3072	case 's':
3073	    TRACE(("found begin unbounded position stack \"%s\" FIXME\n",
3074		   fragment_to_tempstr(&optionarg)));
3075	    /* FIXME: handle */
3076	    break;
3077	case 'W':
3078	case 'w':
3079	    TRACE(("found temporary write options \"%s\"\n",
3080		   fragment_to_tempstr(&optionarg)));
3081	    if (!load_regis_write_control_set(state, context->graphic,
3082					      context->graphics_output_cursor_x, context->graphics_output_cursor_y,
3083					      &optionarg, &context->temporary_write_controls)) {
3084		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
3085		       fragment_to_tempstr(&optionarg)));
3086	    }
3087	    break;
3088	default:
3089	    TRACE(("DATA_ERROR: ignoring unknown ReGIS vector command option '%c' arg \"%s\"\n",
3090		   state->option, fragment_to_tempstr(&optionarg)));
3091	    break;
3092	}
3093	break;
3094    case 'w':
3095	skip_regis_whitespace(&optionarg);
3096	TRACE(("inspecting write option \"%c\" with value \"%s\"\n",
3097	       state->option, fragment_to_tempstr(&optionarg)));
3098	if (!load_regis_write_control(state, context->graphic,
3099				      context->graphics_output_cursor_x, context->graphics_output_cursor_y,
3100				      state->option, &optionarg, &context->persistent_write_controls)) {
3101	    TRACE(("DATA_ERROR: invalid write options\n"));
3102	}
3103	break;
3104    default:
3105	TRACE(("DATA_ERROR: unexpected option in \"%c\" command: \"%s\"\n",
3106	       state->command, fragment_to_tempstr(&optionarg)));
3107	return 0;
3108    }
3109
3110    return 1;
3111}
3112
3113static int
3114parse_regis_items(RegisParseState *state, RegisGraphicsContext *context)
3115{
3116    RegisDataFragment *input;
3117    RegisDataFragment item;
3118
3119    switch (state->level) {
3120    case INPUT:
3121	input = &state->input;
3122	break;
3123    case OPTIONSET:
3124	input = &state->optionset;
3125	break;
3126    default:
3127	TRACE(("invalid parse level: %d\n", state->level));
3128	return 0;
3129    }
3130
3131    if (input->pos >= input->len)
3132	return 0;
3133
3134    if (extract_regis_extent(input, &item)) {
3135	TRACE(("found extent \"%s\"\n", fragment_to_tempstr(&item)));
3136	switch (state->command) {
3137	case 'c':
3138	    {
3139		int orig_x, orig_y;
3140		int new_x, new_y;
3141
3142		if (state->num_points > 0) {
3143		    orig_x = state->x_points[state->num_points - 1];
3144		    orig_y = state->y_points[state->num_points - 1];
3145		} else {
3146		    orig_x = context->graphics_output_cursor_x;
3147		    orig_y = context->graphics_output_cursor_y;
3148		}
3149		if (!load_regis_extent(fragment_to_tempstr(&item),
3150				       orig_x, orig_y,
3151				       &new_x, &new_y)) {
3152		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
3153			   state->command, fragment_to_tempstr(&item)));
3154		    break;
3155		}
3156
3157		switch (state->curve_mode) {
3158		case CURVE_POSITION_ARC_CENTER:
3159		case CURVE_POSITION_ARC_EDGE:
3160		    {
3161			double radians;
3162			int degrees;
3163			int c_x, c_y;
3164			int e_x, e_y;
3165			int e_x_final = 0, e_y_final = 0;
3166
3167			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
3168			    c_x = new_x;
3169			    c_y = new_y;
3170			    e_x = orig_x;
3171			    e_y = orig_y;
3172			} else {
3173			    c_x = orig_x;
3174			    c_y = orig_y;
3175			    e_x = new_x;
3176			    e_y = new_y;
3177			}
3178
3179			radians = atan2((double) (c_y - e_y),
3180					(double) (e_x - c_x));
3181			degrees = (int) (360.0 * radians / (2.0 * M_PI));
3182			if (degrees < 0)
3183			    degrees += 360;
3184
3185			TRACE(("drawing arc centered at location %d,%d to location %d,%d from %d degrees (%g radians) for %d degrees\n",
3186			       c_x, c_y,
3187			       e_x, e_y,
3188			       degrees, radians, state->arclen));
3189			draw_patterned_arc(context,
3190					   c_x, c_y,
3191					   e_x, e_y,
3192					   degrees, state->arclen,
3193					   &e_x_final, &e_y_final);
3194
3195#ifdef DEBUG_ARC_CENTER
3196			draw_solid_pixel(context->graphic, c_x + 1, c_y, 3U);
3197			draw_solid_pixel(context->graphic, c_x - 1, c_y, 3U);
3198			draw_solid_pixel(context->graphic, c_x, c_y + 1, 3U);
3199			draw_solid_pixel(context->graphic, c_x, c_y - 1, 3U);
3200			draw_solid_pixel(context->graphic, c_x, c_y, 3U);
3201#endif
3202
3203#ifdef DEBUG_ARC_START
3204			draw_solid_pixel(context->graphic, e_x + 1, e_y, 2U);
3205			draw_solid_pixel(context->graphic, e_x - 1, e_y, 2U);
3206			draw_solid_pixel(context->graphic, e_x, e_y + 1, 2U);
3207			draw_solid_pixel(context->graphic, e_x, e_y - 1, 2U);
3208			draw_solid_pixel(context->graphic, e_x, e_y, 2U);
3209#endif
3210
3211#ifdef DEBUG_ARC_END
3212			draw_solid_pixel(context->graphic, e_x_final + 1,
3213					 e_y_final + 1, 1U);
3214			draw_solid_pixel(context->graphic, e_x_final + 1,
3215					 e_y_final - 1, 1U);
3216			draw_solid_pixel(context->graphic, e_x_final - 1,
3217					 e_y_final + 1, 1U);
3218			draw_solid_pixel(context->graphic, e_x_final - 1,
3219					 e_y_final - 1, 1U);
3220			draw_solid_pixel(context->graphic, e_x_final,
3221					 e_y_final, 1U);
3222#endif
3223
3224			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
3225			    TRACE(("moving cursor to final point on arc %d,%d\n",
3226				   e_x_final, e_y_final));
3227			    if (state->num_points > 0) {
3228				state->x_points[state->num_points - 1] = e_x_final;
3229				state->y_points[state->num_points - 1] = e_y_final;
3230			    }
3231			    context->graphics_output_cursor_x = e_x_final;
3232			    context->graphics_output_cursor_y = e_y_final;
3233			}
3234		    }
3235		    break;
3236		case CURVE_POSITION_OPEN_CURVE:
3237		case CURVE_POSITION_CLOSED_CURVE:
3238		    if (state->num_points >= MAX_INPUT_CURVE_POINTS) {
3239			TRACE(("DATA_ERROR: got curve point, but already have max points (%d)\n", state->num_points));
3240			break;
3241		    }
3242		    state->x_points[state->num_points] = new_x;
3243		    state->y_points[state->num_points] = new_y;
3244		    state->num_points++;
3245		    TRACE(("adding point to curve with location %d,%d\n",
3246			   new_x, new_y));
3247		    break;
3248		default:
3249		    TRACE(("ERROR: got position, but curve mode %d is unknown\n", state->curve_mode));
3250		    break;
3251		}
3252	    }
3253	    break;
3254	case 'p':
3255	    /* FIXME TRACE(("DATA_ERROR: ignoring pen command with no location\n")); */
3256	    if (!load_regis_extent(fragment_to_tempstr(&item),
3257				   context->graphics_output_cursor_x, context->graphics_output_cursor_y,
3258				   &context->graphics_output_cursor_x, &context->graphics_output_cursor_y)) {
3259		TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
3260		       state->command, fragment_to_tempstr(&item)));
3261		break;
3262	    }
3263	    TRACE(("moving pen to location %d,%d\n",
3264		   context->graphics_output_cursor_x,
3265		   context->graphics_output_cursor_y));
3266	    break;
3267	case 's':
3268	    /* FIXME: parse, handle */
3269	    TRACE(("extent in screen command FIXME\n"));
3270	    break;
3271	case 't':
3272	    /* FIXME: parse, handle */
3273	    TRACE(("extent in text command FIXME\n"));
3274	    break;
3275	case 'v':
3276	    {
3277		int orig_x, orig_y;
3278
3279		orig_x = context->graphics_output_cursor_x;
3280		orig_y = context->graphics_output_cursor_y;
3281		if (!load_regis_extent(fragment_to_tempstr(&item),
3282				       orig_x, orig_y,
3283				       &context->graphics_output_cursor_x, &context->graphics_output_cursor_y)) {
3284		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
3285			   state->command, fragment_to_tempstr(&item)));
3286		    break;
3287		}
3288		TRACE(("drawing line to location %d,%d\n",
3289		       context->graphics_output_cursor_x,
3290		       context->graphics_output_cursor_y));
3291		draw_patterned_line(context,
3292				    orig_x, orig_y,
3293				    context->graphics_output_cursor_x,
3294				    context->graphics_output_cursor_y);
3295	    }
3296	    break;
3297	default:
3298	    TRACE(("DATA_ERROR: unexpected extent in \"%c\" command: \"%s\"\n",
3299		   state->command, fragment_to_tempstr(&item)));
3300	    break;
3301	}
3302	return 1;
3303    }
3304
3305    if (extract_regis_pixelvector(input, &item)) {
3306	TRACE(("found pixel vector \"%s\"\n", fragment_to_tempstr(&item)));
3307	switch (state->command) {
3308	case 'c':
3309	    /* FIXME: parse, handle */
3310	    TRACE(("pixelvector in curve command FIXME\n"));
3311	    break;
3312	    /* FIXME: not sure if 'f' supports pvs */
3313	case 'p':
3314	    /* FIXME: error checking */
3315	    if (!load_regis_pixelvector(fragment_to_tempstr(&item), context->temporary_write_controls.pv_multiplier,
3316					context->graphics_output_cursor_x, context->graphics_output_cursor_y,
3317					&context->graphics_output_cursor_x, &context->graphics_output_cursor_y)) {
3318		TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
3319		       state->command, fragment_to_tempstr(&item)));
3320		break;
3321	    }
3322	    TRACE(("moving pen to location %d,%d\n",
3323		   context->graphics_output_cursor_x,
3324		   context->graphics_output_cursor_y));
3325	    break;
3326	case 's':
3327	    /* FIXME: parse, handle scroll argument */
3328	    TRACE(("pixelvector in screen command FIXME\n"));
3329	    break;
3330	case 't':
3331	    /* FIXME: parse, handle */
3332	    TRACE(("pixelvector in text command FIXME\n"));
3333	    break;
3334	case 'v':
3335	    /* FIXME: error checking */
3336	    {
3337		int orig_x, orig_y;
3338
3339		orig_x = context->graphics_output_cursor_x;
3340		orig_y = context->graphics_output_cursor_y;
3341		if (!load_regis_pixelvector(fragment_to_tempstr(&item), context->temporary_write_controls.pv_multiplier,
3342					    orig_x, orig_y,
3343					    &context->graphics_output_cursor_x, &context->graphics_output_cursor_y)) {
3344		    TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
3345			   state->command, fragment_to_tempstr(&item)));
3346		    break;
3347		}
3348		TRACE(("drawing line to location %d,%d\n",
3349		       context->graphics_output_cursor_x,
3350		       context->graphics_output_cursor_y));
3351		draw_patterned_line(context, orig_x, orig_y,
3352				    context->graphics_output_cursor_x,
3353				    context->graphics_output_cursor_y);
3354	    }
3355	    break;
3356	default:
3357	    TRACE(("DATA_ERROR: unexpected pixel vector in \"%c\" command: \"%s\"\n",
3358		   state->command, fragment_to_tempstr(&item)));
3359	    break;
3360	}
3361	return 1;
3362    }
3363
3364    if (extract_regis_string(input, state->temp, state->templen)) {
3365	switch (state->command) {
3366	case 'l':
3367	    TRACE(("found character to load: \"%s\" FIXME\n", state->temp));
3368	    /* FIXME: handle */
3369	case 't':
3370	    TRACE(("found string to draw: \"%s\" FIXME\n", state->temp));
3371	    /* FIXME: handle */
3372	    break;
3373	default:
3374	    TRACE(("DATA_ERROR: unexpected string in \"%c\" command: \"%s\"\n",
3375		   state->command, state->temp));
3376	    break;
3377	}
3378	return 1;
3379    }
3380
3381    /* hex values */
3382    if (state->command == 'l') {
3383	char ch1 = peek_fragment(input);
3384	char ch2 = peek_fragment(input);
3385	if ((ch1 == '0' ||
3386	     ch1 == '1' ||
3387	     ch1 == '2' ||
3388	     ch1 == '3' ||
3389	     ch1 == '4' ||
3390	     ch1 == '5' ||
3391	     ch1 == '6' ||
3392	     ch1 == '7' ||
3393	     ch1 == '8' ||
3394	     ch1 == '9' ||
3395	     ch1 == 'a' ||
3396	     ch1 == 'b' ||
3397	     ch1 == 'c' ||
3398	     ch1 == 'd' ||
3399	     ch1 == 'e' ||
3400	     ch1 == 'f' ||
3401	     ch1 == 'A' ||
3402	     ch1 == 'B' ||
3403	     ch1 == 'C' ||
3404	     ch1 == 'D' ||
3405	     ch1 == 'E' ||
3406	     ch1 == 'F') &&
3407	    (ch2 == '0' ||
3408	     ch2 == '1' ||
3409	     ch2 == '2' ||
3410	     ch2 == '3' ||
3411	     ch2 == '4' ||
3412	     ch2 == '5' ||
3413	     ch2 == '6' ||
3414	     ch2 == '7' ||
3415	     ch2 == '8' ||
3416	     ch2 == '9' ||
3417	     ch2 == 'a' ||
3418	     ch2 == 'b' ||
3419	     ch2 == 'c' ||
3420	     ch2 == 'd' ||
3421	     ch2 == 'e' ||
3422	     ch2 == 'f' ||
3423	     ch2 == 'A' ||
3424	     ch2 == 'B' ||
3425	     ch2 == 'C' ||
3426	     ch2 == 'D' ||
3427	     ch2 == 'E' ||
3428	     ch2 == 'F')) {
3429	    /* FIXME: handle */
3430	    TRACE(("found hex number: \"%c%c\" FIXME\n", ch1, ch2));
3431	    pop_fragment(input);
3432	    pop_fragment(input);
3433	    if (peek_fragment(input) == ',')
3434		pop_fragment(input);
3435	    return 1;
3436	}
3437    }
3438
3439    return 0;
3440}
3441
3442/*
3443 * context:
3444 * two pages of 800x480
3445 * current page #
3446 * current command
3447 * persistent write options
3448 * temporary write options
3449 * output position stack
3450 */
3451void
3452parse_regis(XtermWidget xw, ANSI *params, char const *string)
3453{
3454    TScreen *screen = TScreenOf(xw);
3455    RegisGraphicsContext context;
3456    RegisParseState state;
3457    unsigned iterations;
3458    int charrow = 0;
3459    int charcol = 0;
3460    unsigned type = 1;		/* FIXME: use page number */
3461
3462    (void) xw;
3463    (void) string;
3464    (void) params;
3465
3466    TRACE(("ReGIS vector graphics mode, params=%d\n", params->a_nparam));
3467
3468    init_fragment(&state.input, string);
3469    init_fragment(&state.optionset, "");
3470    state.level = INPUT;
3471    state.templen = (unsigned) strlen(string) + 1U;
3472    if (!(state.temp = malloc((size_t) state.templen))) {
3473	TRACE(("Unable to allocate temporary buffer of size %u\n", state.templen));
3474	return;
3475    }
3476    state.command = '_';
3477    state.option = '_';
3478
3479    memset(&context, 0, sizeof(context));
3480
3481    context.graphic = get_new_or_matching_graphic(xw,
3482						  charrow, charcol,
3483						  800, 480,
3484						  type);
3485    init_regis_graphics_context(screen->terminal_id, &context);
3486    context.graphic->valid = 1;
3487    context.graphic->dirty = 1;
3488    refresh_modified_displayed_graphics(screen);
3489
3490    iterations = 0U;
3491    for (;;) {
3492	state.level = INPUT;
3493	TRACE(("parsing at top level: %d of %d (next char %c)\n",
3494	       state.input.pos,
3495	       state.input.len,
3496	       peek_fragment(&state.input)));
3497	if (skip_regis_whitespace(&state.input))
3498	    continue;
3499	iterations++;
3500	if (parse_regis_command(&state)) {
3501	    if (iterations > ITERATIONS_BEFORE_REFRESH) {
3502		iterations = 0U;
3503		refresh_modified_displayed_graphics(screen);
3504	    }
3505	    context.graphic->dirty = 1;
3506	    /* FIXME: verify that these are the things reset on a new command */
3507	    copy_regis_write_controls(&context.persistent_write_controls, &context.temporary_write_controls);
3508	    context.pattern_count = 0U;
3509	    context.pattern_bit = 1U;
3510	    continue;
3511	}
3512	if (parse_regis_optionset(&state)) {
3513	    state.level = OPTIONSET;
3514	    TRACE(("parsing at optionset level: %d of %d\n",
3515		   state.optionset.pos,
3516		   state.optionset.len));
3517	    for (;;) {
3518		if (state.optionset.pos >= state.optionset.len)
3519		    break;
3520		TRACE(("looking at optionset character: \"%c\"\n",
3521		       peek_fragment(&state.optionset)));
3522		if (skip_regis_whitespace(&state.optionset))
3523		    continue;
3524		if (peek_fragment(&state.optionset) == ',') {
3525		    pop_fragment(&state.optionset);
3526		    continue;
3527		}
3528		if (parse_regis_option(&state, &context))
3529		    continue;
3530		if (parse_regis_items(&state, &context))
3531		    continue;
3532		if (state.optionset.pos >= state.optionset.len)
3533		    break;
3534		TRACE(("DATA_ERROR: skipping unknown token in optionset: \"%c\"\n",
3535		       pop_fragment(&state.optionset)));
3536		/* FIXME: suboptions */
3537	    }
3538	    state.option = '_';
3539	    continue;
3540	}
3541	if (parse_regis_items(&state, &context))
3542	    continue;
3543	if (state.optionset.pos >= state.optionset.len)
3544	    break;
3545	TRACE(("DATA_ERROR: skipping unknown token at top level: \"%c\"\n",
3546	       pop_fragment(&state.input)));
3547    }
3548
3549    free(state.temp);
3550
3551    refresh_modified_displayed_graphics(screen);
3552    TRACE(("DONE! Successfully parsed ReGIS data.\n"));
3553}
3554