graphics_regis.c revision 2e4f8982
1/* $XTermId: graphics_regis.c,v 1.80 2016/05/29 16:20:42 tom Exp $ */
2
3/*
4 * Copyright 2014-2015,2016 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#if OPT_DOUBLE_BUFFER
41#include <X11/extensions/Xdbe.h>
42#endif
43
44#include <data.h>
45#include <VTparse.h>
46#include <ptyx.h>
47
48#include <assert.h>
49#include <graphics.h>
50#include <graphics_regis.h>
51
52/* get rid of shadowing warnings (we will not draw Bessel functions) */
53#define y1 my_y1
54#define y0 my_y0
55
56#define IS_HEX_DIGIT(CH) ( \
57  (CH) == '0' || \
58  (CH) == '1' || \
59  (CH) == '2' || \
60  (CH) == '3' || \
61  (CH) == '4' || \
62  (CH) == '5' || \
63  (CH) == '6' || \
64  (CH) == '7' || \
65  (CH) == '8' || \
66  (CH) == '9' || \
67  (CH) == 'a' || \
68  (CH) == 'b' || \
69  (CH) == 'c' || \
70  (CH) == 'd' || \
71  (CH) == 'e' || \
72  (CH) == 'f' || \
73  (CH) == 'A' || \
74  (CH) == 'B' || \
75  (CH) == 'C' || \
76  (CH) == 'D' || \
77  (CH) == 'E' || \
78  (CH) == 'F' )
79
80#define SCALE_FIXED_POINT 16U
81
82#undef DEBUG_PARSING
83#undef DEBUG_ALPHABET_LOOKUP
84#undef DEBUG_ALPHABETS
85#undef DEBUG_BEZIER
86#undef DEBUG_SPLINE_SEGMENTS
87#undef DEBUG_SPLINE_POINTS
88#undef DEBUG_SPLINE_WITH_ROTATION
89#undef DEBUG_SPLINE_WITH_OVERDRAW
90#undef DEBUG_ARC_POINTS
91#undef DEBUG_ARC_CENTER
92#undef DEBUG_ARC_START
93#undef DEBUG_ARC_END
94#undef DEBUG_SPECIFIC_CHAR_METRICS
95#define IS_DEBUG_CHAR(CH) ((CH) == 'W')		/* glyphs to dump to terminal */
96#undef DEBUG_COMPUTED_FONT_METRICS
97#undef DEBUG_FONT_NAME
98#undef DEBUG_FONT_SIZE_SEARCH
99#undef DEBUG_XFT_GLYPH
100#undef DEBUG_USER_GLYPH
101#undef DEBUG_LOAD
102
103/* controls for extensions over VT3x0 limitations */
104#define ENABLE_RGB_COLORSPECS
105#define ENABLE_FREE_ROTATION
106#define ENABLE_UPLOAD_ALPHABET_FROM_FONT
107#define ENABLE_UPLOAD_ALPHABET_ZERO
108#define ENABLE_USER_FONT_SIZE
109#define ENABLE_VARIABLE_ITALICS
110
111#define MIN_ITERATIONS_BEFORE_REFRESH 10U
112#define MIN_MS_BEFORE_REFRESH 33
113/* *INDENT-OFF* */
114typedef struct RegisPoint {
115    int  x, y;
116} RegisPoint;
117
118typedef struct RegisWriteControls {
119    unsigned     pv_multiplier;
120    unsigned     pattern;
121    unsigned     pattern_multiplier;
122    unsigned     invert_pattern;
123    unsigned     plane_mask;
124    unsigned     write_style;
125    RegisterNum  foreground;
126    unsigned     shading_enabled;
127    char         shading_character;
128    int          shading_reference;
129    unsigned     shading_reference_dim;
130    unsigned     line_width;
131} RegisWriteControls;
132
133typedef struct RegisTextControls {
134    unsigned  alphabet_num;
135    unsigned  character_set_l; /* default: "(B" (ASCII) */
136    unsigned  character_set_r; /* default: "-@" (Latin-1) */
137    unsigned  character_display_w;
138    unsigned  character_display_h;
139    unsigned  character_unit_cell_w;
140    unsigned  character_unit_cell_h;
141    int       character_inc_x;
142    int       character_inc_y;
143    int       string_rotation;
144    int       character_rotation;
145    int       slant; /* for italic/oblique */
146} RegisTextControls;
147
148#define FixedCopy(dst, src, len) strncpy(dst, src, len - 1)[len - 1] = '\0'
149#define CopyFontname(dst, src) FixedCopy(dst, src, REGIS_FONTNAME_LEN)
150
151#define MAX_REGIS_PAGES 8U
152
153#define MAX_REGIS_ALPHABETS 8U
154#define REGIS_ALPHABET_NAME_LEN 11U
155#define REGIS_FONTNAME_LEN 256U
156/* enough for a 16x24 font (about 100KB) */
157#define MAX_REGIS_ALPHABET_BYTES (256U * 16U * 24U)
158#define MAX_GLYPH_PIXELS 8192U
159#define MAX_GLYPHS 256U
160#define INVALID_ALPHABET_NUM ~0U
161
162typedef struct RegisAlphabet {
163    unsigned       alphabet_num;
164    unsigned       pixw, pixh;
165    char           name[REGIS_ALPHABET_NAME_LEN];
166    char           fontname[REGIS_FONTNAME_LEN];
167    int            use_font;
168    int            loaded[MAX_GLYPHS];
169    unsigned char *bytes;
170} RegisAlphabet;
171
172typedef struct RegisDataFragment {
173    char const  *start;
174    unsigned     pos;
175    unsigned     len;
176} RegisDataFragment;
177/* *INDENT-ON* */
178
179#define POSITION_STACK_SIZE 16U
180#define DUMMY_STACK_X -32768
181#define DUMMY_STACK_Y -32768
182
183#define CURVE_POSITION_ARC_EDGE     0U
184#define CURVE_POSITION_ARC_CENTER   1U
185#define CURVE_POSITION_OPEN_CURVE   2U
186#define CURVE_POSITION_CLOSED_CURVE 3U
187
188#define MAX_INPUT_CURVE_POINTS 16U
189#define MAX_CURVE_POINTS (MAX_INPUT_CURVE_POINTS + 4U)
190
191#define MAX_FILL_POINTS 2048U
192
193typedef struct RegisParseState {
194    RegisDataFragment input;
195    char *temp;
196    unsigned templen;
197    char command;
198    char option;
199    /* position stack */
200    int stack_x[POSITION_STACK_SIZE];
201    int stack_y[POSITION_STACK_SIZE];
202    unsigned stack_next;	/* next empty position */
203    /* curve options */
204    int curve_mode;
205    int arclen;
206    int x_points[MAX_CURVE_POINTS];
207    int y_points[MAX_CURVE_POINTS];
208    unsigned num_points;
209    /* load options */
210    char load_name[REGIS_ALPHABET_NAME_LEN];
211    unsigned load_alphabet;
212    unsigned load_w, load_h;
213    unsigned load_index;
214    unsigned load_glyph;
215    unsigned load_row;
216    /* text options */
217    int string_rot_set;		/* flag to distinguish string vs. character rotation */
218} RegisParseState;
219
220typedef struct RegisGraphicsContext {
221    Graphic *destination_graphic;
222    Graphic *display_graphic;
223    int terminal_id;
224    int x_off, y_off;
225    int x_div, y_div;
226    int width, height;
227    unsigned all_planes;
228    RegisterNum background;
229    char const *builtin_font;
230    RegisAlphabet alphabets[MAX_REGIS_ALPHABETS];
231    RegisWriteControls persistent_write_controls;
232    RegisWriteControls temporary_write_controls;
233    RegisTextControls persistent_text_controls;
234    RegisTextControls temporary_text_controls;
235    RegisTextControls *current_text_controls;
236    int multi_input_mode;
237    int graphics_output_cursor_x;
238    int graphics_output_cursor_y;
239    unsigned pattern_count;
240    unsigned pattern_bit;
241    int fill_mode;
242    RegisPoint fill_points[MAX_FILL_POINTS];
243    unsigned fill_point_count;
244    unsigned destination_page;
245    unsigned display_page;
246    int force_refresh;
247} RegisGraphicsContext;
248
249static RegisGraphicsContext persistent_context;
250static RegisParseState persistent_state;
251
252#define MAX_PATTERN_BITS 8U
253
254#define WRITE_STYLE_OVERLAY 1U
255#define WRITE_STYLE_REPLACE 2U
256#define WRITE_STYLE_COMPLEMENT 3U
257#define WRITE_STYLE_ERASE 4U
258
259#define WRITE_SHADING_REF_Y 0U
260#define WRITE_SHADING_REF_X 1U
261#define WRITE_SHADING_REF_NONE 2U
262
263/* keypress event example: http://iraf.net/forum/viewtopic.php?showtopic=61692 */
264
265#define MIN2(X, Y) ( (X) < (Y) ? (X) : (Y) )
266#define MIN3(X, Y, Z) ( MIN2(MIN2((X), (Y)), MIN2((Y), (Z))) )
267#define MAX2(X, Y) ( (X) > (Y) ? (X) : (Y) )
268#define MAX3(X, Y, Z) ( MAX2(MAX2((X), (Y)), MAX2((Y), (Z))) )
269
270#define ROT_LEFT_N(V, N) ( (((V) << ((N) & 3U )) & 255U) | \
271			   ((V) >> (8U - ((N) & 3U))) )
272#define ROT_LEFT(V) ( (((V) << 1U) & 255U) | ((V) >> 7U) )
273
274/* convert user coordinates to absolute pixel coordinates */
275#define SCALE_XCOORD(C, X, S) ( ( (X) * ((C)->width  - 1) ) / ( (C)->x_div * (S) ) )
276#define SCALE_YCOORD(C, Y, S) ( ( (Y) * ((C)->height - 1) ) / ( (C)->y_div * (S) ) )
277#define TRANSLATE_XCOORD(C, X, S) SCALE_XCOORD((C), (X) - (C)->x_off * (S), (S) )
278#define TRANSLATE_YCOORD(C, Y, S) SCALE_YCOORD((C), (Y) - (C)->y_off * (S), (S) )
279
280#if 0
281/* convert absolute pixel coordinate to user coordinates */
282#define SCALE_XPIX(C, X, S) ( ( (X) * ((C)->x_div * (S) ) ) / ((C)->width  - 1) )
283#define SCALE_YPIX(C, Y, S) ( ( (Y) * ((C)->y_div * (S) ) ) / ((C)->height - 1) )
284#define TRANSLATE_XPIX(C, X, S) ( SCALE_XPIX((C), (X), (S) ) + (C)->x_off * (S) )
285#define TRANSLATE_YPIX(C, Y, S) ( SCALE_YPIX((C), (Y), (S) ) + (C)->y_off * (S) )
286#endif
287
288#define READ_PIXEL(C, X, Y) read_pixel((C)->destination_graphic, (X), (Y))
289#define DRAW_PIXEL(C, X, Y, COL) draw_solid_pixel((C)->destination_graphic, (X), (Y), (COL))
290#define DRAW_ALL(C, COL) \
291    draw_solid_rectangle((C)->destination_graphic, 0, 0, (C)->width, (C)->height, (COL))
292
293static unsigned get_shade_character_pixel(unsigned char const *pixels,
294					  unsigned w, unsigned h,
295					  unsigned smaxf, unsigned scale,
296					  int slant_dx, int px, int py);
297static void get_bitmap_of_character(RegisGraphicsContext const *context,
298				    int ch, unsigned maxw, unsigned maxh,
299				    unsigned char *pixels,
300				    unsigned *w, unsigned *h,
301				    unsigned max_pixels);
302
303static int
304ifloor(double d)
305{
306    double dl = floor(d);
307    return (int) dl;
308}
309
310static int
311isqrt(double d)
312{
313    double dl = sqrt(d);
314    return (int) dl;
315}
316
317static void
318draw_regis_pixel(RegisGraphicsContext *context, int x, int y,
319		 unsigned value)
320{
321    unsigned color = 0;
322
323    switch (context->temporary_write_controls.write_style) {
324    case WRITE_STYLE_OVERLAY:
325	/*
326	 * Update pixels with foreground when pattern is 1,
327	 * don't change when pattern is 0.
328	 */
329	if (!value) {
330	    return;
331	}
332
333	if (context->temporary_write_controls.invert_pattern) {
334	    color = context->background;
335	} else {
336	    color = context->temporary_write_controls.foreground;
337	}
338	break;
339
340    case WRITE_STYLE_REPLACE:
341	/*
342	 * Update pixels with foreground when pattern is 1,
343	 * set to background when pattern is 0.
344	 */
345	{
346	    unsigned fg, bg;
347
348	    if (context->temporary_write_controls.invert_pattern) {
349		fg = context->background;
350		bg = context->temporary_write_controls.foreground;
351	    } else {
352		fg = context->temporary_write_controls.foreground;
353		bg = context->background;
354	    }
355	    color = value ? fg : bg;
356	}
357	break;
358
359    case WRITE_STYLE_COMPLEMENT:
360	/*
361	 * Update pixels with background when pattern is 1,
362	 * don't change when pattern is 0.
363	 */
364	if (!value) {
365	    return;
366	}
367
368	color = READ_PIXEL(context, x, y);
369	if (color == COLOR_HOLE)
370	    color = context->background;
371	color = color ^ context->all_planes;
372	break;
373
374    case WRITE_STYLE_ERASE:
375	/* Update pixels to foreground. */
376	if (context->temporary_write_controls.invert_pattern) {
377	    color = context->temporary_write_controls.foreground;
378	} else {
379	    color = context->background;
380	}
381	break;
382    }
383
384    DRAW_PIXEL(context, x, y, color);
385}
386
387static void
388shade_pattern_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref,
389		       int x, int y)
390{
391    unsigned value;
392
393    if (dim == WRITE_SHADING_REF_X) {
394	int delta = x > ref ? 1 : -1;
395	int curr_x;
396
397	context->pattern_bit = 1U << (((unsigned) y) & 7U);
398	for (curr_x = ref; curr_x != x + delta; curr_x += delta) {
399	    value = context->temporary_write_controls.pattern &
400		context->pattern_bit;
401	    draw_regis_pixel(context, curr_x, y, value);
402	}
403    } else if (dim == WRITE_SHADING_REF_Y) {
404	int delta = y > ref ? 1 : -1;
405	int curr_y;
406
407	for (curr_y = ref; curr_y != y + delta; curr_y += delta) {
408	    context->pattern_bit = 1U << (((unsigned) curr_y) & 7U);
409	    value = context->temporary_write_controls.pattern &
410		context->pattern_bit;
411	    draw_regis_pixel(context, x, curr_y, value);
412	}
413    } else {
414	TRACE(("ERROR: shading requested, but there is no reference axis\n"));
415    }
416}
417
418static void
419shade_char_to_pixel(RegisGraphicsContext *context, unsigned char const *pixels,
420		    unsigned w, unsigned h, unsigned dim, int ref, int x, int y)
421{
422    unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
423    unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
424    unsigned smaxf;
425    unsigned s;
426    unsigned scale;
427    unsigned value;
428
429    if (xmaxf > ymaxf) {
430	smaxf = ymaxf;
431	s = h;
432    } else {
433	smaxf = xmaxf;
434	s = w;
435    }
436    scale = (s << SCALE_FIXED_POINT) / smaxf;
437
438    if (dim == WRITE_SHADING_REF_X) {
439	int delta = x > ref ? 1 : -1;
440	int curr_x;
441
442	for (curr_x = ref; curr_x != x + delta; curr_x += delta) {
443	    value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0,
444					      curr_x, y);
445	    draw_regis_pixel(context, curr_x, y, value);
446	}
447    } else if (dim == WRITE_SHADING_REF_Y) {
448	int delta = y > ref ? 1 : -1;
449	int curr_y;
450
451	for (curr_y = ref; curr_y != y + delta; curr_y += delta) {
452	    value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0, x,
453					      curr_y);
454	    draw_regis_pixel(context, x, curr_y, value);
455	}
456    } else {
457	TRACE(("ERROR: shading requested, but there is no reference axis\n"));
458    }
459}
460
461static void
462draw_patterned_pixel(RegisGraphicsContext *context, int x, int y)
463{
464    if (context->pattern_count >=
465	context->temporary_write_controls.pattern_multiplier) {
466	context->pattern_count = 0U;
467	context->pattern_bit = ROT_LEFT(context->pattern_bit);
468    }
469    context->pattern_count++;
470
471    draw_regis_pixel(context, x, y,
472		     context->temporary_write_controls.pattern &
473		     context->pattern_bit);
474}
475
476static void
477shade_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref,
478	       int x, int y)
479{
480    if (context->temporary_write_controls.shading_character != '\0') {
481	unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
482	unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
483	char ch = context->temporary_write_controls.shading_character;
484	unsigned char pixels[MAX_GLYPH_PIXELS];
485	unsigned w, h;
486
487	get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
488				MAX_GLYPH_PIXELS);
489	if (w > 0 && h > 0) {
490	    shade_char_to_pixel(context, pixels, w, h, dim, ref, x, y);
491	}
492    } else {
493	shade_pattern_to_pixel(context, dim, ref, x, y);
494    }
495}
496
497static void
498draw_or_save_patterned_pixel(RegisGraphicsContext *context, int x, int y)
499{
500    if (context->fill_mode == 1) {
501	if (context->fill_point_count >= MAX_FILL_POINTS) {
502	    TRACE(("point %d,%d can not be added to filled polygon\n",
503		   x, y));
504	    return;
505	}
506	if (context->fill_point_count > 0U &&
507	    context->fill_points[context->fill_point_count - 1U].x == x &&
508	    context->fill_points[context->fill_point_count - 1U].y == y) {
509	    return;
510	}
511	context->fill_points[context->fill_point_count].x = x;
512	context->fill_points[context->fill_point_count].y = y;
513	context->fill_point_count++;
514	return;
515    }
516
517    if (context->temporary_write_controls.shading_enabled) {
518	unsigned dim = context->temporary_write_controls.shading_reference_dim;
519	int ref = context->temporary_write_controls.shading_reference;
520
521	shade_to_pixel(context, dim, ref, x, y);
522	return;
523    }
524
525    draw_patterned_pixel(context, x, y);
526}
527
528static int
529sort_points(void const *l, void const *r)
530{
531    RegisPoint const *const lp = l;
532    RegisPoint const *const rp = r;
533
534    if (lp->y < rp->y)
535	return -1;
536    if (lp->y > rp->y)
537	return +1;
538    if (lp->x < rp->x)
539	return -1;
540    if (lp->x > rp->x)
541	return +1;
542    return 0;
543}
544
545static void
546draw_filled_polygon(RegisGraphicsContext *context)
547{
548    unsigned p;
549    int old_x, old_y;
550    int inside;
551    unsigned char pixels[MAX_GLYPH_PIXELS];
552    unsigned w, h;
553
554    if (context->temporary_write_controls.shading_character != '\0') {
555	char ch = context->temporary_write_controls.shading_character;
556	unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
557	unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
558
559	get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
560				MAX_GLYPH_PIXELS);
561	if (w < 1U || h < 1U) {
562	    return;
563	}
564    }
565
566    qsort(context->fill_points, context->fill_point_count,
567	  sizeof(context->fill_points[0]), sort_points);
568
569    old_x = DUMMY_STACK_X;
570    old_y = DUMMY_STACK_Y;
571    inside = 0;
572    for (p = 0U; p < context->fill_point_count; p++) {
573	int new_x = context->fill_points[p].x;
574	int new_y = context->fill_points[p].y;
575#if 0
576	printf("got %d,%d (%d,%d) inside=%d\n", new_x, new_y, old_x, old_y, inside);
577#endif
578
579	/*
580	 * FIXME: This is using pixels to represent lines which loses
581	 * information about exact slope and how many lines are present which
582	 * causes misbehavior with some inputs (especially complex polygons).
583	 * It also takes more room than remembering vertices, but I'd rather
584	 * not have to implement line segments for arcs.  Maybe store a count
585	 * at each vertex instead (doesn't fix the slope problem).
586	 */
587	/*
588	 * FIXME: Change this to only draw inside of polygons, and round
589	 * points in a uniform direction to avoid overlapping drawing.  As an
590	 * option we could continue to support drawing the outline.
591	 */
592	if (new_y != old_y) {
593	    if (inside) {
594		/*
595		 * Just draw the vertical line when there is not a matching
596		 * edge on the right side.
597		 */
598		if (context->temporary_write_controls.shading_character != '\0') {
599		    shade_char_to_pixel(context, pixels, w, h,
600					WRITE_SHADING_REF_X,
601					old_x, old_x, old_y);
602		} else {
603		    shade_pattern_to_pixel(context, WRITE_SHADING_REF_X,
604					   old_x, old_x, old_y);
605		}
606	    }
607	    inside = 1;
608	} else {
609	    if (inside) {
610		if (context->temporary_write_controls.shading_character != '\0') {
611		    shade_char_to_pixel(context, pixels, w, h,
612					WRITE_SHADING_REF_X,
613					old_x, new_x, new_y);
614		} else {
615		    shade_pattern_to_pixel(context, WRITE_SHADING_REF_X,
616					   old_x, new_x, new_y);
617		}
618	    }
619	    if (new_x > old_x + 1) {
620		inside = !inside;
621	    }
622	}
623
624	old_x = new_x;
625	old_y = new_y;
626    }
627
628    context->destination_graphic->dirty = 1;
629}
630
631static void
632draw_patterned_line(RegisGraphicsContext *context, int x1, int y1,
633		    int x2, int y2)
634{
635    int x, y;
636    int dx, dy;
637    int dir, diff;
638
639    dx = abs(x1 - x2);
640    dy = abs(y1 - y2);
641
642    if (dx > dy) {
643	if (x1 > x2) {
644	    int tmp;
645	    EXCHANGE(x1, x2, tmp);
646	    EXCHANGE(y1, y2, tmp);
647	}
648	if (y1 < y2)
649	    dir = 1;
650	else if (y1 > y2)
651	    dir = -1;
652	else
653	    dir = 0;
654
655	diff = 0;
656	y = y1;
657	for (x = x1; x <= x2; x++) {
658	    if (diff >= dx) {
659		diff -= dx;
660		y += dir;
661	    }
662	    diff += dy;
663	    draw_or_save_patterned_pixel(context, x, y);
664	}
665    } else {
666	if (y1 > y2) {
667	    int tmp;
668	    EXCHANGE(y1, y2, tmp);
669	    EXCHANGE(x1, x2, tmp);
670	}
671	if (x1 < x2)
672	    dir = 1;
673	else if (x1 > x2)
674	    dir = -1;
675	else
676	    dir = 0;
677
678	diff = 0;
679	x = x1;
680	for (y = y1; y <= y2; y++) {
681	    if (diff >= dy) {
682		diff -= dy;
683		x += dir;
684	    }
685	    diff += dx;
686	    draw_or_save_patterned_pixel(context, x, y);
687	}
688    }
689
690    context->destination_graphic->dirty = 1;
691}
692
693typedef struct {
694    int dxx;
695    int dxy;
696    int dyx;
697    int dyy;
698} quadmap_coords;
699
700static void
701draw_patterned_arc(RegisGraphicsContext *context,
702		   int cx, int cy,
703		   int ex, int ey,
704		   int a_start, int a_length,
705		   int *ex_final, int *ey_final)
706{
707    const double third = hypot((double) (cx - ex), (double) (cy - ey));
708    const int radius = (int) third;
709    const int ra = radius;
710    const int rb = radius;
711    const quadmap_coords neg_quadmap[4] =
712    {
713	{-1, 0, 0, +1},
714	{0, -1, -1, 0},
715	{+1, 0, 0, -1},
716	{0, +1, +1, 0},
717    };
718    const quadmap_coords pos_quadmap[4] =
719    {
720	{-1, 0, 0, -1},
721	{0, -1, +1, 0},
722	{+1, 0, 0, +1},
723	{0, +1, -1, 0},
724    };
725    const quadmap_coords *quadmap;
726    int total_points;
727    int half_degree;
728    int points_start, points_stop;
729    int points;
730    unsigned iterations;
731    long rx, ry;
732    long dx, dy;
733    int x, y;
734    long e2;
735    long error;
736
737    TRACE(("orig a_length=%d a_start=%d\n", a_length, a_start));
738    if (a_length == 0)
739	return;
740    if (a_length > 0) {
741	quadmap = pos_quadmap;
742    } else {
743	quadmap = neg_quadmap;
744	if (a_start != 0)
745	    a_start = 3600 - a_start;
746	a_length = abs(a_length);
747    }
748    TRACE(("positive a_length=%d a_start=%d\n", a_length, a_start));
749
750    rx = -ra;
751    ry = 0;
752    e2 = rb;
753    dx = (2 * rx + 1) * e2 * e2;
754    dy = rx * rx;
755    error = dx + dy;
756    total_points = 0;
757    do {
758	total_points += 4;
759	e2 = 2 * error;
760	if (e2 >= dx) {
761	    rx++;
762	    dx += 2 * rb * rb;
763	    error += dx;
764	}
765	if (e2 <= dy) {
766	    ry++;
767	    dy += 2 * ra * ra;
768	    error += dy;
769	}
770    }
771    while (rx <= 0);
772
773    /* FIXME: This is apparently not accurate enough because some arcs start or
774     * end a few pixels off.  Maybe compare line slopes in the loop below
775     * instead?
776     */
777    half_degree = total_points * 5;
778    points_start = (total_points * a_start - half_degree) / 3600;
779    points_stop = (total_points * a_start +
780		   total_points * a_length + half_degree) / 3600;
781    TRACE(("drawing arc with %d points clockwise from %g degrees for %g degrees (from point %d to %d out of %d)\n",
782	   total_points, a_start / 10.0, a_length / 10.0, points_start, points_stop,
783	   total_points));
784
785    /* FIXME: The four pixels at the cardinal directions are double-drawn. */
786    points = 0;
787    for (iterations = 0U; iterations < 8U; iterations++) {
788	int q2 = iterations & 0x3;
789
790	rx = -ra;
791	ry = 0;
792	e2 = rb;
793	dx = (2 * rx + 1) * e2 * e2;
794	dy = rx * rx;
795	error = dx + dy;
796	do {
797#ifdef DEBUG_ARC_POINTS
798	    double rad = atan2(
799				  (double) (quadmap[q2].dyx * rx +
800					    quadmap[q2].dyy * ry),
801				  (double) (quadmap[q2].dxx * rx +
802					    quadmap[q2].dxy * ry));
803	    double deg = (360.0 * rad / (2.0 * M_PI));
804	    if (deg < 0.0)
805		deg += 360.0;
806#endif
807
808	    if (points >= points_start && points <= points_stop) {
809		x = (int) (cx +
810			   quadmap[q2].dxx * rx +
811			   quadmap[q2].dxy * ry);
812		y = (int) (cy +
813			   quadmap[q2].dyx * rx +
814			   quadmap[q2].dyy * ry);
815#ifdef DEBUG_ARC_POINTS
816		TRACE(("drawing point %u at %d,%d (%.5g deg)\n",
817		       points, x, y, deg));
818#endif
819		draw_or_save_patterned_pixel(context, x, y);
820		if (ex_final)
821		    *ex_final = x;
822		if (ey_final)
823		    *ey_final = y;
824	    } else {
825#ifdef DEBUG_ARC_POINTS
826		x = (int) (cx + quadmap[q2].dxx * rx + quadmap[q2].dxy * ry);
827		y = (int) (cy + quadmap[q2].dyx * rx + quadmap[q2].dyy * ry);
828		TRACE(("skipping point %u at %d,%d which is outside of range (%.5g deg)\n",
829		       points, x, y, deg));
830#endif
831	    }
832	    points++;
833
834	    e2 = 2 * error;
835	    if (e2 >= dx) {
836		rx++;
837		dx += 2 * rb * rb;
838		error += dx;
839	    }
840	    if (e2 <= dy) {
841		ry++;
842		dy += 2 * ra * ra;
843		error += dy;
844	    }
845	}
846	while (rx <= 0);
847    }
848
849    context->destination_graphic->dirty = 1;
850}
851
852/*
853 * The plot* functions are based on optimized rasterization primitves written by Zingl Alois.
854 * See http://members.chello.at/easyfilter/bresenham.html
855 */
856
857/*
858 * FIXME:
859 * This is a terrible temporary hack.  The plot functions below can be adapted
860 * to work like the other rasterization functions but there's no point in doing
861 * that until we know we don't have to write something completely different.
862 */
863static RegisGraphicsContext *global_context;
864static void
865setPixel(int x, int y)
866{
867    draw_or_save_patterned_pixel(global_context, x, y);
868}
869
870static void
871plotLine(int x0, int y0, int x1, int y1)
872{
873    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
874    int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
875    int err = dx + dy;		/* error value e_xy */
876
877    for (;;) {			/* loop */
878	int e2;
879	setPixel(x0, y0);
880	e2 = 2 * err;
881	if (e2 >= dy) {		/* e_xy+e_x > 0 */
882	    if (x0 == x1)
883		break;
884	    err += dy;
885	    x0 += sx;
886	}
887	if (e2 <= dx) {		/* e_xy+e_y < 0 */
888	    if (y0 == y1)
889		break;
890	    err += dx;
891	    y0 += sy;
892	}
893    }
894}
895
896static void
897plotQuadBezierSeg(int x0, int y0, int x1, int y1, int x2, int y2)
898{				/* plot a limited quadratic Bezier segment */
899    int sx = x2 - x1;
900    int sy = y2 - y1;
901    long xx = (x0 - x1);	/* relative values for checks */
902    long yy = (y0 - y1);
903    double cur = (double) (xx * sy - yy * sx);	/* curvature */
904
905    assert(xx * sx <= 0 && yy * sy <= 0);	/* sign of gradient must not change */
906
907    if (sx * (long) sx + sy * (long) sy > xx * xx + yy * yy) {	/* begin with longer part */
908	x2 = x0;
909	x0 = sx + x1;
910	y2 = y0;
911	y0 = sy + y1;
912	cur = -cur;		/* swap P0 P2 */
913    }
914    if (cur != 0) {		/* no straight line */
915	long xy;
916	double dx, dy, err;
917
918	xx += sx;
919	xx *= sx = x0 < x2 ? 1 : -1;	/* x step direction */
920	yy += sy;
921	yy *= sy = y0 < y2 ? 1 : -1;	/* y step direction */
922	xy = 2 * xx * yy;
923	xx *= xx;
924	yy *= yy;		/* differences 2nd degree */
925	if (cur * sx * sy < 0) {	/* negated curvature? */
926	    xx = -xx;
927	    yy = -yy;
928	    xy = -xy;
929	    cur = -cur;
930	}
931	/* differences 1st degree */
932	dx = ((4.0 * sy * cur * (x1 - x0)) + (double) xx) - (double) xy;
933	dy = ((4.0 * sx * cur * (y0 - y1)) + (double) yy) - (double) xy;
934	xx += xx;
935	yy += yy;
936	err = dx + dy + (double) xy;	/* error 1st step */
937	do {
938	    setPixel(x0, y0);	/* plot curve */
939	    if (x0 == x2 && y0 == y2)
940		return;		/* last pixel -> curve finished */
941	    y1 = (2 * err) < dx;	/* save value for test of y step */
942	    if ((2 * err) > dy) {
943		x0 += sx;
944		dx -= (double) xy;
945		dy += (double) yy;
946		err += dy;
947	    }			/* x step */
948	    if (y1) {
949		y0 += sy;
950		dy -= (double) xy;
951		dx += (double) xx;
952		err += dx;
953	    }			/* y step */
954	} while (dy < 0 && dx > 0);	/* gradient negates -> algorithm fails */
955    }
956    plotLine(x0, y0, x2, y2);	/* plot remaining part to end */
957}
958
959#if 0
960static void
961plotQuadBezier(int x0, int y0, int x1, int y1, int x2, int y2)
962{				/* plot any quadratic Bezier curve */
963    int x = x0 - x1;
964    int y = y0 - y1;
965    double t = x0 - 2 * x1 + x2;
966    double r;
967
968    if ((long) x * (x2 - x1) > 0) {	/* horizontal cut at P4? */
969	if ((long) y * (y2 - y1) > 0)	/* vertical cut at P6 too? */
970	    if (fabs((y0 - 2 * y1 + y2) / t * x) > abs(y)) {	/* which first? */
971		x0 = x2;
972		x2 = x + x1;
973		y0 = y2;
974		y2 = y + y1;	/* swap points */
975	    }			/* now horizontal cut at P4 comes first */
976	t = (x0 - x1) / t;
977	r = (1 - t) * ((1 - t) * y0 + 2.0 * t * y1) + t * t * y2;	/* By(t=P4) */
978	t = (x0 * x2 - x1 * x1) * t / (x0 - x1);	/* gradient dP4/dx=0 */
979	x = ifloor(t + 0.5);
980	y = ifloor(r + 0.5);
981	r = (y1 - y0) * (t - x0) / (x1 - x0) + y0;	/* intersect P3 | P0 P1 */
982	plotQuadBezierSeg(x0, y0, x, ifloor(r + 0.5), x, y);
983	r = (y1 - y2) * (t - x2) / (x1 - x2) + y2;	/* intersect P4 | P1 P2 */
984	x0 = x1 = x;
985	y0 = y;
986	y1 = ifloor(r + 0.5);	/* P0 = P4, P1 = P8 */
987    }
988    if ((long) (y0 - y1) * (y2 - y1) > 0) {	/* vertical cut at P6? */
989	t = y0 - 2 * y1 + y2;
990	t = (y0 - y1) / t;
991	r = (1 - t) * ((1 - t) * x0 + 2.0 * t * x1) + t * t * x2;	/* Bx(t=P6) */
992	t = (y0 * y2 - y1 * y1) * t / (y0 - y1);	/* gradient dP6/dy=0 */
993	x = ifloor(r + 0.5);
994	y = ifloor(t + 0.5);
995	r = (x1 - x0) * (t - y0) / (y1 - y0) + x0;	/* intersect P6 | P0 P1 */
996	plotQuadBezierSeg(x0, y0, ifloor(r + 0.5), y, x, y);
997	r = (x1 - x2) * (t - y2) / (y1 - y2) + x2;	/* intersect P7 | P1 P2 */
998	x0 = x;
999	x1 = ifloor(r + 0.5);
1000	y0 = y1 = y;		/* P0 = P6, P1 = P7 */
1001    }
1002    plotQuadBezierSeg(x0, y0, x1, y1, x2, y2);	/* remaining part */
1003}
1004#endif
1005
1006static void
1007plotCubicBezierSeg(int x0, int y0,
1008		   double x1, double y1,
1009		   double x2, double y2,
1010		   int x3, int y3)
1011{				/* plot limited cubic Bezier segment */
1012    int f, fx, fy, tt;
1013    int leg = 1;
1014    int sx = x0 < x3 ? 1 : -1;
1015    int sy = y0 < y3 ? 1 : -1;	/* step direction */
1016    double xc = -fabs(x0 + x1 - x2 - x3);
1017    double xa = xc - 4 * sx * (x1 - x2);
1018    double xb = sx * (x0 - x1 - x2 + x3);
1019    double yc = -fabs(y0 + y1 - y2 - y3);
1020    double ya = yc - 4 * sy * (y1 - y2);
1021    double yb = sy * (y0 - y1 - y2 + y3);
1022    double ab, ac, bc, cb, xx, xy, yy, dx, dy, ex, *pxy;
1023    double EP = 0.01;
1024    /* check for curve restrains */
1025    /* slope P0-P1 == P2-P3    and  (P0-P3 == P1-P2      or   no slope change) */
1026    assert((x1 - x0) * (x2 - x3) < EP &&
1027	   ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP));
1028    assert((y1 - y0) * (y2 - y3) < EP &&
1029	   ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP));
1030
1031    if (xa == 0 && ya == 0) {	/* quadratic Bezier */
1032	sx = ifloor((3 * x1 - x0 + 1) / 2);
1033	sy = ifloor((3 * y1 - y0 + 1) / 2);	/* new midpoint */
1034	plotQuadBezierSeg(x0, y0, sx, sy, x3, y3);
1035	return;
1036    }
1037    x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1;	/* line lengths */
1038    x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1;
1039    do {			/* loop over both ends */
1040	ab = xa * yb - xb * ya;
1041	ac = xa * yc - xc * ya;
1042	bc = xb * yc - xc * yb;
1043	ex = ab * (ab + ac - 3 * bc) + ac * ac;		/* P0 part of self-intersection loop? */
1044	f = ((ex > 0.0)
1045	     ? 1
1046	     : isqrt(1 + 1024 / x1));	/* calculate resolution */
1047	ab *= f;
1048	ac *= f;
1049	bc *= f;
1050	ex *= f * f;		/* increase resolution */
1051	xy = 9 * (ab + ac + bc) / 8;
1052	cb = 8 * (xa - ya);	/* init differences of 1st degree */
1053	dx = 27 * (8 * ab * (yb * yb - ya * yc) +
1054		   ex * (ya + 2 * yb + yc)) / 64 - ya * ya * (xy - ya);
1055	dy = 27 * (8 * ab * (xb * xb - xa * xc) -
1056		   ex * (xa + 2 * xb + xc)) / 64 - xa * xa * (xy + xa);
1057	/* init differences of 2nd degree */
1058	xx = 3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) -
1059		  ya * (3 * ac * (ya + yb) + ya * cb)) / 4;
1060	yy = 3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) -
1061		  xa * (3 * ac * (xa + xb) + xa * cb)) / 4;
1062	xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + cb);
1063	ac = ya * ya;
1064	cb = xa * xa;
1065	xy = 3 * (xy + 9 * f * (cb * yb * yc - xb * xc * ac) -
1066		  18 * xb * yb * ab) / 8;
1067
1068	if (ex < 0) {		/* negate values if inside self-intersection loop */
1069	    dx = -dx;
1070	    dy = -dy;
1071	    xx = -xx;
1072	    yy = -yy;
1073	    xy = -xy;
1074	    ac = -ac;
1075	    cb = -cb;
1076	}			/* init differences of 3rd degree */
1077	ab = 6 * ya * ac;
1078	ac = -6 * xa * ac;
1079	bc = 6 * ya * cb;
1080	cb = -6 * xa * cb;
1081	dx += xy;
1082	ex = dx + dy;
1083	dy += xy;		/* error of 1st step */
1084
1085	for (pxy = &xy, fx = fy = f; x0 != x3 && y0 != y3;) {
1086	    setPixel(x0, y0);	/* plot curve */
1087	    do {		/* move sub-steps of one pixel */
1088		if (dx > *pxy || dy < *pxy)
1089		    goto exit;	/* confusing values */
1090		y1 = 2 * ex - dy;	/* save value for test of y step */
1091		if (2 * ex >= dx) {	/* x sub-step */
1092		    fx--;
1093		    ex += dx += xx;
1094		    dy += xy += ac;
1095		    yy += bc;
1096		    xx += ab;
1097		}
1098		if (y1 <= 0) {	/* y sub-step */
1099		    fy--;
1100		    ex += dy += yy;
1101		    dx += xy += bc;
1102		    xx += ac;
1103		    yy += cb;
1104		}
1105	    } while (fx > 0 && fy > 0);		/* pixel complete? */
1106	    if (2 * fx <= f) {
1107		x0 += sx;
1108		fx += f;
1109	    }			/* x step */
1110	    if (2 * fy <= f) {
1111		y0 += sy;
1112		fy += f;
1113	    }			/* y step */
1114	    if (pxy == &xy && dx < 0 && dy > 0)
1115		pxy = &EP;	/* pixel ahead valid */
1116	}
1117      exit:
1118	EXCHANGE(x0, x3, tt);
1119	sx = -sx;
1120	xb = -xb;		/* swap legs */
1121	EXCHANGE(y0, y3, tt);
1122	sy = -sy;
1123	yb = -yb;
1124	x1 = x2;
1125    } while (leg--);		/* try other end */
1126    plotLine(x0, y0, x3, y3);	/* remaining part in case of cusp or crunode */
1127}
1128
1129static void
1130plotCubicBezier(int x0, int y0, int x1, int y1,
1131		int x2, int y2, int x3, int y3)
1132{				/* plot any cubic Bezier curve */
1133    int n = 0, i = 0;
1134    long xc = x0 + x1 - x2 - x3;
1135    long xa = xc - 4 * (x1 - x2);
1136    long xb = x0 - x1 - x2 + x3;
1137    long xd = xb + 4 * (x1 + x2);
1138    long yc = y0 + y1 - y2 - y3;
1139    long ya = yc - 4 * (y1 - y2);
1140    long yb = y0 - y1 - y2 + y3;
1141    long yd = yb + 4 * (y1 + y2);
1142    double fx0 = x0;
1143    double fy0 = y0;
1144    double t1 = (double) (xb * xb - xa * xc), t2, t[5];
1145
1146#ifdef DEBUG_BEZIER
1147    printf("plotCubicBezier(%d,%d, %d,%d, %d,%d, %d,%d\n",
1148	   x0, y0, x1, y1, x2, y2, x3, y3);
1149#endif
1150    /* sub-divide curve at gradient sign changes */
1151    if (xa == 0) {		/* horizontal */
1152	if (labs(xc) < 2 * labs(xb))
1153	    t[n++] = (double) xc / (2.0 * (double) xb);		/* one change */
1154    } else if (t1 > 0.0) {	/* two changes */
1155	t2 = sqrt(t1);
1156	t1 = ((double) xb - t2) / (double) xa;
1157	if (fabs(t1) < 1.0)
1158	    t[n++] = t1;
1159	t1 = ((double) xb + t2) / (double) xa;
1160	if (fabs(t1) < 1.0)
1161	    t[n++] = t1;
1162    }
1163    t1 = (double) (yb * yb - ya * yc);
1164    if (ya == 0) {		/* vertical */
1165	if (labs(yc) < 2 * labs(yb))
1166	    t[n++] = (double) yc / (2.0 * (double) yb);		/* one change */
1167    } else if (t1 > 0.0) {	/* two changes */
1168	t2 = sqrt(t1);
1169	t1 = ((double) yb - t2) / (double) ya;
1170	if (fabs(t1) < 1.0)
1171	    t[n++] = t1;
1172	t1 = ((double) yb + t2) / (double) ya;
1173	if (fabs(t1) < 1.0)
1174	    t[n++] = t1;
1175    }
1176    for (i = 1; i < n; i++)	/* bubble sort of 4 points */
1177	if ((t1 = t[i - 1]) > t[i]) {
1178	    t[i - 1] = t[i];
1179	    t[i] = t1;
1180	    i = 0;
1181	}
1182
1183    t1 = -1.0;
1184    t[n] = 1.0;			/* begin / end point */
1185    for (i = 0; i <= n; i++) {	/* plot each segment separately */
1186	double fx1, fx2, fx3;
1187	double fy1, fy2, fy3;
1188
1189	t2 = t[i];		/* sub-divide at t[i-1], t[i] */
1190	fx1 = (t1 * (t1 * (double) xb - (double) (2 * xc)) -
1191	       t2 * (t1 * (t1 * (double) xa - (double) (2 * xb)) + (double)
1192		     xc) + (double) xd) / 8 - fx0;
1193	fy1 = (t1 * (t1 * (double) yb - (double) (2 * yc)) -
1194	       t2 * (t1 * (t1 * (double) ya - (double) (2 * yb)) + (double)
1195		     yc) + (double) yd) / 8 - fy0;
1196	fx2 = (t2 * (t2 * (double) xb - (double) (2 * xc)) -
1197	       t1 * (t2 * (t2 * (double) xa - (double) (2 * xb)) + (double)
1198		     xc) + (double) xd) / 8 - fx0;
1199	fy2 = (t2 * (t2 * (double) yb - (double) (2 * yc)) -
1200	       t1 * (t2 * (t2 * (double) ya - (double) (2 * yb)) + (double)
1201		     yc) + (double) yd) / 8 - fy0;
1202	fx0 -= fx3 = (t2 * (t2 * ((double) (3 * xb) - t2 * (double) xa) -
1203			    (double) (3 * xc)) + (double) xd) / 8;
1204	fy0 -= fy3 = (t2 * (t2 * ((double) (3 * yb) - t2 * (double) ya) -
1205			    (double) (3 * yc)) + (double) yd) / 8;
1206	x3 = ifloor(fx3 + 0.5);
1207	y3 = ifloor(fy3 + 0.5);	/* scale bounds to int */
1208	if (fx0 != 0.0) {
1209	    fx1 *= fx0 = (x0 - x3) / fx0;
1210	    fx2 *= fx0;
1211	}
1212	if (fy0 != 0.0) {
1213	    fy1 *= fy0 = (y0 - y3) / fy0;
1214	    fy2 *= fy0;
1215	}
1216	if (x0 != x3 || y0 != y3)	/* segment t1 - t2 */
1217	    plotCubicBezierSeg(x0, y0,
1218			       x0 + fx1, y0 + fy1,
1219			       x0 + fx2, y0 + fy2,
1220			       x3, y3);
1221	x0 = x3;
1222	y0 = y3;
1223	fx0 = fx3;
1224	fy0 = fy3;
1225	t1 = t2;
1226    }
1227}
1228
1229#if 0
1230static void
1231plotQuadSpline(int n, int x[], int y[], int skip_segments)
1232{				/* plot quadratic spline, destroys input arrays x,y */
1233#define M_MAX 12
1234    double mi = 1, m[M_MAX];	/* diagonal constants of matrix */
1235    int i, x0, y0, x1, y1, x2, y2;
1236#ifdef DEBUG_SPLINE_SEGMENTS
1237    int color = 0;
1238#endif
1239
1240    assert(n > 1);		/* need at least 3 points P[0]..P[n] */
1241
1242#ifdef DEBUG_SPLINE_POINTS
1243    {
1244	int save_pattern;
1245
1246	i = 0;
1247	global_context->temporary_write_controls.foreground = 11;
1248	save_pattern = global_context->temporary_write_controls.pattern;
1249	global_context->temporary_write_controls.pattern = 0xff;
1250	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
1251			   3600, NULL, NULL);
1252	i++;
1253	global_context->temporary_write_controls.foreground = 15;
1254	for (; i < n; i++) {
1255	    draw_patterned_arc(global_context,
1256			       x[i], y[i],
1257			       x[i] + 2, y[i],
1258			       0, 3600, NULL, NULL);
1259	}
1260	global_context->temporary_write_controls.foreground = 10;
1261	draw_patterned_arc(global_context, x[i], y[n], x[i] + 2, y[i], 0,
1262			   3600, NULL, NULL);
1263	global_context->temporary_write_controls.pattern = save_pattern;
1264    }
1265#endif
1266
1267    x2 = x[n];
1268    y2 = y[n];
1269
1270    x[1] = x0 = 8 * x[1] - 2 * x[0];	/* first row of matrix */
1271    y[1] = y0 = 8 * y[1] - 2 * y[0];
1272
1273    for (i = 2; i < n; i++) {	/* forward sweep */
1274	if (i - 2 < M_MAX)
1275	    m[i - 2] = mi = 1.0 / (6.0 - mi);
1276	x[i] = x0 = ifloor(8 * x[i] - x0 * mi + 0.5);	/* store yi */
1277	y[i] = y0 = ifloor(8 * y[i] - y0 * mi + 0.5);
1278    }
1279    x1 = ifloor((x0 - 2 * x2) / (5.0 - mi) + 0.5);	/* correction last row */
1280    y1 = ifloor((y0 - 2 * y2) / (5.0 - mi) + 0.5);
1281
1282    for (i = n - 2; i > 0; i--) {	/* back substitution */
1283	if (i <= M_MAX)
1284	    mi = m[i - 1];
1285	x0 = ifloor((x[i] - x1) * mi + 0.5);	/* next corner */
1286	y0 = ifloor((y[i] - y1) * mi + 0.5);
1287#ifdef DEBUG_SPLINE_SEGMENTS
1288	color++;
1289	global_context->temporary_write_controls.foreground = color;
1290#endif
1291	if ((n - 2) - i < skip_segments)
1292	    plotQuadBezier((x0 + x1) / 2, (y0 + y1) / 2, x1, y1, x2, y2);
1293	x2 = (x0 + x1) / 2;
1294	x1 = x0;
1295	y2 = (y0 + y1) / 2;
1296	y1 = y0;
1297    }
1298#ifdef DEBUG_SPLINE_SEGMENTS
1299    color++;
1300    global_context->temporary_write_controls.foreground = color;
1301#endif
1302    if (skip_segments > 0)
1303	plotQuadBezier(x[0], y[0], x1, y1, x2, y2);
1304}
1305#endif
1306
1307static void
1308plotCubicSpline(int n, int x[], int y[], int skip_first_last)
1309{
1310#define M_MAX 12
1311    double mi = 0.25, m[M_MAX];	/* diagonal constants of matrix */
1312    int x3, y3, x4, y4;
1313    int i, x0, y0, x1, y1, x2, y2;
1314#ifdef DEBUG_SPLINE_SEGMENTS
1315    RegisterNum color = 0;
1316#endif
1317
1318    assert(n > 2);		/* need at least 4 points P[0]..P[n] */
1319
1320#ifdef DEBUG_SPLINE_POINTS
1321    {
1322	unsigned save_pattern;
1323
1324	i = 0;
1325	global_context->temporary_write_controls.foreground = 11;
1326	save_pattern = global_context->temporary_write_controls.pattern;
1327	global_context->temporary_write_controls.pattern = 0xff;
1328	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
1329			   3600, NULL, NULL);
1330	i++;
1331	global_context->temporary_write_controls.foreground = 15;
1332	for (; i < n; i++) {
1333	    draw_patterned_arc(global_context,
1334			       x[i], y[i],
1335			       x[i] + 2, y[i],
1336			       0, 3600, NULL, NULL);
1337	}
1338	global_context->temporary_write_controls.foreground = 10;
1339	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
1340			   3600, NULL, NULL);
1341	global_context->temporary_write_controls.pattern = save_pattern;
1342    }
1343#endif
1344
1345    x3 = x[n - 1];
1346    y3 = y[n - 1];
1347    x4 = x[n];
1348    y4 = y[n];
1349
1350    x[1] = x0 = 12 * x[1] - 3 * x[0];	/* first row of matrix */
1351    y[1] = y0 = 12 * y[1] - 3 * y[0];
1352
1353    for (i = 2; i < n; i++) {	/* forward sweep */
1354	if (i - 2 < M_MAX)
1355	    m[i - 2] = mi = 0.25 / (2.0 - mi);
1356	x[i] = x0 = ifloor(12 * x[i] - 2 * x0 * mi + 0.5);
1357	y[i] = y0 = ifloor(12 * y[i] - 2 * y0 * mi + 0.5);
1358    }
1359    x2 = ifloor((x0 - 3 * x4) / (7 - 4 * mi) + 0.5);	/* correct last row */
1360    /* printf("y0=%d, y4=%d mi=%g\n", y0, y4, mi); */
1361    y2 = ifloor((y0 - 3 * y4) / (7 - 4 * mi) + 0.5);
1362    /* printf("y2=%d, y3=%d, y4=%d\n", y2, y3, y4); */
1363#ifdef DEBUG_SPLINE_SEGMENTS
1364    color++;
1365    global_context->temporary_write_controls.foreground = color;
1366#endif
1367    if (!skip_first_last)
1368	plotCubicBezier(x3, y3, (x2 + x4) / 2, (y2 + y4) / 2, x4, y4, x4, y4);
1369
1370    if (n - 3 < M_MAX)
1371	mi = m[n - 3];
1372    x1 = ifloor((x[n - 2] - 2 * x2) * mi + 0.5);
1373    y1 = ifloor((y[n - 2] - 2 * y2) * mi + 0.5);
1374    for (i = n - 3; i > 0; i--) {	/* back substitution */
1375	if (i <= M_MAX)
1376	    mi = m[i - 1];
1377	x0 = ifloor((x[i] - 2 * x1) * mi + 0.5);
1378	y0 = ifloor((y[i] - 2 * y1) * mi + 0.5);
1379	x4 = ifloor((x0 + 4 * x1 + x2 + 3) / 6.0);	/* reconstruct P[i] */
1380	y4 = ifloor((y0 + 4 * y1 + y2 + 3) / 6.0);
1381#ifdef DEBUG_SPLINE_SEGMENTS
1382	color++;
1383	global_context->temporary_write_controls.foreground = color;
1384#endif
1385#define CB_PARM(num) ifloor((num) / 3.0 + 0.5)
1386	plotCubicBezier(x4, y4,
1387			CB_PARM(2 * x1 + x2),
1388			CB_PARM(2 * y1 + y2),
1389			CB_PARM(x1 + 2 * x2),
1390			CB_PARM(y1 + 2 * y2),
1391			x3, y3);
1392	x3 = x4;
1393	y3 = y4;
1394	x2 = x1;
1395	y2 = y1;
1396	x1 = x0;
1397	y1 = y0;
1398    }
1399    x0 = x[0];
1400    x4 = ifloor((3 * x0 + 7 * x1 + 2 * x2 + 6) / 12.0);		/* reconstruct P[1] */
1401    y0 = y[0];
1402    y4 = ifloor((3 * y0 + 7 * y1 + 2 * y2 + 6) / 12.0);
1403#ifdef DEBUG_SPLINE_SEGMENTS
1404    global_context->temporary_write_controls.foreground = 4;
1405#endif
1406    plotCubicBezier(x4, y4,
1407		    CB_PARM(2 * x1 + x2),
1408		    CB_PARM(2 * y1 + y2),
1409		    CB_PARM(x1 + 2 * x2),
1410		    CB_PARM(y1 + 2 * y2),
1411		    x3, y3);
1412#ifdef DEBUG_SPLINE_SEGMENTS
1413    color++;
1414    global_context->temporary_write_controls.foreground = color;
1415#endif
1416    if (!skip_first_last)
1417	plotCubicBezier(x0, y0, x0, y0, (x0 + x1) / 2, (y0 + y1) / 2, x4, y4);
1418}
1419
1420static unsigned
1421find_free_alphabet_index(RegisGraphicsContext *context, unsigned alphabet,
1422			 unsigned pixw, unsigned pixh)
1423{
1424    unsigned ii, jj;
1425
1426    /* try an exact match */
1427    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1428	if (context->alphabets[ii].alphabet_num == alphabet &&
1429	    context->alphabets[ii].pixw == pixw &&
1430	    context->alphabets[ii].pixh == pixh) {
1431	    return ii;
1432	}
1433    }
1434
1435    /* otherwise use any empty slot */
1436    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1437	if (context->alphabets[ii].alphabet_num == INVALID_ALPHABET_NUM) {
1438	    context->alphabets[ii].alphabet_num = alphabet;
1439	    context->alphabets[ii].pixw = pixw;
1440	    context->alphabets[ii].pixh = pixh;
1441	    return ii;
1442	}
1443    }
1444
1445    /* otherwise recycle a slot with a different font size */
1446    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1447	if (context->alphabets[ii].alphabet_num == alphabet) {
1448	    context->alphabets[ii].pixw = pixw;
1449	    context->alphabets[ii].pixh = pixh;
1450	    context->alphabets[ii].name[0] = '\0';
1451	    context->alphabets[ii].fontname[0] = '\0';
1452	    context->alphabets[ii].use_font = 0;
1453	    if (context->alphabets[ii].bytes != NULL) {
1454		free(context->alphabets[ii].bytes);
1455		context->alphabets[ii].bytes = NULL;
1456	    }
1457	    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
1458		context->alphabets[ii].loaded[jj] = 0;
1459	    }
1460	    return ii;
1461	}
1462    }
1463
1464    /* finally just recycle this arbitrary slot */
1465    context->alphabets[0U].alphabet_num = alphabet;
1466    context->alphabets[0U].pixw = pixw;
1467    context->alphabets[0U].pixh = pixh;
1468    context->alphabets[0U].name[0] = '\0';
1469    context->alphabets[0U].fontname[0] = '\0';
1470    context->alphabets[0U].use_font = 0;
1471    if (context->alphabets[0U].bytes != NULL) {
1472	free(context->alphabets[0U].bytes);
1473	context->alphabets[0U].bytes = NULL;
1474    }
1475    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
1476	context->alphabets[0U].loaded[jj] = 0;
1477    }
1478
1479    return 0U;
1480}
1481
1482#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1483static void
1484dump_bitmap_pixels(unsigned char const *pixels, unsigned w, unsigned h)
1485{
1486    unsigned yy, xx;
1487
1488    for (yy = 0U; yy < h; yy++) {
1489	printf(" ");
1490	for (xx = 0U; xx < w; xx++) {
1491	    if (pixels[yy * w + xx]) {
1492		printf("#");
1493	    } else {
1494		printf("_");
1495	    }
1496	}
1497	printf("\n");
1498    }
1499}
1500#endif
1501
1502#if OPT_RENDERFONT && defined(HAVE_TYPE_FCCHAR32)
1503static int
1504copy_bitmap_from_xft_font(Display *display, XftFont *font, FcChar32 ch,
1505			  unsigned char *pixels, unsigned w, unsigned h,
1506			  unsigned xmin, unsigned ymin)
1507{
1508    /*
1509     * FIXME: cache:
1510     * - the bitmap for the last M characters and target dimensions
1511     * - resuse the pixmap object where possible
1512     */
1513    XftColor bg, fg;
1514    Pixmap bitmap;
1515    XftDraw *draw;
1516    XImage *image;
1517    unsigned bmw, bmh;
1518    unsigned xx, yy;
1519
1520    bg.pixel = 0UL;
1521    bg.color.red = 0;
1522    bg.color.green = 0;
1523    bg.color.blue = 0;
1524    bg.color.alpha = 0x0;
1525
1526    fg.pixel = 1UL;
1527    fg.color.red = 0xffff;
1528    fg.color.green = 0xffff;
1529    fg.color.blue = 0xffff;
1530    fg.color.alpha = 0xffff;
1531
1532    bmw = w + xmin;
1533    bmh = h;
1534    bitmap = XCreatePixmap(display,
1535			   DefaultRootWindow(display),
1536			   bmw, bmh,
1537			   1);
1538    if (bitmap == None) {
1539	TRACE(("Unable to create Pixmap\n"));
1540	return 0;
1541    }
1542    draw = XftDrawCreateBitmap(display, bitmap);
1543    if (!draw) {
1544	TRACE(("Unable to create XftDraw\n"));
1545	XFreePixmap(display, bitmap);
1546	return 0;
1547    }
1548
1549    XftDrawRect(draw, &bg, 0, 0, bmw, bmh);
1550    XftDrawString32(draw, &fg, font, 0, font->ascent - (int) ymin,
1551		    &ch, 1);
1552
1553    image = XGetImage(display, bitmap, (int) xmin, 0, w, h, 1, XYPixmap);
1554    if (!image) {
1555	TRACE(("Unable to create XImage\n"));
1556	XftDrawDestroy(draw);
1557	XFreePixmap(display, bitmap);
1558	return 0;
1559    }
1560
1561    for (yy = 0U; yy < h; yy++) {
1562	for (xx = 0U; xx < w; xx++) {
1563	    pixels[yy * w + xx] = (unsigned char) XGetPixel(image,
1564							    (int) xx,
1565							    (int) yy);
1566	}
1567    }
1568
1569    XDestroyImage(image);
1570    XftDrawDestroy(draw);
1571    XFreePixmap(display, bitmap);
1572    return 1;
1573}
1574
1575static void
1576get_xft_glyph_dimensions(Display *display, XftFont *font, unsigned *w,
1577			 unsigned *h, unsigned *xmin, unsigned *ymin)
1578{
1579    unsigned workw, workh;
1580    FcChar32 ch;
1581    unsigned char *pixels;
1582    unsigned yy, xx;
1583    unsigned char_count, pixel_count;
1584    unsigned real_minx, real_maxx, real_miny, real_maxy;
1585    unsigned char_minx, char_maxx, char_miny, char_maxy;
1586
1587    /*
1588     * For each ASCII or ISO-8859-1 printable code, find out what its
1589     * dimensions are.
1590     *
1591     * We actually render the glyphs and determine the extents ourselves
1592     * because the font library can lie by several pixels, and since we are
1593     * doing manual character placement in fixed areas the glyph boundary needs
1594     * to be accurate.
1595     *
1596     * Ignore control characters and spaces - their extent information is
1597     * misleading.
1598     */
1599
1600    /* Our "work area" is just a buffer which should be big enough to hold the
1601     * largest glyph even if its size is under-reported by a couple of pixels
1602     * in each dimension.
1603     */
1604    workw = (unsigned) font->max_advance_width + 2U;
1605    if (font->ascent + font->descent > font->height) {
1606	workh = (unsigned) (font->ascent + font->descent) + 2U;
1607    } else {
1608	workh = (unsigned) font->height + 2U;
1609    }
1610
1611    if (!(pixels = malloc(workw * workh))) {
1612	*w = 0U;
1613	*h = 0U;
1614	return;
1615    }
1616
1617    /* FIXME: ch is in UCS32 -- try to support non-ASCII characters */
1618    char_count = 0U;
1619    real_minx = workw - 1U;
1620    real_maxx = 0U;
1621    real_miny = workh - 1U;
1622    real_maxy = 0U;
1623    for (ch = 33; ch < 256; ++ch) {
1624	if (ch >= 127 && ch <= 160)
1625	    continue;
1626	if (!FcCharSetHasChar(font->charset, ch))
1627	    continue;
1628
1629	copy_bitmap_from_xft_font(display, font, ch, pixels,
1630				  workw, workh, 0U, 0U);
1631
1632	pixel_count = 0U;
1633	char_minx = workh - 1U;
1634	char_maxx = 0U;
1635	char_miny = workh - 1U;
1636	char_maxy = 0U;
1637	for (yy = 0U; yy < workh; yy++) {
1638	    for (xx = 0U; xx < workw; xx++) {
1639		if (pixels[yy * workw + xx]) {
1640		    if (xx < char_minx)
1641			char_minx = xx;
1642		    if (xx > char_maxx)
1643			char_maxx = xx;
1644		    if (yy < char_miny)
1645			char_miny = yy;
1646		    if (yy > char_maxy)
1647			char_maxy = yy;
1648		    pixel_count++;
1649		}
1650	    }
1651	}
1652	if (pixel_count < 1U)
1653	    continue;
1654
1655#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1656	if (IS_DEBUG_CHAR(ch)) {
1657	    printf("char: '%c' (%d)\n", (char) ch, ch);
1658	    printf(" minx: %u\n", char_minx);
1659	    printf(" maxx: %u\n", char_maxx);
1660	    printf(" miny: %u\n", char_miny);
1661	    printf(" maxy: %u\n", char_maxy);
1662	    dump_bitmap_pixels(pixels, workw, workh);
1663	    printf("\n");
1664	}
1665#endif
1666
1667	if (char_minx < real_minx)
1668	    real_minx = char_minx;
1669	if (char_maxx > real_maxx)
1670	    real_maxx = char_maxx;
1671	if (char_miny < real_miny)
1672	    real_miny = char_miny;
1673	if (char_maxy > real_maxy)
1674	    real_maxy = char_maxy;
1675	char_count++;
1676    }
1677
1678    free(pixels);
1679
1680    if (char_count < 1U) {
1681	*w = 0U;
1682	*h = 0U;
1683	return;
1684    }
1685
1686    *w = (unsigned) (1 + real_maxx - real_minx);
1687    *h = (unsigned) (1 + real_maxy - real_miny);
1688    *xmin = real_minx;
1689    *ymin = real_miny;
1690
1691#ifdef DEBUG_COMPUTED_FONT_METRICS
1692    printf("reported metrics:\n");
1693    printf(" %ux%u ascent=%u descent=%u\n", font->max_advance_width,
1694	   font->height, font->ascent, font->descent);
1695    printf("computed metrics:\n");
1696    printf(" real_minx=%u real_maxx=%u real_miny=%u real_maxy=%u\n",
1697	   real_minx, real_maxx, real_miny, real_maxy);
1698    printf(" final: %ux%u xmin=%u ymin=%u\n", *w, *h, *xmin, *ymin);
1699#endif
1700}
1701
1702#define FONT_SIZE_CACHE_SIZE 32U
1703
1704/* Find the font pixel size which returns the font which is closest to the given
1705 * maxw and maxh without overstepping either dimension.
1706 */
1707static XftFont *
1708find_best_xft_font_size(Display *display, Screen *screen, char const *fontname,
1709			unsigned maxw, unsigned maxh, unsigned max_pixels,
1710			unsigned *w, unsigned *h,
1711			unsigned *xmin, unsigned *ymin)
1712{
1713    XftFont *font;
1714    unsigned targeth;
1715    unsigned ii, cacheindex;
1716    static struct {
1717	char fontname[REGIS_FONTNAME_LEN];
1718	unsigned maxw, maxh, max_pixels;
1719	unsigned targeth;
1720	unsigned w, h;
1721	unsigned xmin;
1722	unsigned ymin;
1723    } cache[FONT_SIZE_CACHE_SIZE];
1724
1725    assert(display);
1726    assert(screen);
1727    assert(fontname);
1728    assert(w);
1729    assert(h);
1730    assert(xmin);
1731    assert(ymin);
1732
1733    cacheindex = FONT_SIZE_CACHE_SIZE;
1734    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
1735	if (cache[ii].maxw == maxw && cache[ii].maxh == maxh &&
1736	    cache[ii].max_pixels == max_pixels &&
1737	    strcmp(cache[ii].fontname, fontname) == 0) {
1738	    cacheindex = ii;
1739	    break;
1740	}
1741    }
1742
1743    if (cacheindex < FONT_SIZE_CACHE_SIZE) {
1744	targeth = cache[cacheindex].targeth;
1745    } else {
1746	targeth = maxh * 10U + 5U;
1747    }
1748    for (;;) {
1749	if (targeth <= 5U) {
1750	    TRACE(("Giving up finding suitable Xft font size for %ux%u.\n",
1751		   maxw, maxh));
1752	    return NULL;
1753	}
1754
1755	/*
1756	 * Xft does a bad job at:
1757	 *  - two-color low-resolution anti-aliased fonts
1758	 *  - non-anti-aliased fonts at low resolution unless a font size is
1759	 *    given (pixel size does not help, and the value of the font size
1760	 *    doesn't appear to matter).
1761	 *
1762	 * In those two cases it literally drops pixels, sometimes whole
1763	 * columns, making the glyphs unreadable and ugly even when readable.
1764	 */
1765	/*
1766	 * FIXME:
1767	 * Also, we need to scale the width and height separately.  The
1768	 * CHAR_WIDTH and CHAR_HEIGHT attributes would seem to be ideal, but
1769	 * don't appear to have any effect if set.  Instead we will manually
1770	 * scale the bitmap later, which may be very ugly because we won't try
1771	 * to identify different parts of glyphs or preserve density.
1772	 */
1773	{
1774	    XftPattern *pat;
1775	    XftPattern *match;
1776	    XftResult status;
1777
1778	    font = NULL;
1779	    if ((pat = XftNameParse(fontname))) {
1780		XftPatternBuild(pat,
1781		/* arbitrary value */
1782				XFT_SIZE, XftTypeDouble, 12.0,
1783				XFT_PIXEL_SIZE, XftTypeDouble, (double)
1784				targeth / 10.0,
1785#if 0
1786				XFT_CHAR_WIDTH, XftTypeInteger, (int) maxw,
1787				XFT_CHAR_HEIGHT, XftTypeInteger, (int)
1788				(targeth / 10U),
1789#endif
1790				XFT_SPACING, XftTypeInteger, XFT_MONO,
1791				XFT_SLANT, XftTypeInteger, 0,
1792				XFT_ANTIALIAS, XftTypeBool, False,
1793				NULL);
1794		if ((match = XftFontMatch(display,
1795					  XScreenNumberOfScreen(screen),
1796					  pat, &status))) {
1797		    font = XftFontOpenPattern(display, match);
1798		}
1799	    }
1800	}
1801	if (!font) {
1802	    TRACE(("Unable to open a monospaced Xft font.\n"));
1803	    return NULL;
1804	}
1805#ifdef DEBUG_FONT_SIZE_SEARCH
1806	{
1807	    char buffer[1024];
1808
1809	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
1810		printf("Testing font named \"%s\"\n", buffer);
1811	    } else {
1812		printf("Testing unknown font\n");
1813	    }
1814	}
1815#endif
1816
1817	if (cacheindex < FONT_SIZE_CACHE_SIZE &&
1818	    targeth == cache[cacheindex].targeth) {
1819	    *w = cache[cacheindex].w;
1820	    *h = cache[cacheindex].h;
1821	    *xmin = cache[cacheindex].xmin;
1822	    *ymin = cache[cacheindex].ymin;
1823	} else {
1824	    get_xft_glyph_dimensions(display, font, w, h, xmin, ymin);
1825	}
1826#ifdef DEBUG_FONT_SIZE_SEARCH
1827	printf("checking max=%ux%u targeth=%u.%u\n", maxw, maxh, targeth /
1828	       10U, targeth % 10U);
1829#endif
1830
1831	if (*h > maxh) {
1832	    XftFontClose(display, font);
1833#ifdef DEBUG_FONT_SIZE_SEARCH
1834	    printf("got %ux%u glyph; too tall; reducing target size\n", *w, *h);
1835#endif
1836	    if (*h > 2U * maxh) {
1837		targeth /= (*h / maxh);
1838	    } else if (targeth > 10U && *h > maxh + 1U) {
1839		targeth -= 10U;
1840	    } else {
1841		targeth--;
1842	    }
1843	    continue;
1844	}
1845	if (*w > maxw) {
1846	    XftFontClose(display, font);
1847#ifdef DEBUG_FONT_SIZE_SEARCH
1848	    printf("got %ux%u glyph; too wide; reducing target size\n", *w, *h);
1849#endif
1850	    if (*w > 2U * maxw) {
1851		targeth /= (*w / maxw);
1852	    } else if (targeth > 10U && *w > maxw + 1U) {
1853		targeth -= 10U;
1854	    } else {
1855		targeth--;
1856	    }
1857	    continue;
1858	}
1859	if (*w * *h > max_pixels) {
1860	    XftFontClose(display, font);
1861#ifdef DEBUG_FONT_SIZE_SEARCH
1862	    printf("got %ux%u glyph; too many pixels; reducing target size\n",
1863		   *w, *h);
1864#endif
1865	    if (*w * *h > 2U * max_pixels) {
1866		unsigned min = *w < *h ? *w : *h;
1867		unsigned divisor = (*w * *h) / (max_pixels * min);
1868		if (divisor > 1U) {
1869		    targeth /= divisor;
1870		} else if (targeth > 10U) {
1871		    targeth -= 10U;
1872		} else {
1873		    targeth--;
1874		}
1875	    } else {
1876		targeth--;
1877	    }
1878	    continue;
1879	}
1880#ifdef DEBUG_FONT_NAME
1881	{
1882	    char buffer[1024];
1883
1884	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
1885		printf("Final font for \"%s\" max %dx%d is \"%s\"\n",
1886		       fontname, maxw, maxh, buffer);
1887	    } else {
1888		printf("Final font for \"%s\" max %dx%d is unknown\n",
1889		       fontname, maxw, maxh);
1890	    }
1891	}
1892#endif
1893
1894	if (cacheindex == FONT_SIZE_CACHE_SIZE) {
1895	    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
1896		if (cache[ii].maxw == 0U || cache[ii].maxh == 0U ||
1897		    cache[ii].max_pixels == 0U) {
1898		    CopyFontname(cache[ii].fontname, fontname);
1899		    cache[ii].maxw = maxw;
1900		    cache[ii].maxh = maxh;
1901		    cache[ii].max_pixels = max_pixels;
1902		    cache[ii].targeth = targeth;
1903		    cache[ii].w = *w;
1904		    cache[ii].h = *h;
1905		    cache[ii].xmin = *xmin;
1906		    cache[ii].ymin = *ymin;
1907		    break;
1908		}
1909	    }
1910	    if (ii == FONT_SIZE_CACHE_SIZE) {
1911		ii = targeth % FONT_SIZE_CACHE_SIZE;
1912		CopyFontname(cache[ii].fontname, fontname);
1913		cache[ii].maxw = maxw;
1914		cache[ii].maxh = maxh;
1915		cache[ii].max_pixels = max_pixels;
1916		cache[ii].targeth = targeth;
1917		cache[ii].w = *w;
1918		cache[ii].h = *h;
1919		cache[ii].xmin = *xmin;
1920		cache[ii].ymin = *ymin;
1921	    }
1922	}
1923	return font;
1924    }
1925}
1926#endif
1927
1928static int
1929get_xft_bitmap_of_character(RegisGraphicsContext const *context,
1930			    char const *fontname, int ch,
1931			    unsigned maxw, unsigned maxh, unsigned char *pixels,
1932			    unsigned max_pixels, unsigned *w, unsigned *h)
1933{
1934    /*
1935     * See Xft / RENDERFONT stuff in fontutils.c and used in utils.c
1936     * Add a separate configuration for ReGIS.
1937     */
1938    /*
1939     * FIXME: cache:
1940     * - resuse the font where possible
1941     */
1942#ifdef XRENDERFONT
1943    Display *display = XtDisplay(context->destination_graphic->xw);
1944    Screen *screen = XtScreen(context->destination_graphic->xw);
1945    XftFont *font;
1946    unsigned xmin = 0U, ymin = 0U;
1947
1948    if (!(font = find_best_xft_font_size(display, screen, fontname, maxw, maxh,
1949					 max_pixels, w, h, &xmin, &ymin))) {
1950	TRACE(("Unable to find suitable Xft font\n"));
1951	return 0;
1952    }
1953
1954    if (!copy_bitmap_from_xft_font(display, font, CharOf(ch), pixels, *w, *h,
1955				   xmin, ymin)) {
1956	TRACE(("Unable to create bitmap for '%c'\n", ch));
1957	XftFontClose(display, font);
1958	return 0;
1959    }
1960    XftFontClose(display, font);
1961    return 1;
1962#else
1963    (void) context;
1964    (void) context;
1965    (void) ch;
1966    (void) maxw;
1967    (void) maxh;
1968    (void) pixels;
1969    (void) max_pixels;
1970    (void) w;
1971    (void) h;
1972
1973    return 0;
1974#endif
1975}
1976
1977static unsigned
1978find_best_alphabet_index(RegisGraphicsContext const *context,
1979			 unsigned minw, unsigned minh,
1980			 unsigned maxw, unsigned maxh,
1981			 unsigned max_pixels)
1982{
1983    unsigned ii;
1984    unsigned bestmatch;
1985    unsigned bestw, besth;
1986
1987    assert(context);
1988    assert(maxw);
1989    assert(maxh);
1990
1991    bestmatch = MAX_REGIS_ALPHABETS;
1992    bestw = 0U;
1993    besth = 0U;
1994    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1995	if (context->alphabets[ii].alphabet_num ==
1996	    context->current_text_controls->alphabet_num &&
1997	    context->alphabets[ii].pixw >= minw &&
1998	    context->alphabets[ii].pixh >= minh &&
1999	    context->alphabets[ii].pixw <= maxw &&
2000	    context->alphabets[ii].pixh <= maxh &&
2001	    context->alphabets[ii].pixw > bestw &&
2002	    context->alphabets[ii].pixh > besth &&
2003	    context->alphabets[ii].pixw *
2004	    context->alphabets[ii].pixh <= max_pixels) {
2005	    bestmatch = ii;
2006	    bestw = context->alphabets[ii].pixw;
2007	    besth = context->alphabets[ii].pixh;
2008	}
2009    }
2010
2011#ifdef DEBUG_ALPHABET_LOOKUP
2012    if (bestmatch < MAX_REGIS_ALPHABETS) {
2013	TRACE(("found alphabet %u at index %u size %ux%u font=%s\n",
2014	       context->current_text_controls->alphabet_num, bestmatch,
2015	       bestw, besth,
2016	       context->alphabets[bestmatch].use_font ?
2017	       context->alphabets[bestmatch].fontname : "(none)"));
2018    }
2019#endif
2020
2021    return bestmatch;
2022}
2023
2024#define GLYPH_WIDTH_BYTES(PIXW) ( ((PIXW) + 7U) >> 3U )
2025
2026static int
2027get_user_bitmap_of_character(RegisGraphicsContext const *context,
2028			     int ch,
2029			     unsigned alphabet_index,
2030			     unsigned char *pixels)
2031{
2032    const unsigned char *glyph;
2033    unsigned w, h;
2034    unsigned xx, yy;
2035    unsigned byte, bit;
2036
2037    assert(context);
2038    assert(pixels);
2039
2040    if (!context->alphabets[alphabet_index].loaded[(unsigned char) ch]) {
2041	TRACE(("in alphabet %u with alphabet index %u user glyph for '%c' not loaded\n",
2042	       context->current_text_controls->alphabet_num, alphabet_index,
2043	       ch));
2044	return 0;
2045    }
2046
2047    assert(context->alphabets[alphabet_index].bytes);
2048
2049    w = context->alphabets[alphabet_index].pixw;
2050    h = context->alphabets[alphabet_index].pixh;
2051    glyph = &context->alphabets[alphabet_index]
2052	.bytes[(unsigned char) ch * GLYPH_WIDTH_BYTES(w) * h];
2053
2054    for (yy = 0U; yy < h; yy++) {
2055	for (xx = 0U; xx < w; xx++) {
2056	    byte = yy * GLYPH_WIDTH_BYTES(w) + (xx >> 3U);
2057	    bit = xx & 7U;
2058	    pixels[yy * w + xx] = ((unsigned) glyph[byte] >> (7U - bit)) & 1U;
2059	}
2060    }
2061
2062    return 1;
2063}
2064
2065/*
2066 * alphabets
2067 *  0    built-in
2068 *  1-N  custom (max is 3 on VT3X0 -- up to MAX_REGIS_ALPHABETS with xterm)
2069 *
2070 * built-in 7-bit charsets
2071 *  (B    ASCII
2072 *  (0    DEC special graphics
2073 *  (>    DEC technical
2074 *  (A    NCR British
2075 *  (4    NCR Dutch
2076 *  (5    NCR Finnish
2077 *  (R    NCR French
2078 *  (9    NCR French Canadian
2079 *  (K    NCR German
2080 *  (Y    NCR Italian
2081 *  ('    NCR Norwegian/Danish
2082 *  (!6   NCR Portuguese
2083 *  (Z    NCR Spanish
2084 *  (7    NCR Swedish
2085 *  (-    NCR Swiss
2086 *
2087 * -@   ???
2088 *
2089 * built-in 8-bit charsets
2090 *  )%5   DEC supplemental graphics
2091 *  -A    ISO Latin-1 supplemental
2092 *  )<    user-preferred supplemental (94 chars)
2093 *
2094 * defaults
2095 *  terminal  char cell size   charsets      angle
2096 *  VT3x0     S1               0:ASCII(94)   0 (positive)
2097 *
2098 */
2099static void
2100get_bitmap_of_character(RegisGraphicsContext const *context, int ch,
2101			unsigned maxw, unsigned maxh, unsigned char *pixels,
2102			unsigned *w, unsigned *h, unsigned max_pixels)
2103{
2104    unsigned bestmatch;
2105    char const *fontname = NULL;
2106
2107    if (context->current_text_controls->alphabet_num == 0) {
2108	fontname = context->builtin_font;
2109    }
2110
2111    *w = 0U;
2112    *h = 0U;
2113
2114    bestmatch = find_best_alphabet_index(context, 1U, 1U, maxw, maxh,
2115					 max_pixels);
2116    if (bestmatch < MAX_REGIS_ALPHABETS) {
2117	RegisAlphabet const *alpha = &context->alphabets[bestmatch];
2118
2119	if (!alpha->use_font &&
2120	    get_user_bitmap_of_character(context, ch, bestmatch, pixels)) {
2121#ifdef DEBUG_USER_GLYPH
2122	    TRACE(("found user glyph for alphabet number %d (index %u)\n\n",
2123		   context->current_text_controls->alphabet_num, bestmatch));
2124#endif
2125	    *w = alpha->pixw;
2126	    *h = alpha->pixh;
2127	    return;
2128	}
2129
2130	if (alpha->use_font)
2131	    fontname = alpha->fontname;
2132    }
2133
2134    if (fontname) {
2135	if (get_xft_bitmap_of_character(context, fontname, ch,
2136					maxw, maxh, pixels,
2137					max_pixels, w, h)) {
2138	    if (*w > maxw) {
2139		TRACE(("BUG: Xft glyph is too wide: %ux%u but max is %ux%u\n",
2140		       *w, *h, maxw, maxh));
2141	    } else if (*h > maxh) {
2142		TRACE(("BUG: Xft glyph is too tall: %ux%u but max is %ux%u\n",
2143		       *w, *h, maxw, maxh));
2144	    } else if (*w * *h > max_pixels) {
2145		TRACE(("BUG: Xft glyph has too many pixels: %u but max is %u\n",
2146		       *w * *h, max_pixels));
2147	    } else {
2148		TRACE(("got glyph from \"%s\" for alphabet number %d\n",
2149		       fontname, context->current_text_controls->alphabet_num));
2150#ifdef DEBUG_SPECIFIC_CHAR_METRICS
2151		if (IS_DEBUG_CHAR(ch)) {
2152		    printf("got %ux%u Xft bitmap for '%c' target size %ux%u:\n",
2153			   *w, *h,
2154			   ch, maxw, maxh);
2155		    dump_bitmap_pixels(pixels, *w, *h);
2156		    printf("\n");
2157		}
2158#endif
2159		return;
2160	    }
2161	}
2162    }
2163
2164    TRACE(("unable to load any bitmap for character '%c' in alphabet number %u at %ux%u\n",
2165	   ch, context->current_text_controls->alphabet_num, maxw, maxh));
2166
2167    /* FIXME: this should probably produce an "unknown character" symbol */
2168    {
2169	unsigned xx, yy;
2170
2171	*w = MIN2(8U, maxh);
2172	*h = MIN2(10U, maxw);
2173	for (yy = 0U; yy < *h; yy++)
2174	    for (xx = 0U; xx < *w; xx++)
2175		pixels[yy * *w + xx] = '\0';
2176    }
2177}
2178
2179#define ROT_SHEAR_SCALE 8192
2180#define SIGNED_UNSIGNED_MOD(VAL, BASE) ( (((VAL) % (int) (BASE)) + (int) (BASE)) % (int) (BASE) )
2181
2182static unsigned
2183get_shade_character_pixel(unsigned char const *pixels, unsigned w, unsigned h,
2184			  unsigned smaxf, unsigned scale, int slant_dx,
2185			  int px, int py)
2186{
2187    unsigned wx, wy;
2188    unsigned fx, fy;
2189
2190    wx = (unsigned) SIGNED_UNSIGNED_MOD(px -
2191					(slant_dx * SIGNED_UNSIGNED_MOD(py, smaxf))
2192					/ ROT_SHEAR_SCALE, smaxf);
2193    wy = (unsigned) SIGNED_UNSIGNED_MOD(py, smaxf);
2194
2195    fx = (wx * scale) >> SCALE_FIXED_POINT;
2196    fy = (wy * scale) >> SCALE_FIXED_POINT;
2197    if (fx < w && fy < h) {
2198	return (unsigned) pixels[fy * w + fx];
2199    }
2200    return 0U;
2201}
2202
2203static void
2204draw_character(RegisGraphicsContext *context, int ch,
2205	       int slant_dx, int rot_shear_x,
2206	       int rot_shear_y, int x_sign_x, int x_sign_y,
2207	       int y_sign_x, int y_sign_y)
2208{
2209    const unsigned xmaxd = context->current_text_controls->character_display_w;
2210    const unsigned ymaxd = context->current_text_controls->character_display_h;
2211    const unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
2212    const unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
2213    unsigned w, h;
2214    unsigned xscale, yscale;
2215    unsigned fx, fy;
2216    unsigned px, py;
2217    int sx;
2218    int rx, ry;
2219    int ox, oy;
2220    unsigned pad_left, pad_right;
2221    unsigned pad_top, pad_bottom;
2222    unsigned char pixels[MAX_GLYPH_PIXELS];
2223    unsigned value;
2224
2225    get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
2226			    MAX_GLYPH_PIXELS);
2227    if (w < 1 || h < 1) {
2228	return;
2229    }
2230
2231    if (xmaxd > xmaxf) {
2232	pad_left = (xmaxd - xmaxf) / 2U;
2233	pad_right = (xmaxd - xmaxf) - pad_left;
2234    } else {
2235	pad_left = 0U;
2236	pad_right = 0U;
2237    }
2238    if (ymaxd > ymaxf) {
2239	pad_top = (ymaxd - ymaxf) / 2U;
2240	pad_bottom = (ymaxd - ymaxf) - pad_top;
2241    } else {
2242	pad_top = 0U;
2243	pad_bottom = 0U;
2244    }
2245
2246    xscale = (w << SCALE_FIXED_POINT) / xmaxf;
2247    yscale = (h << SCALE_FIXED_POINT) / ymaxf;
2248
2249    for (py = 0U; py < ymaxd; py++) {
2250	for (px = 0U; px < xmaxd; px++) {
2251	    if (py < pad_top || px < pad_left ||
2252		py >= ymaxd - pad_bottom || px >= xmaxd - pad_right) {
2253		value = 0U;
2254	    } else {
2255		fx = ((px - pad_left) * xscale) >> SCALE_FIXED_POINT;
2256		fy = ((py - pad_top) * yscale) >> SCALE_FIXED_POINT;
2257		if (fx < w && fy < h) {
2258		    value = (unsigned) pixels[fy * w + fx];
2259		} else {
2260		    value = 0U;
2261		}
2262	    }
2263
2264	    sx = (int) px + (slant_dx * (int) py) / ROT_SHEAR_SCALE;
2265	    rx = x_sign_x * sx + x_sign_y * (int) py;
2266	    ry = y_sign_x * sx + y_sign_y * (int) py;
2267	    ox = rx + (rot_shear_x * ry) / ROT_SHEAR_SCALE;
2268	    oy = ry + (rot_shear_y * ox) / ROT_SHEAR_SCALE;
2269	    ox += (rot_shear_x * oy) / ROT_SHEAR_SCALE;
2270
2271	    draw_regis_pixel(context,
2272			     (int) context->graphics_output_cursor_x + ox,
2273			     (int) context->graphics_output_cursor_y + oy,
2274			     value);
2275	}
2276    }
2277}
2278
2279static void
2280draw_text(RegisGraphicsContext *context, char const *str)
2281{
2282    double total_rotation;
2283    size_t ii;
2284    int str_invert;
2285    int str_shear_x, str_shear_y;
2286    int slant_dx;
2287    int chr_x_sign_x, chr_x_sign_y;
2288    int chr_y_sign_x, chr_y_sign_y;
2289    int chr_shear_x, chr_shear_y;
2290    int begin_x, begin_y;
2291    int rx, ry;
2292    int ox, oy;
2293
2294#ifdef DEBUG_ALPHABETS
2295    {
2296	unsigned n;
2297
2298	for (n = 0U; n < MAX_REGIS_ALPHABETS; n++) {
2299	    printf("alphabet index %u\n", n);
2300	    if (context->alphabets[n].alphabet_num != INVALID_ALPHABET_NUM) {
2301		printf(" alphabet_num=%u\n", context->alphabets[n].alphabet_num);
2302		printf(" pixw=%d\n", context->alphabets[n].pixw);
2303		printf(" pixh=%d\n", context->alphabets[n].pixh);
2304		printf(" name=\"%s\"\n", context->alphabets[n].name);
2305		printf(" use_font=%d\n", context->alphabets[n].use_font);
2306		printf(" fontname=\"%s\"\n", context->alphabets[n].fontname);
2307		printf(" bytes=%p\n", context->alphabets[n].bytes);
2308	    }
2309	}
2310    }
2311#endif
2312
2313    if (context->current_text_controls->slant <= -75 ||
2314	context->current_text_controls->slant >= +75) {
2315	TRACE(("ERROR: unsupported character slant angle %d\n",
2316	       context->current_text_controls->slant));
2317	return;
2318    }
2319
2320    /* FIXME: grab when first entering command */
2321    begin_x = context->graphics_output_cursor_x;
2322    begin_y = context->graphics_output_cursor_y;
2323
2324    total_rotation = 2.0 * M_PI *
2325	context->current_text_controls->string_rotation / 360.0;
2326    while (total_rotation > 1.5 * M_PI) {
2327	total_rotation -= 2.0 * M_PI;
2328    }
2329    if (total_rotation > 0.5 * M_PI) {
2330	total_rotation -= M_PI;
2331	str_invert = -1;
2332    } else {
2333	str_invert = 1;
2334    }
2335    str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
2336    str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
2337
2338    total_rotation = 2.0 * M_PI *
2339	context->current_text_controls->character_rotation / 360.0;
2340    while (total_rotation > 1.5 * M_PI) {
2341	total_rotation -= 2.0 * M_PI;
2342    }
2343    if (total_rotation > 0.5 * M_PI) {
2344	total_rotation -= M_PI;
2345	chr_x_sign_x = -1;
2346	chr_x_sign_y = 0;
2347	chr_y_sign_x = 0;
2348	chr_y_sign_y = -1;
2349    } else {
2350	chr_x_sign_x = 1;
2351	chr_x_sign_y = 0;
2352	chr_y_sign_x = 0;
2353	chr_y_sign_y = 1;
2354    }
2355    chr_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
2356    chr_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
2357    /*
2358     * FIXME: it isn't clear from the docs how slant affects the x positioning.
2359     * For now the code assumes the upper left is fixed.
2360     */
2361    TRACE(("float version: %.5f\n",
2362	   tan(2.0 * M_PI * abs(context->current_text_controls->slant) /
2363	       360.0)));
2364    if (context->current_text_controls->slant < 0) {
2365	slant_dx = (int) +(
2366			      tan(2.0 * M_PI * abs(context->current_text_controls->slant)
2367				  / 360.0) * ROT_SHEAR_SCALE);
2368    } else if (context->current_text_controls->slant > 0) {
2369	slant_dx = (int) -(
2370			      tan(2.0 * M_PI * abs(context->current_text_controls->slant)
2371				  / 360.0) * ROT_SHEAR_SCALE);
2372    } else {
2373	slant_dx = 0;
2374    }
2375    TRACE(("string rotation: %d\n",
2376	   context->current_text_controls->string_rotation));
2377    TRACE(("character rotation: %d\n",
2378	   context->current_text_controls->character_rotation));
2379    TRACE(("character slant: %d (%.5f pixels per line)\n",
2380	   context->current_text_controls->slant,
2381	   slant_dx / (double) ROT_SHEAR_SCALE));
2382    TRACE(("str_shear: %.5f, %.5f (sign=%d)\n",
2383	   str_shear_x / (double) ROT_SHEAR_SCALE,
2384	   str_shear_y / (double) ROT_SHEAR_SCALE,
2385	   str_invert));
2386    TRACE(("chr_shear: %.5f, %.5f (xsign=%d,%d, ysign=%d,%d)\n",
2387	   chr_shear_x / (double) ROT_SHEAR_SCALE,
2388	   chr_shear_y / (double) ROT_SHEAR_SCALE,
2389	   chr_x_sign_x, chr_x_sign_y,
2390	   chr_y_sign_x, chr_y_sign_y));
2391
2392    rx = 0;
2393    ry = 0;
2394    for (ii = 0U; ii < strlen(str); ii++) {
2395	switch (str[ii]) {
2396	case '\r':
2397	    rx = 0;
2398	    break;
2399	case '\n':
2400	    /* FIXME: verify */
2401	    ry += (int) context->current_text_controls->character_display_h;
2402	    break;
2403	case '\b':
2404	    rx -= context->current_text_controls->character_inc_x;
2405	    ry -= context->current_text_controls->character_inc_y;
2406	    break;
2407	case '\t':
2408	    rx += context->current_text_controls->character_inc_x;
2409	    ry += context->current_text_controls->character_inc_y;
2410	    break;
2411	default:
2412	    ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
2413	    oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
2414	    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
2415	    context->graphics_output_cursor_x = begin_x + ox;
2416	    context->graphics_output_cursor_y = begin_y + oy;
2417	    draw_character(context, str[ii], slant_dx,
2418			   chr_shear_x, chr_shear_y,
2419			   chr_x_sign_x, chr_x_sign_y,
2420			   chr_y_sign_x, chr_y_sign_y);
2421	    rx += context->current_text_controls->character_inc_x;
2422	    ry += context->current_text_controls->character_inc_y;
2423	}
2424    }
2425
2426    ox = rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
2427    oy = ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
2428    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
2429    context->graphics_output_cursor_x = begin_x + ox;
2430    context->graphics_output_cursor_y = begin_y + oy;
2431
2432    context->destination_graphic->dirty = 1;
2433    return;
2434}
2435
2436/*
2437 * standard character cell sizes
2438 *   number  disp cell   unit cell       offset
2439 *   S0      [  9, 10]   [  8, disp_h]   [disp_w, 0]
2440 *   S1      [  9, 20]   [  8, disp_h]   [disp_w, 0]
2441 *   S2      [ 18, 30]   [ 16, disp_h]   [disp_w, 0]
2442 *   S3      [ 27, 45]   [ 24, disp_h]   [disp_w, 0]
2443 *   S4      [ 36, 60]   [ 32, disp_h]   [disp_w, 0]
2444 *   S5      [ 45, 75]   [ 40, disp_h]   [disp_w, 0]
2445 *   S6      [ 54, 90]   [ 48, disp_h]   [disp_w, 0]
2446 *   S7      [ 63,105]   [ 56, disp_h]   [disp_w, 0]
2447 *   S8      [ 72,120]   [ 64, disp_h]   [disp_w, 0]
2448 *   S9      [ 81,135]   [ 72, disp_h]   [disp_w, 0]
2449 *   S10     [ 90,150]   [ 80, disp_h]   [disp_w, 0]
2450 *   S11     [ 99,165]   [ 88, disp_h]   [disp_w, 0]
2451 *   S12     [108,180]   [ 96, disp_h]   [disp_w, 0]
2452 *   S13     [117,195]   [104, disp_h]   [disp_w, 0]
2453 *   S14     [126,210]   [112, disp_h]   [disp_w, 0]
2454 *   S15     [135,225]   [120, disp_h]   [disp_w, 0]
2455 *   S16     [144,240]   [128, disp_h]   [disp_w, 0]
2456 */
2457static int
2458get_standard_character_size(int standard, unsigned *disp_w, unsigned
2459			    *disp_h, unsigned *unit_w, unsigned *unit_h,
2460			    int *off_x, int *off_y)
2461{
2462    switch (standard) {
2463    case 0:
2464	*disp_w = 9U;
2465	*disp_h = 10U;
2466	*unit_w = 8U;
2467	break;
2468    case 1:
2469	*disp_w = 9U;
2470	*disp_h = 20U;
2471	*unit_w = 8U;
2472	break;
2473    case 2:
2474	*disp_w = 18U;
2475	*disp_h = 30U;
2476	*unit_w = 16U;
2477	break;
2478    case 3:
2479	*disp_w = 27U;
2480	*disp_h = 45U;
2481	*unit_w = 24U;
2482	break;
2483    case 4:
2484	*disp_w = 36U;
2485	*disp_h = 60U;
2486	*unit_w = 32U;
2487	break;
2488    case 5:
2489	*disp_w = 45U;
2490	*disp_h = 75U;
2491	*unit_w = 40U;
2492	break;
2493    case 6:
2494	*disp_w = 54U;
2495	*disp_h = 90U;
2496	*unit_w = 48U;
2497	break;
2498    case 7:
2499	*disp_w = 63U;
2500	*disp_h = 105U;
2501	*unit_w = 56U;
2502	break;
2503    case 8:
2504	*disp_w = 72U;
2505	*disp_h = 120U;
2506	*unit_w = 64U;
2507	break;
2508    case 9:
2509	*disp_w = 81U;
2510	*disp_h = 135U;
2511	*unit_w = 72U;
2512	break;
2513    case 10:
2514	*disp_w = 90U;
2515	*disp_h = 150U;
2516	*unit_w = 80U;
2517	break;
2518    case 11:
2519	*disp_w = 99U;
2520	*disp_h = 165U;
2521	*unit_w = 88U;
2522	break;
2523    case 12:
2524	*disp_w = 108U;
2525	*disp_h = 180U;
2526	*unit_w = 96U;
2527	break;
2528    case 13:
2529	*disp_w = 117U;
2530	*disp_h = 195U;
2531	*unit_w = 104U;
2532	break;
2533    case 14:
2534	*disp_w = 126U;
2535	*disp_h = 210U;
2536	*unit_w = 112U;
2537	break;
2538    case 15:
2539	*disp_w = 135U;
2540	*disp_h = 225U;
2541	*unit_w = 120U;
2542	break;
2543    case 16:
2544	*disp_w = 144U;
2545	*disp_h = 240U;
2546	*unit_w = 128U;
2547	break;
2548    default:
2549	return 1;
2550    }
2551    *unit_h = *disp_h;
2552    *off_x = (int) *disp_w;
2553    *off_y = 0;
2554
2555    return 0;
2556}
2557
2558static void
2559init_fragment(RegisDataFragment *fragment, char const *str)
2560{
2561    assert(fragment);
2562    assert(str);
2563
2564    fragment->start = str;
2565    fragment->len = (unsigned) strlen(str);
2566    fragment->pos = 0U;
2567}
2568
2569static void
2570copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src)
2571{
2572    assert(dst);
2573    assert(src);
2574
2575    dst->start = src->start;
2576    dst->len = src->len;
2577    dst->pos = src->pos;
2578}
2579
2580static char
2581peek_fragment(RegisDataFragment const *fragment)
2582{
2583    assert(fragment);
2584
2585    if (fragment->pos < fragment->len) {
2586	return fragment->start[fragment->pos];
2587    }
2588    return '\0';
2589}
2590
2591static char
2592pop_fragment(RegisDataFragment *fragment)
2593{
2594    assert(fragment);
2595
2596    if (fragment->pos < fragment->len) {
2597	return fragment->start[fragment->pos++];
2598    }
2599    return '\0';
2600}
2601
2602static char
2603get_fragment(RegisDataFragment const *fragment, unsigned pos)
2604{
2605    assert(fragment);
2606
2607    if (fragment->pos + pos < fragment->len) {
2608	return fragment->start[fragment->pos + pos];
2609    }
2610    return '\0';
2611}
2612
2613static size_t
2614fragment_len(RegisDataFragment const *fragment)
2615{
2616    assert(fragment);
2617
2618    return fragment->len - fragment->pos;
2619}
2620
2621static void
2622fragment_to_string(RegisDataFragment const *fragment, char *out,
2623		   unsigned outlen)
2624{
2625    unsigned remaininglen;
2626    unsigned endpos;
2627
2628    assert(fragment);
2629    assert(out);
2630
2631    if (!outlen)
2632	return;
2633    remaininglen = fragment->len - fragment->pos;
2634    if (remaininglen < outlen - 1U) {
2635	endpos = remaininglen;
2636    } else {
2637	endpos = outlen - 1U;
2638    }
2639    strncpy(out, &fragment->start[fragment->pos], endpos);
2640    out[endpos] = '\0';
2641}
2642
2643#define MAX_FRAG 1024
2644static char const *
2645fragment_to_tempstr(RegisDataFragment const *fragment)
2646{
2647    static char tempstr[MAX_FRAG];
2648
2649    assert(fragment);
2650
2651    fragment_to_string(fragment, tempstr, MAX_FRAG);
2652    return tempstr;
2653}
2654
2655static int
2656skip_regis_whitespace(RegisDataFragment *input)
2657{
2658    int skipped = 0;
2659
2660    assert(input);
2661
2662    for (; input->pos < input->len; input->pos++) {
2663	char ch = input->start[input->pos];
2664	if (ch != ',' && !IsSpace(ch)) {
2665	    break;
2666	}
2667	if (ch == '\n') {
2668	    TRACE(("end of input line\n\n"));
2669	}
2670	skipped = 1;
2671    }
2672
2673    if (skipped)
2674	return 1;
2675    return 0;
2676}
2677
2678static int
2679extract_regis_extent(RegisDataFragment *input, RegisDataFragment *output)
2680{
2681    char ch;
2682
2683    assert(input);
2684    assert(output);
2685
2686    output->start = &input->start[input->pos];
2687    output->len = 0U;
2688    output->pos = 0U;
2689
2690    if (input->pos >= input->len)
2691	return 0;
2692
2693    ch = input->start[input->pos];
2694    if (ch != '[')
2695	return 0;
2696    input->pos++;
2697    output->start++;
2698
2699    /* FIXME: truncate to 16 bit signed integers */
2700    for (; input->pos < input->len; input->pos++, output->len++) {
2701	ch = input->start[input->pos];
2702	if (ch == ';') {
2703	    TRACE(("DATA_ERROR: end of input before closing bracket\n"));
2704	    break;
2705	}
2706	if (ch == ']')
2707	    break;
2708    }
2709    if (ch == ']')
2710	input->pos++;
2711
2712    return 1;
2713}
2714
2715static int
2716extract_regis_num(RegisDataFragment *input, RegisDataFragment *output)
2717{
2718    char ch = 0;
2719    int has_digits = 0;
2720
2721    assert(input);
2722    assert(output);
2723
2724    output->start = &input->start[input->pos];
2725    output->len = 0U;
2726    output->pos = 0U;
2727
2728    if (input->start[input->pos] == '-' ||
2729	input->start[input->pos] == '+') {
2730	input->pos++;
2731	output->len++;
2732    }
2733
2734    for (; input->pos < input->len; input->pos++, output->len++) {
2735	ch = input->start[input->pos];
2736	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
2737	    ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
2738	    ch != '8' && ch != '9') {
2739	    break;
2740	}
2741	has_digits = 1;
2742    }
2743
2744    /* FIXME: what degenerate forms should be accepted ("E10" "1E" "1e" "1." "1ee10")? */
2745    /* FIXME: the terminal is said to support "floating point values", truncating to int... what do these look like? */
2746    if (has_digits && ch == 'E') {
2747	input->pos++;
2748	output->len++;
2749	for (; input->pos < input->len; input->pos++, output->len++) {
2750	    ch = input->start[input->pos];
2751	    if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
2752		ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
2753		ch != '8' && ch != '9') {
2754		break;
2755	    }
2756	}
2757    }
2758
2759    return has_digits;
2760}
2761
2762static int
2763extract_regis_pixelvector(RegisDataFragment *input, RegisDataFragment *output)
2764{
2765    char ch;
2766    int has_digits;
2767
2768    assert(input);
2769    assert(output);
2770
2771    output->start = &input->start[input->pos];
2772    output->len = 0U;
2773    output->pos = 0U;
2774
2775    if (input->pos < input->len) {
2776	ch = input->start[input->pos];
2777	if (ch == '+' || ch == '-') {
2778	    input->pos++;
2779	    output->len++;
2780	}
2781    }
2782
2783    has_digits = 0;
2784    for (; input->pos < input->len; input->pos++, output->len++) {
2785	ch = input->start[input->pos];
2786	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
2787	    ch != '4' && ch != '5' && ch != '6' && ch != '7') {
2788	    break;
2789	}
2790	has_digits = 1;
2791    }
2792
2793    return has_digits;
2794}
2795
2796static int
2797extract_regis_command(RegisDataFragment *input, char *command)
2798{
2799    char ch;
2800
2801    assert(input);
2802    assert(command);
2803
2804    if (input->pos >= input->len)
2805	return 0;
2806
2807    ch = input->start[input->pos];
2808    if (ch == '\0' || ch == ';') {
2809	return 0;
2810    }
2811    if (!islower(CharOf(ch)) && !isupper(CharOf(ch)) && ch != '@') {
2812	return 0;
2813    }
2814    *command = ch;
2815    input->pos++;
2816
2817    return 1;
2818}
2819
2820static int
2821extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen)
2822{
2823    char first_ch;
2824    char ch;
2825    unsigned outlen = 0U;
2826
2827    assert(input);
2828    assert(out);
2829
2830    if (input->pos >= input->len)
2831	return 0;
2832
2833    ch = input->start[input->pos];
2834    if (ch != '\'' && ch != '"')
2835	return 0;
2836    first_ch = ch;
2837    input->pos++;
2838
2839    ch = '\0';
2840    for (; input->pos < input->len; input->pos++) {
2841	char prev_ch = ch;
2842	ch = input->start[input->pos];
2843	/* ';' (resync) is not recognized in strings */
2844	if (prev_ch == first_ch) {
2845	    if (ch == first_ch) {
2846		if (outlen < maxlen) {
2847		    out[outlen] = ch;
2848		}
2849		outlen++;
2850		ch = '\0';
2851		continue;
2852	    }
2853	    if (outlen < maxlen)
2854		out[outlen] = '\0';
2855	    else
2856		out[maxlen] = '\0';
2857	    return 1;
2858	}
2859	if (ch == '\0')
2860	    break;
2861	if (ch != first_ch) {
2862	    if (outlen < maxlen) {
2863		out[outlen] = ch;
2864	    }
2865	    outlen++;
2866	}
2867    }
2868    if (ch == first_ch) {
2869	if (outlen < maxlen)
2870	    out[outlen] = '\0';
2871	else
2872	    out[maxlen] = '\0';
2873	return 1;
2874    }
2875    /* FIXME: handle multiple strings concatenated with commas */
2876
2877    TRACE(("DATA_ERROR: end of input before closing quote\n"));
2878    return 0;
2879}
2880
2881static int
2882extract_regis_parenthesized_data(RegisDataFragment *input,
2883				 RegisDataFragment *output)
2884{
2885    char ch;
2886    char first_ch;
2887    int nesting;
2888
2889    assert(input);
2890    assert(output);
2891
2892    output->start = &input->start[input->pos];
2893    output->len = 0U;
2894    output->pos = 0U;
2895
2896    if (input->pos >= input->len)
2897	return 0;
2898
2899    ch = input->start[input->pos];
2900    if (ch != '(')
2901	return 0;
2902    input->pos++;
2903    output->start++;
2904    nesting = 1;
2905    first_ch = '\0';
2906
2907    ch = '\0';
2908    for (; input->pos < input->len; input->pos++, output->len++) {
2909	char prev_ch = ch;
2910	ch = input->start[input->pos];
2911	if (ch == '\'' || ch == '"') {
2912	    if (first_ch == '\0') {
2913		first_ch = ch;
2914	    } else {
2915		if (ch == prev_ch && prev_ch == first_ch) {
2916		    ch = '\0';
2917		} else if (ch == first_ch) {
2918		    first_ch = '\0';
2919		}
2920	    }
2921	    continue;
2922	}
2923	if (first_ch != '\0')
2924	    continue;
2925
2926	if (ch == ';') {
2927	    TRACE(("leaving parenthesized data nested %d levels deep due to command termination character\n",
2928		   nesting));
2929	    break;
2930	}
2931	if (ch == '(')
2932	    nesting++;
2933	if (ch == ')') {
2934	    nesting--;
2935	    if (nesting == 0) {
2936		input->pos++;
2937		return 1;
2938	    }
2939	}
2940    }
2941
2942    TRACE(("DATA_ERROR: end of input before closing paren (%d levels deep)\n",
2943	   nesting));
2944    return 0;
2945}
2946
2947static int
2948extract_regis_option(RegisDataFragment *input,
2949		     char *option,
2950		     RegisDataFragment *output)
2951{
2952    char ch;
2953    int paren_level, bracket_level;
2954    char first_ch;
2955
2956    assert(input);
2957    assert(option);
2958    assert(output);
2959
2960    /* LETTER suboptions* value? */
2961    /*
2962     * FIXME: what are the rules for using separate parens vs. sharing between
2963     * options?
2964     */
2965
2966    output->start = &input->start[input->pos];
2967    output->len = 0U;
2968    output->pos = 0U;
2969
2970    if (input->pos >= input->len) {
2971	return 0;
2972    }
2973
2974    ch = input->start[input->pos];
2975    /* FIXME: are options always letters or are some special characters ok? */
2976    if (ch == ';' || ch == ',' ||
2977	ch == '(' || ch == ')' ||
2978	ch == '[' || ch == ']' ||
2979	ch == '"' || ch == '\'' ||
2980	isdigit(CharOf(ch))) {
2981	return 0;
2982    }
2983    *option = ch;
2984    input->pos++;
2985    output->start++;
2986    paren_level = 0;
2987    bracket_level = 0;
2988
2989    first_ch = '\0';
2990    for (; input->pos < input->len; input->pos++, output->len++) {
2991	ch = input->start[input->pos];
2992	TRACE(("looking at char '%c' in option '%c'\n", ch, *option));
2993	/* FIXME: any special rules for commas? */
2994	/* FIXME: handle escaped quotes */
2995	if (ch == '\'' || ch == '"') {
2996	    if (first_ch == ch) {
2997		first_ch = '\0';
2998	    } else {
2999		first_ch = ch;
3000	    }
3001	    continue;
3002	}
3003	if (first_ch != '\0')
3004	    continue;
3005	if (ch == '(') {
3006	    paren_level++;
3007	}
3008	if (ch == ')') {
3009	    paren_level--;
3010	    if (paren_level < 0) {
3011		TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n",
3012		       *option));
3013		return 0;
3014	    }
3015	}
3016	if (ch == '[') {
3017	    bracket_level++;
3018	}
3019	if (ch == ']') {
3020	    bracket_level--;
3021	    if (bracket_level < 0) {
3022		TRACE(("DATA_ERROR: found ReGIS option has value with too many close brackets \"%c\"\n",
3023		       *option));
3024		return 0;
3025	    }
3026	}
3027	if (paren_level == 0 && bracket_level == 0) {
3028	    /*
3029	     * Top-level commas indicate the end of this option and the start of
3030	     * another.
3031	     */
3032	    if (ch == ',')
3033		break;
3034	    /*
3035	     * Top-level command/option/suboption names also indicate the end of
3036	     * this option.  "E" is valid as the exponent indicator in a numeric
3037	     * parameter.
3038	     */
3039	    if (ch != 'E' && ch != 'e' &&
3040		((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')))
3041		break;
3042	}
3043	if (ch == ';')
3044	    break;
3045    }
3046    if (paren_level != 0) {
3047	TRACE(("DATA_ERROR: mismatched parens in argument to ReGIS option \"%c\"\n",
3048	       *option));
3049	return 0;
3050    }
3051    if (bracket_level != 0) {
3052	TRACE(("DATA_ERROR: mismatched brackets in argument to ReGIS option \"%c\"\n",
3053	       *option));
3054	return 0;
3055    }
3056
3057    TRACE(("found ReGIS option and value \"%c\" \"%s\"\n",
3058	   *option,
3059	   fragment_to_tempstr(output)));
3060    return 1;
3061}
3062
3063static int
3064regis_num_to_int(RegisDataFragment const *input, int *out)
3065{
3066    char ch;
3067
3068    /* FIXME: handle exponential notation and rounding */
3069    /* FIXME: check for junk after the number */
3070    ch = peek_fragment(input);
3071    if (ch != '0' &&
3072	ch != '1' &&
3073	ch != '2' &&
3074	ch != '3' &&
3075	ch != '4' &&
3076	ch != '5' &&
3077	ch != '6' &&
3078	ch != '7' &&
3079	ch != '8' &&
3080	ch != '9' &&
3081	ch != '+' &&
3082	ch != '-') {
3083	return 0;
3084    }
3085
3086    TRACE(("converting \"%s\" to an int\n", fragment_to_tempstr(input)));
3087    *out = atoi(fragment_to_tempstr(input));
3088    return 1;
3089}
3090
3091static int
3092load_regis_colorspec(RegisGraphicsContext const *context,
3093		     RegisDataFragment const *input,
3094		     short *r_out, short *g_out, short *b_out)
3095{
3096    RegisDataFragment colorspec;
3097    short r = -1, g = -1, b = -1;
3098    short l = -1;
3099    int simple;
3100
3101    copy_fragment(&colorspec, input);
3102    TRACE(("colorspec option: \"%s\"\n", fragment_to_tempstr(&colorspec)));
3103
3104    skip_regis_whitespace(&colorspec);
3105    simple = 0;
3106    if (fragment_len(&colorspec) == 1) {
3107	simple = 1;
3108    } else if (fragment_len(&colorspec) > 1) {
3109	char after = get_fragment(&colorspec, 1);
3110	if (IsSpace(after))
3111	    simple = 1;
3112    }
3113    if (simple) {
3114	char ch = pop_fragment(&colorspec);
3115
3116	TRACE(("got ReGIS RGB colorspec pattern '%c' with arguments: \"%s\"\n",
3117	       ch, fragment_to_tempstr(&colorspec)));
3118	switch (ch) {
3119	case 'D':
3120	case 'd':
3121	    r = 0;
3122	    g = 0;
3123	    b = 0;
3124	    l = 0;
3125	    break;
3126	case 'R':
3127	case 'r':
3128	    r = 100;
3129	    g = 0;
3130	    b = 0;
3131	    l = 46;
3132	    break;
3133	case 'G':
3134	case 'g':
3135	    r = 0;
3136	    g = 100;
3137	    b = 0;
3138	    l = 50;
3139	    break;
3140	case 'B':
3141	case 'b':
3142	    r = 0;
3143	    g = 0;
3144	    b = 100;
3145	    l = 50;
3146	    break;
3147	case 'C':
3148	case 'c':
3149	    r = 0;
3150	    g = 100;
3151	    b = 100;
3152	    l = 50;
3153	    break;
3154	case 'Y':
3155	case 'y':
3156	    r = 100;
3157	    g = 100;
3158	    b = 0;
3159	    l = 50;
3160	    break;
3161	case 'M':
3162	case 'm':
3163	    r = 100;
3164	    g = 0;
3165	    b = 100;
3166	    l = 50;
3167	    break;
3168	case 'W':
3169	case 'w':
3170	    r = 100;
3171	    g = 100;
3172	    b = 100;
3173	    l = 100;
3174	    break;
3175	default:
3176	    TRACE(("unknown RGB color name: \"%c\"\n", ch));
3177	    return 0;
3178	}
3179    } else {
3180	RegisDataFragment num;
3181	int max, val;
3182	char comp;
3183	short h = -1;
3184	short s = -1;
3185
3186	while (colorspec.pos < colorspec.len) {
3187	    if (skip_regis_whitespace(&colorspec))
3188		continue;
3189
3190	    comp = pop_fragment(&colorspec);
3191	    switch (comp) {
3192	    case ',':
3193		/* not sure if this is valid, but it is easy to handle */
3194		continue;
3195	    case 'H':
3196	    case 'h':
3197		max = 360;
3198		comp = 'H';
3199		break;
3200	    case 'L':
3201	    case 'l':
3202		max = 100;
3203		comp = 'L';
3204		break;
3205	    case 'S':
3206	    case 's':
3207		max = 100;
3208		comp = 'S';
3209		break;
3210#ifdef ENABLE_RGB_COLORSPECS
3211	    case 'R':		/* RLogin extension */
3212	    case 'r':
3213		max = 100;
3214		comp = 'R';
3215		break;
3216	    case 'G':		/* RLogin extension */
3217	    case 'g':
3218		max = 100;
3219		comp = 'G';
3220		break;
3221	    case 'B':		/* RLogin extension */
3222	    case 'b':
3223		max = 100;
3224		comp = 'B';
3225		break;
3226#endif
3227	    default:
3228		TRACE(("unrecognized character in colorspec: \"%c\"\n", comp));
3229		return 0;
3230	    }
3231
3232	    skip_regis_whitespace(&colorspec);
3233	    if (!extract_regis_num(&colorspec, &num)) {
3234		TRACE(("unrecognized character in colorspec: \"%c\"\n", comp));
3235		return 0;
3236	    }
3237	    if (!regis_num_to_int(&num, &val)) {
3238		TRACE(("DATA_ERROR: component value %s is not a number\n",
3239		       fragment_to_tempstr(&num)));
3240		return 0;
3241	    }
3242	    /* FIXME: error, truncate, wrap, ...? */
3243	    if (val < 0 || val > max) {
3244		TRACE(("DATA_ERROR: component value %d out of range\n", val));
3245		return 0;
3246	    }
3247
3248	    switch (comp) {
3249	    case 'H':
3250		h = (short) val;
3251		break;
3252	    case 'L':
3253		l = (short) val;
3254		break;
3255	    case 'S':
3256		s = (short) val;
3257		break;
3258	    case 'R':
3259		r = (short) val;
3260		break;
3261	    case 'G':
3262		g = (short) val;
3263		break;
3264	    case 'B':
3265		b = (short) val;
3266		break;
3267	    }
3268	}
3269
3270	if (h >= 0 && l >= 0 && s >= 0 && r < 0 && g < 0 && b < 0) {
3271	    TRACE(("found HLS colorspec to be converted: %hd,%hd,%hd\n",
3272		   h, l, s));
3273	    hls2rgb(h, l, s, &r, &g, &b);
3274	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
3275	} else if (h < 0 && l < 0 && s < 0 && r >= 0 && g >= 0 && b >= 0) {
3276	    TRACE(("found RGB colorspec: %hd,%hd,%hd\n", r, g, b));
3277	    l = (short) ((MIN3(r, g, b) + MAX3(r, g, b)) / 2);
3278	    TRACE(("calculated L: %d\n", l));
3279	} else if (h < 0 && l >= 0 && s < 0 && r < 0 && g < 0 && b < 0) {
3280	    TRACE(("found L colorspec to be converted: %hd,%hd,%hd\n",
3281		   h, l, s));
3282	    hls2rgb(0, l, 0, &r, &g, &b);
3283	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
3284	} else {
3285	    TRACE(("unrecognized colorspec format\n"));
3286	    return 0;
3287	}
3288    }
3289
3290    /*
3291     * The VT240 and VT330 models convert to the closest grayscale value.
3292     */
3293    if (context->terminal_id == 240 || context->terminal_id == 330) {
3294	hls2rgb(0, l, 0, &r, &g, &b);
3295	TRACE(("converted to grayscale: %hd,%hd,%hd\n", r, g, b));
3296    }
3297
3298    *r_out = r;
3299    *g_out = g;
3300    *b_out = b;
3301
3302    skip_regis_whitespace(&colorspec);
3303    if (colorspec.pos < colorspec.len) {
3304	char skip;
3305
3306	skip = pop_fragment(&colorspec);
3307	(void) skip;		/* variable needed only if tracing */
3308	TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS colorspec \"%c\"\n",
3309	       skip));
3310    }
3311
3312    return 1;
3313}
3314
3315static int
3316load_regis_regnum_or_colorspec(RegisGraphicsContext const *context,
3317			       RegisDataFragment const *input,
3318			       RegisterNum *out)
3319{
3320    int val;
3321    RegisDataFragment colorspec;
3322    RegisDataFragment num;
3323    RegisDataFragment coloroption;
3324
3325    copy_fragment(&colorspec, input);
3326    TRACE(("looking at colorspec pattern: \"%s\"\n",
3327	   fragment_to_tempstr(&colorspec)));
3328
3329    skip_regis_whitespace(&colorspec);
3330
3331    if (extract_regis_num(&colorspec, &num)) {
3332	if (!regis_num_to_int(&num, &val)) {
3333	    TRACE(("DATA_ERROR: colorspec value %s is not a valid register\n",
3334		   fragment_to_tempstr(&num)));
3335	    return 0;
3336	}
3337	if (val < 0) {
3338	    /* FIXME: error, truncate, wrap, ...? */
3339	    TRACE(("DATA_ERROR: ignoring negative colorspec value: %d\n", val));
3340	    return 0;
3341	}
3342	if (val >= (int) context->destination_graphic->valid_registers) {
3343	    /* FIXME: error, truncate, wrap, ...? */
3344	    TRACE(("DATA_ERROR: colorspec value %d is too big; wrapping\n",
3345		   val));
3346	    val %= (int) context->destination_graphic->valid_registers;
3347	}
3348
3349	TRACE(("colorspec contains index for register %u\n", val));
3350	*out = (RegisterNum) val;
3351
3352	skip_regis_whitespace(&colorspec);
3353	if (colorspec.pos < colorspec.len) {
3354	    char skip;
3355
3356	    skip = pop_fragment(&colorspec);
3357	    (void) skip;	/* variable needed only if tracing */
3358	    TRACE(("DATA_ERROR: unexpected character after register \"%c\"\n",
3359		   skip));
3360	    return 0;
3361	}
3362
3363	return 1;
3364    }
3365
3366    if (extract_regis_parenthesized_data(&colorspec, &coloroption)) {
3367	short r, g, b;
3368
3369	if (!load_regis_colorspec(context, &coloroption, &r, &g, &b)) {
3370	    TRACE(("unable to parse colorspec\n"));
3371	    return 0;
3372	}
3373
3374	*out = find_color_register(context->destination_graphic->color_registers,
3375				   r, g, b);
3376	TRACE(("colorspec maps to closest register %u\n", *out));
3377
3378	return 1;
3379    }
3380
3381    TRACE(("expected register number or colorspec, but found: \"%s\"\n",
3382	   fragment_to_tempstr(&colorspec)));
3383    return 0;
3384}
3385
3386static int
3387to_scaled_int(char const *num, int scale, int *value)
3388{
3389    unsigned long whole, frac;
3390    char *end;
3391
3392    /* FIXME: handle whitespace? how about trailing junk? */
3393    whole = strtoul(num, &end, 10);
3394    if (end[0] == '.') {
3395	char temp[5] = "0000";
3396
3397	if (end[1] != '\0') {
3398	    temp[0] = end[1];
3399	    if (end[2] != '\0') {
3400		temp[1] = end[2];
3401		if (end[3] != '\0') {
3402		    temp[2] = end[3];
3403		    if (end[4] != '\0') {
3404			temp[3] = end[4];
3405		    }
3406		}
3407	    }
3408	}
3409	frac = strtoul(temp, NULL, 10);
3410    } else if (end[0] == '\0' || end[0] == ',' || IsSpace(end[0])) {
3411	frac = 0;
3412    } else {
3413	TRACE(("unexpected character %c in number %s\n", end[0], num));
3414	return 0;
3415    }
3416
3417    *value = (int) (whole * (unsigned) scale +
3418		    (frac * (unsigned) scale) / 10000);
3419
3420    return 1;
3421}
3422
3423static int
3424load_regis_raw_extent(char const *extent, int *relx, int *rely,
3425		      int *xloc, int *yloc, int scale)
3426{
3427    int xsign, ysign;
3428    char const *xpart;
3429    char const *ypart;
3430
3431    xpart = extent;
3432    if ((ypart = strchr(extent, ','))) {
3433	ypart++;
3434    } else {
3435	ypart = "";
3436    }
3437
3438    while (IsSpace(xpart[0]))
3439	xpart++;
3440    while (IsSpace(ypart[0]))
3441	ypart++;
3442
3443    if (xpart[0] == '-') {
3444	xsign = -1;
3445	xpart++;
3446    } else if (xpart[0] == '+') {
3447	xsign = +1;
3448	xpart++;
3449    } else {
3450	xsign = 0;
3451    }
3452    if (ypart[0] == '-') {
3453	ysign = -1;
3454	ypart++;
3455    } else if (ypart[0] == '+') {
3456	ysign = +1;
3457	ypart++;
3458    } else {
3459	ysign = 0;
3460    }
3461
3462    if (xpart[0] == '\0' || xpart[0] == ',') {
3463	*relx = 1;
3464	*xloc = 0;
3465    } else if (xsign == 0) {
3466	int val;
3467
3468	if (!to_scaled_int(xpart, scale, &val))
3469	    return 0;
3470	*relx = 0;
3471	*xloc = val;
3472    } else {
3473	int val;
3474
3475	if (!to_scaled_int(xpart, scale, &val))
3476	    return 0;
3477	*relx = 1;
3478	*xloc = xsign * val;
3479    }
3480    if (ypart[0] == '\0') {
3481	*rely = 1;
3482	*yloc = 0;
3483    } else if (ysign == 0) {
3484	int val;
3485
3486	if (!to_scaled_int(ypart, scale, &val))
3487	    return 0;
3488	*rely = 0;
3489	*yloc = val;
3490    } else {
3491	int val;
3492
3493	if (!to_scaled_int(ypart, scale, &val))
3494	    return 0;
3495	*rely = 1;
3496	*yloc = ysign * val;
3497    }
3498
3499    return 1;
3500}
3501
3502static int
3503load_regis_pixel_extent(char const *extent, int origx, int origy,
3504			int *xloc, int *yloc)
3505{
3506    int relx, rely;
3507    int px, py;
3508
3509    if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) {
3510	TRACE(("invalid coordinates in extent %s\n", extent));
3511	return 0;
3512    }
3513
3514    *xloc = px;
3515    *yloc = py;
3516
3517    if (relx)
3518	*xloc += origx;
3519    if (rely)
3520	*yloc += origy;
3521
3522    return 1;
3523}
3524
3525#define COORD_SCALE 1000
3526
3527static int
3528load_regis_coord_extent(RegisGraphicsContext const *context, char const *extent,
3529			int origx, int origy, int *xloc, int *yloc)
3530{
3531    int relx, rely;
3532    int ux, uy;
3533
3534    if (!load_regis_raw_extent(extent, &relx, &rely, &ux, &uy, COORD_SCALE)) {
3535	TRACE(("invalid coordinates in extent %s\n", extent));
3536	return 0;
3537    }
3538
3539    if (relx) {
3540	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
3541	TRACE(("converted relative user X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
3542	       ux / (double) COORD_SCALE, px, context->width,
3543	       context->x_off, context->x_div));
3544	*xloc = origx + px;
3545    } else {
3546	const int px = TRANSLATE_XCOORD(context, ux, COORD_SCALE);
3547	TRACE(("converted absolute user X coord %.03f to absolute pixel X coord %d\n",
3548	       ux / (double) COORD_SCALE, px));
3549	*xloc = px;
3550    }
3551    if (rely) {
3552	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
3553	TRACE(("converted relative user Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
3554	       uy / (double) COORD_SCALE, py, context->height,
3555	       context->y_off, context->y_div));
3556	*yloc = origy + py;
3557    } else {
3558	const int py = TRANSLATE_YCOORD(context, uy, COORD_SCALE);
3559	TRACE(("converted absolute user Y coord %.03f to absolute pixel Y coord %d\n",
3560	       uy / (double) COORD_SCALE, py));
3561	*yloc = py;
3562    }
3563
3564    return 1;
3565}
3566
3567static int
3568load_regis_raw_pixelvector_digit(char const *pixelvector,
3569				 unsigned *offset,
3570				 int *dx, int *dy, int mul)
3571{
3572    switch (pixelvector[*offset]) {
3573    case '0':
3574	*dx += mul;
3575	break;
3576    case '1':
3577	*dx += mul;
3578	*dy -= mul;
3579	break;
3580    case '2':
3581	*dy -= mul;
3582	break;
3583    case '3':
3584	*dx -= mul;
3585	*dy -= mul;
3586	break;
3587    case '4':
3588	*dx -= mul;
3589	break;
3590    case '5':
3591	*dx -= mul;
3592	*dy += mul;
3593	break;
3594    case '6':
3595	*dy += mul;
3596	break;
3597    case '7':
3598	*dx += mul;
3599	*dy += mul;
3600	break;
3601    default:
3602	return 0;
3603    }
3604
3605    (*offset)++;
3606    return 1;
3607}
3608
3609static int
3610load_regis_pixel_pixelvector(char const *pixelvector,
3611			     int mul,
3612			     int origx, int origy,
3613			     int *xloc, int *yloc)
3614{
3615    int found = 0;
3616    int px = 0, py = 0;
3617    unsigned offset = 0U;
3618    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
3619					    &px, &py,
3620					    mul))
3621	found = 1;
3622    if (pixelvector[offset] != '\0') {
3623	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
3624	       &pixelvector[offset]));
3625    }
3626
3627    *xloc = origx + px;
3628    *yloc = origy + py;
3629
3630    return found;
3631}
3632
3633static int
3634load_regis_coord_pixelvector(RegisGraphicsContext const *context,
3635			     char const *pixelvector,
3636			     int origx, int origy,
3637			     int *xloc, int *yloc)
3638{
3639    const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE);
3640    int found = 0;
3641    int ux = 0, uy = 0;
3642    unsigned offset = 0U;
3643    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
3644					    &ux, &uy,
3645					    mul))
3646	found = 1;
3647    if (pixelvector[offset] != '\0') {
3648	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
3649	       &pixelvector[offset]));
3650    } {
3651	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
3652	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
3653
3654	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
3655	       ux / (double) COORD_SCALE, px, context->width,
3656	       context->x_off, context->x_div));
3657	*xloc = origx + px;
3658
3659	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
3660	       uy / (double) COORD_SCALE, py, context->height,
3661	       context->y_off, context->y_div));
3662	*yloc = origy + py;
3663    }
3664
3665    return found;
3666}
3667
3668static int
3669load_regis_coord_pixelvector_step(RegisGraphicsContext const *context,
3670				  char const *pixelvector,
3671				  unsigned *offset,
3672				  int origx, int origy,
3673				  int *xloc, int *yloc)
3674{
3675    const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE);
3676    int found = 0;
3677    int ux = 0, uy = 0;
3678    if (load_regis_raw_pixelvector_digit(pixelvector, offset, &ux, &uy, mul))
3679	found = 1;
3680    if (!found && pixelvector[*offset] != '\0') {
3681	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
3682	       &pixelvector[*offset]));
3683    } {
3684	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
3685	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
3686
3687	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
3688	       ux / (double) COORD_SCALE, px, context->width,
3689	       context->x_off, context->x_div));
3690	*xloc = origx + px;
3691
3692	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
3693	       uy / (double) COORD_SCALE, py, context->height,
3694	       context->y_off, context->y_div));
3695	*yloc = origy + py;
3696    }
3697
3698    return found;
3699}
3700
3701static int
3702load_regis_write_control(RegisParseState *state,
3703			 RegisGraphicsContext const *context,
3704			 int cur_x, int cur_y,
3705			 int option,
3706			 RegisDataFragment *arg,
3707			 RegisWriteControls *out)
3708{
3709    TRACE(("checking write control option \"%c\" with arg \"%s\"\n",
3710	   option, fragment_to_tempstr(arg)));
3711    switch (option) {
3712    case 'C':
3713    case 'c':
3714	TRACE(("write control compliment writing mode \"%s\"\n",
3715	       fragment_to_tempstr(arg)));
3716	out->write_style = WRITE_STYLE_COMPLEMENT;
3717	break;
3718    case 'E':
3719    case 'e':
3720	TRACE(("write control erase writing mode \"%s\"\n",
3721	       fragment_to_tempstr(arg)));
3722	out->write_style = WRITE_STYLE_ERASE;
3723	break;
3724    case 'F':
3725    case 'f':
3726	TRACE(("write control plane write mask \"%s\"\n",
3727	       fragment_to_tempstr(arg)));
3728	{
3729	    int val;
3730	    if (!regis_num_to_int(arg, &val) ||
3731		val < 0 || val >= (int) context->destination_graphic->valid_registers) {
3732		TRACE(("interpreting out of range value as 0 FIXME\n"));
3733		out->plane_mask = 0U;
3734	    } else {
3735		out->plane_mask = (unsigned) val;
3736	    }
3737	}
3738	break;
3739    case 'I':
3740    case 'i':
3741	TRACE(("write control foreground color \"%s\"\n",
3742	       fragment_to_tempstr(arg)));
3743	if (!load_regis_regnum_or_colorspec(context, arg, &out->foreground)) {
3744	    TRACE(("DATA_ERROR: write control foreground color specifier not recognized: \"%s\"\n",
3745		   fragment_to_tempstr(arg)));
3746	    return 0;
3747	}
3748	break;
3749    case 'L':
3750    case 'l':
3751	TRACE(("write control line width \"%s\" (FIXME: currently ignored)\n",
3752	       fragment_to_tempstr(arg)));
3753	{
3754	    int val;
3755	    if (!regis_num_to_int(arg, &val) ||
3756		val < 0 || val >= (int) 9) {
3757		TRACE(("interpreting out of range value as 1 FIXME\n"));
3758		out->line_width = 1U;
3759	    } else {
3760		out->line_width = (unsigned) val;
3761	    }
3762	}
3763	break;
3764    case 'M':
3765    case 'm':
3766	TRACE(("write control found pixel multiplication factor \"%s\"\n",
3767	       fragment_to_tempstr(arg)));
3768	{
3769	    int val;
3770	    if (!regis_num_to_int(arg, &val) || val <= 0) {
3771		TRACE(("interpreting out of range value %d as 1 FIXME\n", val));
3772		out->pv_multiplier = 1U;
3773	    } else {
3774		out->pv_multiplier = (unsigned) val;
3775	    }
3776	}
3777	break;
3778    case 'N':
3779    case 'n':
3780	TRACE(("write control negative pattern control \"%s\"\n",
3781	       fragment_to_tempstr(arg)));
3782	{
3783	    int val;
3784	    if (!regis_num_to_int(arg, &val)) {
3785		val = -1;
3786	    }
3787	    switch (val) {
3788	    default:
3789		TRACE(("interpreting out of range value %d as 0 FIXME\n", val));
3790		out->invert_pattern = 0U;
3791		break;
3792	    case 0:
3793		out->invert_pattern = 0U;
3794		break;
3795	    case 1:
3796		out->invert_pattern = 1U;
3797		break;
3798	    }
3799	}
3800	break;
3801    case 'P':
3802    case 'p':
3803	TRACE(("write control found pattern control \"%s\"\n",
3804	       fragment_to_tempstr(arg)));
3805	{
3806	    RegisDataFragment suboptionset;
3807	    RegisDataFragment suboptionarg;
3808	    RegisDataFragment item;
3809	    char suboption;
3810
3811	    while (arg->pos < arg->len) {
3812		if (skip_regis_whitespace(arg))
3813		    continue;
3814
3815		TRACE(("looking for option in \"%s\"\n",
3816		       fragment_to_tempstr(arg)));
3817		if (extract_regis_parenthesized_data(arg, &suboptionset)) {
3818		    TRACE(("got write pattern suboptionset: \"%s\"\n",
3819			   fragment_to_tempstr(&suboptionset)));
3820		    while (suboptionset.pos < suboptionset.len) {
3821			skip_regis_whitespace(&suboptionset);
3822			if (extract_regis_option(&suboptionset, &suboption,
3823						 &suboptionarg)) {
3824			    skip_regis_whitespace(&suboptionarg);
3825			    TRACE(("inspecting write pattern suboption \"%c\" with value \"%s\"\n",
3826				   suboption,
3827				   fragment_to_tempstr(&suboptionarg)));
3828			    switch (suboption) {
3829			    case 'M':
3830			    case 'm':
3831				TRACE(("found pattern multiplier \"%s\"\n",
3832				       fragment_to_tempstr(&suboptionarg)));
3833				{
3834				    RegisDataFragment num;
3835				    int val;
3836
3837				    if (extract_regis_num(&suboptionarg,
3838							  &num)) {
3839					if (!regis_num_to_int(&num, &val)
3840					    || val < 1) {
3841					    TRACE(("interpreting out of range pattern multiplier \"%s\" as 2 FIXME\n",
3842						   fragment_to_tempstr(&num)));
3843					    out->pattern_multiplier = 2U;
3844					} else {
3845					    out->pattern_multiplier =
3846						(unsigned) val;
3847					}
3848					skip_regis_whitespace(&suboptionarg);
3849				    }
3850
3851				    if (fragment_len(&suboptionarg)) {
3852					TRACE(("DATA_ERROR: unknown content after pattern multiplier \"%s\"\n",
3853					       fragment_to_tempstr(&suboptionarg)));
3854					return 0;
3855				    }
3856				}
3857				break;
3858			    default:
3859				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
3860				       suboption,
3861				       fragment_to_tempstr(&suboptionarg)));
3862				return 0;
3863			    }
3864			    continue;
3865			}
3866
3867			TRACE(("DATA_ERROR: skipping unknown token in pattern control suboptionset (expecting option): \"%s\"\n",
3868			       fragment_to_tempstr(&suboptionset)));
3869			pop_fragment(&suboptionset);
3870		    }
3871		    continue;
3872		}
3873
3874		TRACE(("looking for int in \"%s\"\n",
3875		       fragment_to_tempstr(arg)));
3876		if (extract_regis_num(arg, &item)) {
3877		    if (peek_fragment(&item) == '0' ||
3878			peek_fragment(&item) == '1') {
3879			unsigned pattern = 0U;
3880			unsigned bitcount;
3881
3882			TRACE(("converting pattern bits \"%s\"\n",
3883			       fragment_to_tempstr(&item)));
3884			for (bitcount = 0;; bitcount++) {
3885			    char ch = pop_fragment(&item);
3886			    if (ch == '\0')
3887				break;
3888			    switch (ch) {
3889			    case '0':
3890				if (bitcount < MAX_PATTERN_BITS) {
3891				    pattern <<= 1U;
3892				}
3893				break;
3894			    case '1':
3895				if (bitcount < MAX_PATTERN_BITS) {
3896				    pattern <<= 1U;
3897				    pattern |= 1U;
3898				}
3899				break;
3900			    default:
3901				TRACE(("DATA_ERROR: unknown ReGIS write pattern bit value \"%c\"\n",
3902				       ch));
3903				return 0;
3904			    }
3905			}
3906
3907			if (bitcount > 0U) {
3908			    unsigned extrabits;
3909
3910			    for (extrabits = 0;
3911				 bitcount + extrabits < MAX_PATTERN_BITS;
3912				 extrabits++) {
3913				if (pattern & (1U << (bitcount - 1U))) {
3914				    pattern <<= 1U;
3915				    pattern |= 1U;
3916				} else {
3917				    pattern <<= 1U;
3918				}
3919			    }
3920			}
3921
3922			out->pattern = pattern;
3923		    } else {
3924			int val;
3925
3926			TRACE(("converting pattern id \"%s\"\n",
3927			       fragment_to_tempstr(&item)));
3928			if (!regis_num_to_int(&item, &val))
3929			    val = -1;
3930			switch (val) {	/* FIXME: exponential allowed? */
3931			case 0:
3932			    out->pattern = 0x00;	/* solid bg */
3933			    break;
3934			case 1:
3935			    out->pattern = 0xff;	/* solid fg */
3936			    break;
3937			case 2:
3938			    out->pattern = 0xf0;	/* dash */
3939			    break;
3940			case 3:
3941			    out->pattern = 0xe4;	/* dash dot */
3942			    break;
3943			case 4:
3944			    out->pattern = 0xaa;	/* dot */
3945			    break;
3946			case 5:
3947			    out->pattern = 0xea;	/* dash dot dot */
3948			    break;
3949			case 6:
3950			    out->pattern = 0x88;	/* sparse dot */
3951			    break;
3952			case 7:
3953			    out->pattern = 0x84;	/* asymmetric sparse dot */
3954			    break;
3955			case 8:
3956			    out->pattern = 0xc8;	/* sparse dash dot */
3957			    break;
3958			case 9:
3959			    out->pattern = 0x86;	/* sparse dot dash */
3960			    break;
3961			default:
3962			    TRACE(("DATA_ERROR: unknown ReGIS standard write pattern \"%d\"\n",
3963				   val));
3964			    return 0;
3965			}
3966		    }
3967
3968		    TRACE(("final pattern is %02x\n", out->pattern));
3969		    continue;
3970		}
3971		skip_regis_whitespace(arg);
3972
3973		TRACE(("DATA_ERROR: skipping unknown token in pattern suboption: \"%s\"\n",
3974		       fragment_to_tempstr(arg)));
3975		pop_fragment(arg);
3976	    }
3977	}
3978	break;
3979    case 'R':
3980    case 'r':
3981	TRACE(("write control switch to replacement writing mode \"%s\"\n",
3982	       fragment_to_tempstr(arg)));
3983	out->write_style = WRITE_STYLE_REPLACE;
3984	break;
3985    case 'S':
3986    case 's':
3987	TRACE(("write control shading control \"%s\"\n",
3988	       fragment_to_tempstr(arg)));
3989	{
3990	    RegisDataFragment suboptionset;
3991	    RegisDataFragment suboptionarg;
3992	    RegisDataFragment item;
3993	    char suboption;
3994	    char shading_character = '\0';
3995	    unsigned reference_dim = WRITE_SHADING_REF_Y;
3996	    /* FIXME: are relative offsets additive? */
3997	    int ref_x = cur_x, ref_y = cur_y;
3998	    int shading_enabled = 0;
3999
4000	    while (arg->pos < arg->len) {
4001		if (skip_regis_whitespace(arg))
4002		    continue;
4003
4004		if (extract_regis_string(arg, state->temp, state->templen)) {
4005		    TRACE(("found fill char \"%s\"\n", state->temp));
4006		    /* FIXME: allow longer strings, ignore extra chars, or treat as error? */
4007		    if (strlen(state->temp) != 1) {
4008			TRACE(("DATA_ERROR: expected exactly one char in fill string FIXME\n"));
4009			return 0;
4010		    }
4011		    shading_character = state->temp[0];
4012		    shading_enabled = 1;
4013		    TRACE(("shading character is: '%c' (%d)\n",
4014			   shading_character, (int) shading_character));
4015		    continue;
4016		}
4017
4018		if (extract_regis_parenthesized_data(arg, &suboptionset)) {
4019		    skip_regis_whitespace(&suboptionset);
4020		    TRACE(("got shading control suboptionset: \"%s\"\n",
4021			   fragment_to_tempstr(&suboptionset)));
4022		    while (suboptionset.pos < suboptionset.len) {
4023			if (skip_regis_whitespace(&suboptionset))
4024			    continue;
4025			if (extract_regis_option(&suboptionset, &suboption,
4026						 &suboptionarg)) {
4027			    TRACE(("inspecting write shading suboption \"%c\" with value \"%s\"\n",
4028				   suboption,
4029				   fragment_to_tempstr(&suboptionarg)));
4030			    switch (suboption) {
4031			    case 'X':
4032			    case 'x':
4033				TRACE(("found horizontal shading suboption \"%s\"\n",
4034				       fragment_to_tempstr(&suboptionarg)));
4035				if (fragment_len(&suboptionarg)) {
4036				    TRACE(("DATA_ERROR: unexpected value to horizontal shading suboption FIXME\n"));
4037				    return 0;
4038				}
4039				reference_dim = WRITE_SHADING_REF_X;
4040				shading_enabled = 1;
4041				break;
4042			    default:
4043				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
4044				       suboption,
4045				       fragment_to_tempstr(&suboptionarg)));
4046				return 0;
4047			    }
4048			    continue;
4049			}
4050
4051			TRACE(("DATA_ERROR: skipping unknown token in shading control suboptionset (expecting option): \"%s\"\n",
4052			       fragment_to_tempstr(&suboptionset)));
4053			pop_fragment(&suboptionset);
4054		    }
4055		    continue;
4056		}
4057
4058		if (extract_regis_extent(arg, &item)) {
4059		    TRACE(("found extent in shading option curr=%d,%d ref=%d,%d\n",
4060			   cur_x, cur_y, ref_x, ref_y));
4061		    if (!load_regis_coord_extent(context,
4062						 fragment_to_tempstr(&item),
4063						 ref_x, ref_y,
4064						 &ref_x, &ref_y)) {
4065			TRACE(("DATA_ERROR: unable to parse extent in write shading option '%c': \"%s\"\n",
4066			       option, fragment_to_tempstr(&item)));
4067			return 0;
4068		    }
4069		    TRACE(("shading reference = %d,%d (%s)\n", ref_x, ref_y,
4070			   ((reference_dim == WRITE_SHADING_REF_X)
4071			    ? "X"
4072			    : "Y")));
4073		    continue;
4074		}
4075
4076		if (extract_regis_num(arg, &item)) {
4077		    if (!regis_num_to_int(&item, &shading_enabled)) {
4078			TRACE(("DATA_ERROR: unable to parse int in write shading option '%c': \"%s\"\n",
4079			       option, fragment_to_tempstr(&item)));
4080			return 0;
4081		    }
4082		    if (shading_enabled < 0 || shading_enabled > 1) {
4083			TRACE(("interpreting out of range value %d as 0 FIXME\n",
4084			       shading_enabled));
4085			shading_enabled = 0;
4086		    }
4087		    TRACE(("shading enabled = %d\n", shading_enabled));
4088		    continue;
4089		}
4090
4091		if (skip_regis_whitespace(arg)) {
4092		    continue;
4093		}
4094
4095		TRACE(("DATA_ERROR: skipping unknown token in shade suboption: \"%s\"\n",
4096		       fragment_to_tempstr(arg)));
4097		pop_fragment(arg);
4098	    }
4099
4100	    if (shading_enabled) {
4101		out->shading_enabled = 1U;
4102		out->shading_reference_dim = reference_dim;
4103		out->shading_reference = ((reference_dim == WRITE_SHADING_REF_X)
4104					  ? ref_x
4105					  : ref_y);
4106		out->shading_character = shading_character;
4107		TRACE(("final shading state: enabled, dim=%d ref=%d, char=%c\n",
4108		       out->shading_reference_dim, out->shading_reference,
4109		       out->shading_character));
4110	    } else {
4111		/* FIXME: confirm there is no effect if shading isn't enabled
4112		 * in the same command
4113		 */
4114		out->shading_enabled = 0U;
4115		TRACE(("final shading state: shading disabled\n"));
4116	    }
4117	}
4118	break;
4119    case 'V':
4120    case 'v':
4121	TRACE(("write control switch to overlay writing mode \"%s\"\n",
4122	       fragment_to_tempstr(arg)));
4123	out->write_style = WRITE_STYLE_OVERLAY;
4124	break;
4125    default:
4126	TRACE(("DATA_ERROR: ignoring unknown ReGIS write option \"%c\" arg \"%s\"\n",
4127	       option, fragment_to_tempstr(arg)));
4128	return 0;
4129    }
4130
4131    return 1;
4132}
4133
4134static int
4135load_regis_write_control_set(RegisParseState *state,
4136			     RegisGraphicsContext const *context,
4137			     int cur_x, int cur_y,
4138			     RegisDataFragment *controls,
4139			     RegisWriteControls *out)
4140{
4141    RegisDataFragment optionset;
4142    RegisDataFragment arg;
4143    char option;
4144
4145    while (controls->pos < controls->len) {
4146	if (skip_regis_whitespace(controls))
4147	    continue;
4148
4149	if (extract_regis_parenthesized_data(controls, &optionset)) {
4150	    TRACE(("got write control optionset: \"%s\"\n",
4151		   fragment_to_tempstr(&optionset)));
4152	    while (optionset.pos < optionset.len) {
4153		skip_regis_whitespace(&optionset);
4154		if (extract_regis_option(&optionset, &option, &arg)) {
4155		    skip_regis_whitespace(&arg);
4156		    TRACE(("got write control option and value: \"%c\" \"%s\"\n",
4157			   option, fragment_to_tempstr(&arg)));
4158		    if (!load_regis_write_control(state, context,
4159						  cur_x, cur_y,
4160						  option, &arg, out)) {
4161			return 0;
4162		    }
4163		    continue;
4164		}
4165
4166		TRACE(("DATA_ERROR: skipping unknown token in write control optionset (expecting option): \"%s\"\n",
4167		       fragment_to_tempstr(&optionset)));
4168		pop_fragment(&optionset);
4169	    }
4170	    continue;
4171	}
4172
4173	TRACE(("DATA_ERROR: skipping unknown token in write controls (expecting optionset): \"%s\"\n",
4174	       fragment_to_tempstr(controls)));
4175	pop_fragment(controls);
4176    }
4177
4178    return 1;
4179}
4180
4181static void
4182init_regis_write_controls(int terminal_id, unsigned all_planes,
4183			  RegisWriteControls *controls)
4184{
4185    controls->pv_multiplier = 1U;
4186    controls->pattern = 0xff;	/* solid */
4187    controls->pattern_multiplier = 2U;
4188    controls->invert_pattern = 0U;
4189    controls->plane_mask = all_planes;
4190    controls->write_style = WRITE_STYLE_OVERLAY;
4191    switch (terminal_id) {
4192    case 125:			/* FIXME: verify */
4193    case 240:			/* FIXME: verify */
4194    case 241:			/* FIXME: verify */
4195    case 330:
4196	controls->foreground = 3U;
4197	break;
4198    case 340:
4199    default:
4200	controls->foreground = 7U;
4201	break;
4202    case 382:
4203	controls->foreground = 1U;	/* FIXME: verify */
4204	break;
4205    }
4206    controls->shading_enabled = 0U;
4207    controls->shading_character = '\0';
4208    controls->shading_reference = 0;	/* no meaning if shading is disabled */
4209    controls->shading_reference_dim = WRITE_SHADING_REF_NONE;
4210    controls->line_width = 1U;
4211    /* FIXME: add the rest */
4212}
4213
4214static void
4215map_regis_graphics_pages(XtermWidget xw, RegisGraphicsContext *context)
4216{
4217    const int charrow = 0;
4218    const int charcol = 0;
4219    unsigned old_display_id = ~0U;
4220
4221    if (context->destination_graphic)
4222	context->destination_graphic->hidden = 1;
4223    if (context->display_graphic) {
4224	context->display_graphic->hidden = 1;
4225	old_display_id = context->display_graphic->id;
4226    }
4227
4228    context->destination_graphic = get_new_or_matching_graphic(xw,
4229							       charrow, charcol,
4230							       context->width,
4231							       context->height,
4232							       context->destination_page);
4233    if (context->destination_graphic) {
4234	context->destination_graphic->hidden = 1;
4235	context->destination_graphic->valid = 1;
4236    }
4237
4238    context->display_graphic = get_new_or_matching_graphic(xw,
4239							   charrow, charcol,
4240							   context->width,
4241							   context->height,
4242							   context->display_page);
4243    if (context->display_graphic) {
4244	context->display_graphic->hidden = 0;
4245	if (old_display_id != context->display_graphic->id) {
4246	    if (!context->display_graphic->valid) {
4247		draw_solid_rectangle(context->display_graphic, 0, 0,
4248				     context->width, context->height,
4249				     context->background);
4250	    }
4251	    context->display_graphic->dirty = 1;
4252	    context->force_refresh = 1;
4253	    /* FIXME: This isn't really enough.  If there are holes in the new
4254	     * graphic they should be cleared and set to the text from the same
4255	     * page.  But we don't have pages for text in xterm (the alt buffer
4256	     * is similar though).
4257	     */
4258	}
4259	context->display_graphic->valid = 1;
4260    }
4261
4262    TRACE(("using graphics destination=[%d -> %u] display=[%d -> %u]\n",
4263	   context->destination_page,
4264	   (context->destination_graphic
4265	    ? context->destination_graphic->id
4266	    : 0U),
4267	   context->display_page,
4268	   (context->display_graphic
4269	    ? context->display_graphic->id
4270	    : 0U)));
4271}
4272
4273static void
4274copy_regis_write_controls(RegisWriteControls const *src,
4275			  RegisWriteControls *dst)
4276{
4277    dst->pv_multiplier = src->pv_multiplier;
4278    dst->pattern = src->pattern;
4279    dst->pattern_multiplier = src->pattern_multiplier;
4280    dst->invert_pattern = src->invert_pattern;
4281    dst->foreground = src->foreground;
4282    dst->plane_mask = src->plane_mask;
4283    dst->write_style = src->write_style;
4284    dst->shading_enabled = src->shading_enabled;
4285    dst->shading_character = src->shading_character;
4286    dst->shading_reference = src->shading_reference;
4287    dst->shading_reference_dim = src->shading_reference_dim;
4288    dst->line_width = src->line_width;
4289}
4290
4291static void
4292init_regis_text_controls(RegisTextControls *controls)
4293{
4294    controls->alphabet_num = 0U;	/* built-in */
4295    controls->character_set_l = 0U;	/* ASCII */
4296    controls->character_set_r = 0U;	/* Latin-1 */
4297    get_standard_character_size(1, &controls->character_display_w,
4298				&controls->character_display_h,
4299				&controls->character_unit_cell_w,
4300				&controls->character_unit_cell_h,
4301				&controls->character_inc_x,
4302				&controls->character_inc_y);
4303    controls->string_rotation = 0;
4304    controls->character_rotation = 0;
4305    controls->slant = 0;
4306}
4307
4308static void
4309copy_regis_text_controls(RegisTextControls const *src, RegisTextControls *dst)
4310{
4311    dst->alphabet_num = src->alphabet_num;
4312    dst->character_set_l = src->character_set_l;
4313    dst->character_set_r = src->character_set_r;
4314    dst->character_display_w = src->character_display_w;
4315    dst->character_display_h = src->character_display_h;
4316    dst->character_unit_cell_w = src->character_unit_cell_w;
4317    dst->character_unit_cell_h = src->character_unit_cell_h;
4318    dst->character_inc_x = src->character_inc_x;
4319    dst->character_inc_y = src->character_inc_y;
4320    dst->string_rotation = src->string_rotation;
4321    dst->character_rotation = src->character_rotation;
4322    dst->slant = src->slant;
4323}
4324
4325static void
4326init_regis_alphabets(RegisGraphicsContext *context)
4327{
4328    unsigned alphabet_index;
4329
4330    for (alphabet_index = 0U; alphabet_index < MAX_REGIS_ALPHABETS;
4331	 alphabet_index++) {
4332	context->alphabets[alphabet_index].alphabet_num = INVALID_ALPHABET_NUM;
4333	context->alphabets[alphabet_index].pixw = 0U;
4334	context->alphabets[alphabet_index].pixh = 0U;
4335	context->alphabets[alphabet_index].name[0] = '\0';
4336	context->alphabets[alphabet_index].fontname[0] = '\0';
4337	context->alphabets[alphabet_index].use_font = 0;
4338	context->alphabets[alphabet_index].bytes = NULL;
4339    }
4340}
4341
4342static void
4343init_regis_graphics_context(int terminal_id, int width, int height,
4344			    unsigned max_colors, const char *builtin_font,
4345			    RegisGraphicsContext *context)
4346{
4347    context->destination_graphic = NULL;
4348    context->display_graphic = NULL;
4349    context->display_page = 0U;
4350    context->destination_page = 0U;
4351    context->terminal_id = terminal_id;
4352
4353    /* reset addressing / clear user coordinates */
4354    context->width = width;
4355    context->height = height;
4356    context->x_off = 0;
4357    context->y_off = 0;
4358    context->x_div = width - 1;
4359    context->y_div = height - 1;
4360
4361    /*
4362     * Generate a mask covering all valid color register address bits
4363     * (but don't bother past 2**16).
4364     */
4365    context->all_planes = max_colors;
4366    context->all_planes--;
4367    context->all_planes |= 1U;
4368    context->all_planes |= context->all_planes >> 1U;
4369    context->all_planes |= context->all_planes >> 2U;
4370    context->all_planes |= context->all_planes >> 4U;
4371    context->all_planes |= context->all_planes >> 8U;
4372
4373    context->builtin_font = builtin_font;
4374
4375    init_regis_write_controls(terminal_id, context->all_planes,
4376			      &context->persistent_write_controls);
4377    copy_regis_write_controls(&context->persistent_write_controls,
4378			      &context->temporary_write_controls);
4379
4380    init_regis_text_controls(&context->persistent_text_controls);
4381    context->current_text_controls = &context->persistent_text_controls;
4382    init_regis_alphabets(context);
4383
4384    context->multi_input_mode = 0;
4385    /* FIXME: coordinates */
4386    /* FIXME: scrolling */
4387    context->background = 0U;
4388    /* FIXME: input cursor location */
4389    /* FIXME: input cursor style */
4390    context->graphics_output_cursor_x = 0;
4391    context->graphics_output_cursor_y = 0;
4392    /* FIXME: output cursor style */
4393
4394    context->force_refresh = 0;
4395}
4396
4397static int
4398parse_regis_command(RegisParseState *state)
4399{
4400    char ch = peek_fragment(&state->input);
4401    if (ch == '\0')
4402	return 0;
4403
4404    if (!extract_regis_command(&state->input, &ch))
4405	return 0;
4406
4407    switch (ch) {
4408    case 'C':
4409    case 'c':
4410	/* Curve
4411
4412	 * C
4413	 * (A)  # set the arc length in degrees (+ or nothing for
4414	 *      # counter-clockwise, - for clockwise, rounded to the
4415	 *      # closest integer degree)
4416	 * (B)  # begin closed curve sequence (must have at least two
4417	 *      # values; this option can not be nested)
4418	 * (C)  # position is the center, current location is the
4419	 *      # circumference (stays in effect until next command)
4420	 * (E)  # end curve sequence (drawing is performed here)
4421	 * (S)  # begin open curve sequence
4422	 * (W)  # temporary write options (see write command)
4423	 * [<center, circumference position>]  # center if (C), otherwise point on circumference
4424	 * [<point in curve sequence>]...  # if between (B) and (E)
4425	 * <pv>...  # if between (B) and (E)
4426	 */
4427	TRACE(("found ReGIS command \"%c\" (curve)\n", ch));
4428	state->command = 'c';
4429	state->curve_mode = CURVE_POSITION_ARC_EDGE;
4430	state->arclen = 360;
4431	state->num_points = 0U;
4432	break;
4433    case 'F':
4434    case 'f':
4435	/* Fill
4436
4437	 * F
4438	 * (V)  # polygon (see vector command)
4439	 * (C)  # curve (see curve command)
4440	 * (W)  # temporary write options (see write command)
4441	 */
4442	TRACE(("found ReGIS command \"%c\" (filled polygon)\n", ch));
4443	state->command = 'f';
4444	break;
4445    case 'L':
4446    case 'l':
4447	/* Load
4448
4449	 * L
4450	 * (A)  # set character set number or name
4451	 * (F)"fontname"  # load from font (xterm extension)
4452	 * (S)[w,h]  # set glyph size (xterm extension)
4453	 * "ascii"xx,xx,xx,xx,xx,xx,xx,xx  # pixel values
4454	 */
4455	TRACE(("found ReGIS command \"%c\" (load charset)\n", ch));
4456	state->command = 'l';
4457	state->load_index = MAX_REGIS_ALPHABETS;
4458	state->load_w = 8U;
4459	state->load_h = 10U;
4460	state->load_alphabet = 1U;	/* FIXME: is this the correct default */
4461	state->load_name[0] = '\0';
4462	state->load_glyph = (unsigned) (unsigned char) '\0';
4463	state->load_row = 0U;
4464	break;
4465    case 'P':
4466    case 'p':
4467	/* Position
4468
4469	 * P
4470	 * (B)  # begin bounded position stack (last point returns to first)
4471	 * (E)  # end position stack
4472	 * (P)  # select graphics page for the input and output cursors
4473	 * (S)  # begin unbounded position stack
4474	 * (W)  # temporary write options (see write command)
4475	 * <pv>  # move: 0 == right, 1 == upper right, ..., 7 == lower right
4476	 * [<position>]  # move to position (X, Y, or both)
4477	 *
4478	 * Note the stack does not need to be ended before the next command
4479	 * Note: maximum depth is 16 levels
4480	 */
4481	TRACE(("found ReGIS command \"%c\" (position)\n", ch));
4482	state->command = 'p';
4483	break;
4484    case 'R':
4485    case 'r':
4486	/* Report
4487
4488	 * R
4489	 * (E)  # parse error
4490	 * (I<val>)  # set input mode (0 == one-shot, 1 == multiple) (always returns CR)
4491	 * (L)  # character set
4492	 * (M(<name>)  # macrograph contents
4493	 * (M(=)  # macrograph storage
4494	 * (P)  # output cursor position
4495	 * (P(I))  # input cursor position (when in one-shot or multiple mode)
4496	 */
4497	TRACE(("found ReGIS command \"%c\" (report status)\n", ch));
4498	state->command = 'r';
4499	break;
4500    case 'S':
4501    case 's':
4502	/* Screen
4503
4504	 * S
4505	 * (A[<upper left>][<lower right>])
4506	 * (C<setting>  # 0 (cursor output off), 1 (cursor output on)
4507	 * (E)  # erase to background color, resets shades, curves, and stacks
4508	 * (F)  # print the graphic and erase the screen (DECprint extension)
4509	 * (H(P<printer offset>)[<print area cornet>][<print area corner>)
4510	 * (I<color register>)  # set the background to a specific register
4511	 * (I(<rgbcode>))  # set the background to the register closest to an "RGB" color
4512	 * (I(R<r>G<g>B<b>))  # set the background to the register closest to an RGB triplet (RLogin extension)
4513	 * (I(H<h>L<l>S<s>))  # set the background to the register closest to an HLS triplet
4514	 * (I(L<l>))  # set the background to the register closest to a grayscale value
4515	 * (M<color index to set>(<rgbcode>)...)  # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color and grayscale registers)
4516	 * (M<color index to set>(A<rgbcode>)...)  # codes are D (black), R (red), G (green), B (blue), C (cyan), Y (yellow), M (magenta), W (white) (sets color registers only)
4517	 * (M<color index to set>(R<red>G<green>B<blue>)...)  # 0..100, 0..100, 0..100 (sets color and grayscale registers) (RLogin extension)
4518	 * (M<color index to set>(AR<red>G<green>B<blue>)...)  # 0..100, 0..100, 0..100 (sets color registers only) (RLogin extension)
4519	 * (M<color index to set>(H<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color and grayscale registers)
4520	 * (M<color index to set>(AH<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color registers only)
4521	 * (M<color index to set>(L<mono level>)...)  # level is 0 ... 100 (sets grayscale registers only)
4522	 * (P<graphics page number>)  # 0 (default) or 1
4523	 * (T(<time delay ticks>)  # 60 ticks per second, up to 32767 ticks
4524	 * (W(M<factor>)  # PV multiplier
4525	 * <PV scroll offset>  # scroll data so given coordinate is at the upper-left
4526	 * [scroll offset]  # scroll data so given coordinate is at the upper-left
4527	 */
4528	TRACE(("found ReGIS command \"%c\" (screen)\n", ch));
4529	state->command = 's';
4530	break;
4531    case 'T':
4532    case 't':
4533	/* Text
4534
4535	 * T
4536	 * (A)  # specify which alphabet to select character sets from (0==builtin)
4537	 * (A0L"<designator>"))  # specify a built-in set for GL via two-char designator
4538	 * (A0R"<designator>"))  # specify a built-in set for GR via two-char or three-char designator
4539	 * (A<num>R"<designator>"))  # specify a user-loaded (1-3) set for GR via two-char or three-char designator
4540	 * (B)  # begin temporary text control
4541	 * (D<angle>)  # specify a string or character tilt
4542	 * (E)  # end temporary text control
4543	 * (H<factor>)  # select a height multiplier (1-256)
4544	 * (I<angle>)  # italic/oblique: no slant (0), lean back (-1 though -45), lean forward (+1 through +45)
4545	 * (M[width factor,height factor])  # select size multipliers (width 1-16) (height 1-256)
4546	 * (S<size id>)  # select one of the 17 standard character sizes
4547	 * (S[dimensions])  # set a custom display cell size (char with border)
4548	 * (U[dimensions])  # set a custom unit cell size (char size)
4549	 * (W<write command>)  # temporary write options (see write command)
4550	 * [<char offset>]  # optional offset between characters
4551	 * <PV spacing>  # for subscripts and superscripts
4552	 * '<text>'  # optional
4553	 * "<text>"  # optional
4554	 */
4555	TRACE(("found ReGIS command \"%c\" (text)\n", ch));
4556	state->command = 't';
4557	state->string_rot_set = 0;
4558	break;
4559    case 'V':
4560    case 'v':
4561	/* Vector
4562
4563	 * V
4564	 * (B)  # begin bounded position stack (last point returns to first)
4565	 * (E)  # end position stack
4566	 * (S)  # begin unbounded position stack
4567	 * (W)  # temporary write options (see write command)
4568	 * <pv>  # draw a line to the pixel vector
4569	 * []  # draw a dot at the current location
4570	 * [<position>]  # draw a line to position
4571	 */
4572	TRACE(("found ReGIS command \"%c\" (vector)\n", ch));
4573	state->command = 'v';
4574	break;
4575    case 'W':
4576    case 'w':
4577	/* Write
4578
4579	 * W
4580	 * (C)  # complement writing mode
4581	 * (E)  # erase writing mode
4582	 * (F<plane>)  # set the foreground intensity to a specific register
4583	 * (I<color register>)  # set the foreground to a specific register
4584	 * (I(<rgbcode>))  # set the foreground to the register closest to an "RGB" color
4585	 * (I(R<r>G<g>B<b>))  # set the foreground to the register closest to an RGB triplet (RLogin extension)
4586	 * (I(H<h>L<l>S<s>))  # set the foreground to the register closest to an HLS triplet
4587	 * (I(L<l>))  # set the foreground to the register closest to a grayscale value
4588	 * (L<width>)  # set the line width (RLogin extension)
4589	 * (M<pixel vector multiplier>)  # set the multiplication factor
4590	 * (N<setting>)  # 0 == negative patterns disabled, 1 == negative patterns enabled
4591	 * (P<pattern number>)  # 0..9: 0 == none, 1 == solid, 2 == 50% dash, 3 == dash-dot
4592	 * (P<pattern bits>)  # 2 to 8 bits represented as a 0/1 sequence
4593	 * (P<(M<pattern multiplier>))
4594	 * (R)  # replacement writing mode
4595	 * (S'<character>')  # set shading character
4596	 * (S<setting>)  # 0 == disable shding, 1 == enable shading
4597	 * (S[reference point])  # set a horizontal reference line including this point
4598	 * (S(X)[reference point])  # set a vertical reference line including this point
4599	 * (V)  # overlay writing mode
4600	 */
4601	TRACE(("found ReGIS command \"%c\" (write parameters)\n", ch));
4602	state->command = 'w';
4603	break;
4604    case '@':
4605	/* Macrograph */
4606	TRACE(("found ReGIS macrograph command\n"));
4607	ch = pop_fragment(&state->input);
4608	TRACE(("inspecting macrograph character \"%c\"\n", ch));
4609	switch (ch) {
4610	case '.':
4611	    TRACE(("clearing all macrographs FIXME\n"));
4612	    /* FIXME: handle */
4613	    break;
4614	case ':':
4615	    TRACE(("defining macrograph\n"));
4616	    /* FIXME: what about whitespace before the name? */
4617	    if (fragment_len(&state->input) < 1) {
4618		TRACE(("DATA_ERROR: macrograph definition without name, ignoring\n"));
4619		return 0;
4620	    } {
4621		char name;
4622
4623		name = pop_fragment(&state->input);
4624		TRACE(("defining macrgraph for \"%c\"\n", name));
4625		(void) name;	/* This will be used in the future. */
4626		for (;;) {
4627		    char next = peek_fragment(&state->input);
4628		    if (next == ';') {
4629			/* FIXME: parse, handle  :<name><definition>; */
4630			pop_fragment(&state->input);
4631			break;
4632		    } else if (next == '\0') {
4633			TRACE(("DATA_ERROR: macrograph definition ends before semicolon\n"));
4634			break;
4635		    }
4636		    pop_fragment(&state->input);
4637		}
4638	    }
4639	    break;
4640	case ';':
4641	    TRACE(("DATA_ERROR: found extraneous terminator for macrograph definition\n"));
4642	    break;
4643	default:
4644	    if ((ch > 'A' && ch < 'Z') || (ch > 'a' && ch < 'z')) {
4645		TRACE(("expanding macrograph \"%c\" FIXME\n", ch));
4646		/* FIXME: handle */
4647	    } else {
4648		TRACE(("DATA_ERROR: unknown macrograph subcommand \"%c\"\n",
4649		       ch));
4650	    }
4651	    /* FIXME: parse, handle */
4652	    break;
4653	}
4654	state->command = '@';
4655	break;
4656    default:
4657	TRACE(("DATA_ERROR: unknown ReGIS command %04x (%c), setting to '_'\n",
4658	       (int) ch, ch));
4659	state->command = '_';
4660	state->option = '_';
4661	return 0;
4662    }
4663
4664    state->option = '_';
4665
4666    return 1;
4667}
4668
4669static int
4670parse_regis_option(RegisParseState *state, RegisGraphicsContext *context)
4671{
4672    RegisDataFragment optionarg;
4673
4674    if (!extract_regis_option(&state->input, &state->option, &optionarg))
4675	return 0;
4676    skip_regis_whitespace(&optionarg);
4677
4678    TRACE(("found ReGIS option \"%c\": \"%s\"\n",
4679	   state->option, fragment_to_tempstr(&optionarg)));
4680
4681    switch (state->command) {
4682    case 'c':
4683	TRACE(("inspecting curve option \"%c\" with value \"%s\"\n",
4684	       state->option, fragment_to_tempstr(&optionarg)));
4685	switch (state->option) {
4686	case 'A':
4687	case 'a':
4688	    TRACE(("found arc length \"%s\"\n",
4689		   fragment_to_tempstr(&optionarg)));
4690	    {
4691		RegisDataFragment arclen;
4692
4693		if (!extract_regis_num(&optionarg, &arclen)) {
4694		    TRACE(("DATA_ERROR: expected int in curve arclen option: \"%s\"\n",
4695			   fragment_to_tempstr(&optionarg)));
4696		    break;
4697		}
4698		TRACE(("arc length string %s\n", fragment_to_tempstr(&arclen)));
4699		if (!regis_num_to_int(&arclen, &state->arclen)) {
4700		    TRACE(("DATA_ERROR: unable to parse int in curve arclen option: \"%s\"\n",
4701			   fragment_to_tempstr(&arclen)));
4702		    break;
4703		}
4704		TRACE(("value of arc length is %d\n", state->arclen));
4705		while (state->arclen < -360)
4706		    state->arclen += 360;
4707		while (state->arclen > 360)
4708		    state->arclen -= 360;
4709		TRACE(("using final arc length %d\n", state->arclen));
4710
4711		skip_regis_whitespace(&optionarg);
4712		if (fragment_len(&optionarg)) {
4713		    TRACE(("DATA_ERROR: ignoring trailing junk in arc length option \"%s\"\n",
4714			   fragment_to_tempstr(&optionarg)));
4715		    break;
4716		}
4717	    }
4718	    break;
4719	case 'B':
4720	case 'b':
4721	    TRACE(("begin closed curve \"%s\"\n",
4722		   fragment_to_tempstr(&optionarg)));
4723	    if (fragment_len(&optionarg)) {
4724		TRACE(("DATA_ERROR: invalid closed curve option \"%s\"\n",
4725		       fragment_to_tempstr(&optionarg)));
4726		break;
4727	    }
4728	    state->curve_mode = CURVE_POSITION_CLOSED_CURVE;
4729	    state->num_points = 0U;
4730	    state->x_points[state->num_points] =
4731		context->graphics_output_cursor_x;
4732	    state->y_points[state->num_points] =
4733		context->graphics_output_cursor_y;
4734	    state->num_points++;
4735	    break;
4736	case 'C':
4737	case 'c':
4738	    TRACE(("found center position mode \"%s\"\n",
4739		   fragment_to_tempstr(&optionarg)));
4740	    if (fragment_len(&optionarg)) {
4741		TRACE(("DATA_ERROR: invalid center position option \"%s\"\n",
4742		       fragment_to_tempstr(&optionarg)));
4743		break;
4744	    }
4745	    state->curve_mode = CURVE_POSITION_ARC_CENTER;
4746	    break;
4747	case 'E':
4748	case 'e':
4749	    TRACE(("found end curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
4750	    if (fragment_len(&optionarg) > 0U) {
4751		TRACE(("DATA_ERROR: ignoring unexpected arguments to curve option '%c' arg \"%s\"\n",
4752		       state->option, fragment_to_tempstr(&optionarg)));
4753	    }
4754
4755	    switch (state->curve_mode) {
4756	    case CURVE_POSITION_CLOSED_CURVE:
4757		{
4758		    unsigned i;
4759
4760#ifdef DEBUG_SPLINE_POINTS
4761		    printf("points: \n");
4762		    for (i = 0; i < state->num_points; i++)
4763			printf("  %d,%d\n",
4764			       state->x_points[i], state->y_points[i]);
4765#endif
4766
4767#ifdef DEBUG_SPLINE_WITH_ROTATION
4768		    {
4769			static unsigned shift = 0;
4770			int temp_x[MAX_CURVE_POINTS], temp_y[MAX_CURVE_POINTS];
4771			shift++;
4772			shift = shift % state->num_points;
4773			for (i = 0; i < state->num_points; i++) {
4774			    temp_x[i] = state->x_points[i];
4775			    temp_y[i] = state->y_points[i];
4776			}
4777			for (i = 0; i < state->num_points; i++) {
4778			    state->x_points[i] =
4779				temp_x[(i + shift) % state->num_points];
4780			    state->y_points[i] =
4781				temp_y[(i + shift) % state->num_points];
4782			}
4783
4784#ifdef DEBUG_SPLINE_POINTS
4785			printf("after shift %d: \n", shift);
4786			for (i = 0; i < state->num_points; i++)
4787			    printf("  %d,%d\n",
4788				   state->x_points[i], state->y_points[i]);
4789#endif
4790		    }
4791#endif
4792
4793		    for (i = state->num_points; i > 0; i--) {
4794			state->x_points[i] = state->x_points[i - 1];
4795			state->y_points[i] = state->y_points[i - 1];
4796		    }
4797		    state->x_points[0] = state->x_points[state->num_points];
4798		    state->y_points[0] = state->y_points[state->num_points];
4799		    state->num_points++;
4800		    for (i = state->num_points; i != 0; i--) {
4801			state->x_points[i] = state->x_points[i - 1];
4802			state->y_points[i] = state->y_points[i - 1];
4803		    }
4804		    state->x_points[0] = state->x_points[state->num_points - 1];
4805		    state->y_points[0] = state->y_points[state->num_points - 1];
4806		    state->num_points++;
4807		    state->x_points[state->num_points] = state->x_points[2];
4808		    state->y_points[state->num_points] = state->y_points[2];
4809		    state->num_points++;
4810#ifdef DEBUG_SPLINE_WITH_OVERDRAW
4811		    state->x_points[state->num_points] = state->x_points[3];
4812		    state->y_points[state->num_points] = state->y_points[3];
4813		    state->num_points++;
4814		    state->x_points[state->num_points] = state->x_points[4];
4815		    state->y_points[state->num_points] = state->y_points[4];
4816		    state->num_points++;
4817#endif
4818#ifdef DEBUG_SPLINE_POINTS
4819		    printf("after points added: \n");
4820		    for (i = 0; i < state->num_points; i++)
4821			printf("  %d,%d\n",
4822			       state->x_points[i], state->y_points[i]);
4823#endif
4824		}
4825
4826		TRACE(("drawing closed spline\n"));
4827		TRACE(("output location was: %d,%d\n",
4828		       context->graphics_output_cursor_x,
4829		       context->graphics_output_cursor_y));
4830		global_context = context;	/* FIXME: remove after updating spline code */
4831		plotCubicSpline((int) state->num_points - 1,
4832				state->x_points, state->y_points,
4833				1);
4834		TRACE(("output location now: %d,%d\n",
4835		       context->graphics_output_cursor_x,
4836		       context->graphics_output_cursor_y));
4837		TRACE(("output location finally: %d,%d\n",
4838		       context->graphics_output_cursor_x,
4839		       context->graphics_output_cursor_y));
4840		state->num_points = 0U;
4841		break;
4842	    case CURVE_POSITION_OPEN_CURVE:
4843#ifdef DEBUG_SPLINE_POINTS
4844		{
4845		    unsigned i;
4846
4847		    printf("points: \n");
4848		    for (i = 0U; i < state->num_points; i++)
4849			printf("  %d,%d\n",
4850			       state->x_points[i], state->y_points[i]);
4851		}
4852#endif
4853		TRACE(("drawing open spline\n"));
4854		TRACE(("output location was: %d,%d\n",
4855		       context->graphics_output_cursor_x,
4856		       context->graphics_output_cursor_y));
4857		global_context = context;	/* FIXME: remove after updating spline code */
4858		plotCubicSpline((int) state->num_points - 1,
4859				state->x_points, state->y_points,
4860				1);
4861		TRACE(("output location now: %d,%d\n",
4862		       context->graphics_output_cursor_x,
4863		       context->graphics_output_cursor_y));
4864
4865		context->graphics_output_cursor_x =
4866		    state->x_points[state->num_points - 1];
4867		context->graphics_output_cursor_y =
4868		    state->y_points[state->num_points - 1];
4869		TRACE(("output location finally: %d,%d\n",
4870		       context->graphics_output_cursor_x,
4871		       context->graphics_output_cursor_y));
4872		state->num_points = 0U;
4873		break;
4874	    default:
4875		TRACE(("DATA_ERROR: end curve option unexpected \"%s\"\n",
4876		       fragment_to_tempstr(&optionarg)));
4877		break;
4878	    }
4879	    break;
4880	case 'S':
4881	case 's':
4882	    TRACE(("begin open curve \"%s\"\n",
4883		   fragment_to_tempstr(&optionarg)));
4884	    if (fragment_len(&optionarg)) {
4885		TRACE(("DATA_ERROR: invalid open curve option \"%s\"\n",
4886		       fragment_to_tempstr(&optionarg)));
4887		break;
4888	    }
4889	    state->curve_mode = CURVE_POSITION_OPEN_CURVE;
4890	    state->num_points = 0U;
4891	    state->x_points[state->num_points] =
4892		context->graphics_output_cursor_x;
4893	    state->y_points[state->num_points] =
4894		context->graphics_output_cursor_y;
4895	    state->num_points++;
4896	    TRACE(("first point on curve with location %d,%d\n",
4897		   context->graphics_output_cursor_x,
4898		   context->graphics_output_cursor_y));
4899	    break;
4900	case 'W':
4901	case 'w':
4902	    TRACE(("found temporary write options \"%s\"\n",
4903		   fragment_to_tempstr(&optionarg)));
4904	    if (!load_regis_write_control_set(state, context,
4905					      context->graphics_output_cursor_x,
4906					      context->graphics_output_cursor_y,
4907					      &optionarg,
4908					      &context->temporary_write_controls)) {
4909		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
4910		       fragment_to_tempstr(&optionarg)));
4911		break;
4912	    }
4913	    break;
4914	default:
4915	    TRACE(("DATA_ERROR: ignoring unknown ReGIS curve command option '%c' arg \"%s\"\n",
4916		   state->option, fragment_to_tempstr(&optionarg)));
4917	    break;
4918	}
4919	break;
4920    case 'f':
4921	TRACE(("ERROR: fill commands should not be handled here\n"));
4922	break;
4923    case 'l':
4924	TRACE(("inspecting load option \"%c\" with value \"%s\"\n",
4925	       state->option, fragment_to_tempstr(&optionarg)));
4926	switch (state->option) {
4927	case 'A':
4928	case 'a':
4929	    TRACE(("found alphabet specifier option \"%s\"\n",
4930		   fragment_to_tempstr(&optionarg)));
4931	    for (;;) {
4932		RegisDataFragment alphabetarg;
4933
4934		if (extract_regis_num(&optionarg, &alphabetarg)) {
4935		    int alphabet;
4936
4937		    TRACE(("alphabet number: %s\n",
4938			   fragment_to_tempstr(&alphabetarg)));
4939		    if (!regis_num_to_int(&alphabetarg, &alphabet)) {
4940			TRACE(("DATA_ERROR: unable to parse int in load alphabet option: \"%s\"\n",
4941			       fragment_to_tempstr(&alphabetarg)));
4942			break;
4943		    }
4944		    if (alphabet < 0 ||
4945			(unsigned) alphabet >= MAX_REGIS_ALPHABETS) {
4946			TRACE(("DATA_ERROR: invalid alphabet: \"%d\"\n",
4947			       alphabet));
4948			break;
4949		    }
4950#ifndef ENABLE_UPLOAD_ALPHABET_ZERO
4951		    if (alphabet == 0) {
4952			TRACE(("DATA_ERROR: alphabet 0 can not be modified\n"));
4953			break;
4954		    }
4955#endif
4956
4957		    TRACE(("using alphabet: %d\n", alphabet));
4958		    state->load_alphabet = (unsigned) alphabet;
4959		} else if (extract_regis_string(&optionarg, state->temp,
4960						state->templen)) {
4961		    TRACE(("alphabet name: %s\n",
4962			   fragment_to_tempstr(&alphabetarg)));
4963		    if (strlen(state->temp) == 0U ||
4964			strlen(state->temp) >= REGIS_ALPHABET_NAME_LEN) {
4965			TRACE(("DATA_ERROR: alphabet names must be between 1 and %u characters long: \"%s\" FIXME\n",
4966			       REGIS_ALPHABET_NAME_LEN - 1U, state->temp));
4967			break;
4968		    }
4969
4970		    strcpy(state->load_name, state->temp);
4971		    TRACE(("using name for alphabet %u: %s\n",
4972			   state->load_alphabet, state->load_name));
4973		} else if (skip_regis_whitespace(&optionarg)) {
4974		    ;
4975		} else if (!fragment_len(&optionarg)) {
4976		    break;
4977		} else {
4978		    TRACE(("DATA_ERROR: expected int or string in load alphabet option: \"%s\"\n",
4979			   fragment_to_tempstr(&optionarg)));
4980		    break;
4981		}
4982	    }
4983	    break;
4984#ifdef ENABLE_UPLOAD_ALPHABET_FROM_FONT
4985	case 'F':
4986	case 'f':
4987	    TRACE(("found font option \"%s\"\n",
4988		   fragment_to_tempstr(&optionarg)));
4989
4990	    if (state->load_index == MAX_REGIS_ALPHABETS) {
4991		state->load_index = find_free_alphabet_index(context,
4992							     state->load_alphabet,
4993							     state->load_w,
4994							     state->load_h);
4995		TRACE(("current alphabet is %u and size is %ux%u; assigning alphabet index %u\n",
4996		       state->load_alphabet, state->load_w, state->load_h, state->load_index));
4997	    }
4998
4999	    for (;;) {
5000		RegisDataFragment fontarg;
5001
5002		if (skip_regis_whitespace(&optionarg))
5003		    continue;
5004		if (extract_regis_num(&optionarg, &fontarg)) {
5005		    int enabled;
5006
5007		    TRACE(("fontname enabled: %s\n", fragment_to_tempstr(&fontarg)));
5008		    if (!regis_num_to_int(&fontarg, &enabled)) {
5009			TRACE(("DATA_ERROR: unable to parse int in load fontname option: \"%s\"\n",
5010			       fragment_to_tempstr(&fontarg)));
5011			break;
5012		    }
5013		    if (enabled != 0U && enabled != 1U) {
5014			TRACE(("DATA_ERROR: invalid fontname enable state: \"%d\"\n", enabled));
5015			break;
5016		    }
5017
5018		    TRACE(("fontname enabled: %d\n", enabled));
5019		    context->alphabets[state->load_index].use_font = enabled;
5020		    continue;
5021		}
5022		if (extract_regis_string(&optionarg, state->temp,
5023					 state->templen)) {
5024		    if (strlen(state->temp) == 0U ||
5025			strlen(state->temp) >= REGIS_FONTNAME_LEN) {
5026			TRACE(("DATA_ERROR: font names must be between 1 and %u characters long: \"%s\"\n",
5027			       REGIS_FONTNAME_LEN - 1U, state->temp));
5028			break;
5029		    }
5030
5031		    strcpy(context->alphabets[state->load_index].fontname,
5032			   state->temp);
5033		    context->alphabets[state->load_index].use_font = 1;
5034		    TRACE(("using backing font: %s\n",
5035			   context->alphabets[state->load_index].fontname));
5036		}
5037
5038		if (!fragment_len(&optionarg)) {
5039		    break;
5040		} else {
5041		    TRACE(("DATA_ERROR: unexpected text in load fontname option: \"%s\"\n",
5042			   fragment_to_tempstr(&optionarg)));
5043		    break;
5044		}
5045	    }
5046	    break;
5047#endif
5048#ifdef ENABLE_USER_FONT_SIZE
5049	case 'S':
5050	case 's':
5051	    TRACE(("found glyph size option \"%s\"\n",
5052		   fragment_to_tempstr(&optionarg)));
5053	    while (fragment_len(&optionarg) > 0) {
5054		RegisDataFragment sizearg;
5055
5056		if (skip_regis_whitespace(&optionarg))
5057		    continue;
5058
5059		if (extract_regis_extent(&optionarg, &sizearg)) {
5060		    int w, h;
5061		    unsigned size;
5062
5063		    TRACE(("glyph size: %s\n", fragment_to_tempstr(&sizearg)));
5064		    /* FIXME: verify this is in pixels, not user coordinates */
5065		    if (!load_regis_pixel_extent(fragment_to_tempstr(&sizearg),
5066						 0, 0,
5067						 &w, &h)) {
5068			TRACE(("DATA_ERROR: unable to parse extent in glyph size option: \"%s\"\n",
5069			       fragment_to_tempstr(&sizearg)));
5070			break;
5071		    }
5072		    if (w < 1 || h < 1) {
5073			TRACE(("DATA_ERROR: glyph dimensions must not be negative or zero: %dx%d\n",
5074			       w, h));
5075			break;
5076		    }
5077		    size = GLYPH_WIDTH_BYTES((unsigned) w) * (unsigned) h;
5078		    if (size > MAX_REGIS_ALPHABET_BYTES) {
5079			TRACE(("DATA_ERROR: glyph is too large (%u bytes, limit is %u bytes)\n",
5080			       size, MAX_REGIS_ALPHABET_BYTES));
5081			break;
5082		    }
5083
5084		    if (state->load_index != MAX_REGIS_ALPHABETS) {
5085			TRACE(("DATA_ERROR: glyph size can not be changed after any data is loaded\n"));
5086			break;
5087		    }
5088
5089		    TRACE(("using glyph size: %dx%d\n", w, h));
5090		    state->load_w = (unsigned) w;
5091		    state->load_h = (unsigned) h;
5092		    continue;
5093		}
5094
5095		TRACE(("DATA_ERROR: expected extent in glyph size option: \"%s\"\n",
5096		       fragment_to_tempstr(&sizearg)));
5097		break;
5098	    }
5099	    break;
5100#endif
5101	default:
5102	    TRACE(("DATA_ERROR: ignoring unknown ReGIS load command option '%c' arg \"%s\"\n",
5103		   state->option, fragment_to_tempstr(&optionarg)));
5104	    break;
5105	}
5106	break;
5107    case 'p':
5108	TRACE(("inspecting position option \"%c\" with value \"%s\"\n",
5109	       state->option, fragment_to_tempstr(&optionarg)));
5110	switch (state->option) {
5111	case 'B':
5112	case 'b':
5113	    TRACE(("found begin bounded position stack \"%s\"\n",
5114		   fragment_to_tempstr(&optionarg)));
5115	    skip_regis_whitespace(&optionarg);
5116	    if (fragment_len(&optionarg) > 0U) {
5117		TRACE(("DATA_ERROR: ignoring unexpected arguments to position option '%c' arg \"%s\"\n",
5118		       state->option, fragment_to_tempstr(&optionarg)));
5119	    }
5120	    if (state->stack_next >= POSITION_STACK_SIZE) {
5121		/* FIXME: ignore, error, update counter? */
5122		TRACE(("unable to push position to full stack\n"));
5123		break;
5124	    }
5125	    TRACE(("pushing location: %d,%d\n",
5126		   context->graphics_output_cursor_x,
5127		   context->graphics_output_cursor_y));
5128
5129	    state->stack_x[state->stack_next] =
5130		context->graphics_output_cursor_x;
5131	    state->stack_y[state->stack_next] =
5132		context->graphics_output_cursor_y;
5133	    state->stack_next++;
5134	    break;
5135	case 'E':
5136	case 'e':
5137	    TRACE(("found end position stack \"%s\"\n",
5138		   fragment_to_tempstr(&optionarg)));
5139	    skip_regis_whitespace(&optionarg);
5140	    if (fragment_len(&optionarg) > 0U) {
5141		TRACE(("DATA_ERROR: ignoring unexpected arguments to position option '%c' arg \"%s\"\n",
5142		       state->option, fragment_to_tempstr(&optionarg)));
5143	    }
5144	    if (state->stack_next == 0U) {
5145		TRACE(("DATA_ERROR: unable to pop position from empty stack\n"));
5146		break;
5147	    }
5148
5149	    state->stack_next--;
5150	    if (state->stack_x[state->stack_next] != DUMMY_STACK_X ||
5151		state->stack_y[state->stack_next] != DUMMY_STACK_Y) {
5152		context->graphics_output_cursor_x =
5153		    state->stack_x[state->stack_next];
5154		context->graphics_output_cursor_y =
5155		    state->stack_y[state->stack_next];
5156		TRACE(("popped location: %d,%d\n",
5157		       context->graphics_output_cursor_x,
5158		       context->graphics_output_cursor_y));
5159	    } else {
5160		TRACE(("not popping location\n"));
5161	    }
5162	    break;
5163	case 'P':
5164	case 'p':
5165	    TRACE(("found graphics page destination option \"%s\"\n",
5166		   fragment_to_tempstr(&optionarg)));
5167	    {
5168		RegisDataFragment pagearg;
5169		int page;
5170
5171		if (!extract_regis_num(&optionarg, &pagearg)) {
5172		    TRACE(("DATA_ERROR: expected int in page destination option: \"%s\"\n",
5173			   fragment_to_tempstr(&optionarg)));
5174		    break;
5175		}
5176		TRACE(("page option arg: %s\n", fragment_to_tempstr(&pagearg)));
5177		if (!regis_num_to_int(&pagearg, &page)) {
5178		    TRACE(("DATA_ERROR: unable to parse int in page destination option: \"%s\"\n",
5179			   fragment_to_tempstr(&pagearg)));
5180		    break;
5181		}
5182		if (page < 0 || (unsigned) page >= MAX_REGIS_PAGES) {
5183		    TRACE(("DATA_ERROR: invalid page: \"%d\"\n", page));
5184		    break;
5185		}
5186
5187		TRACE(("using destination page number: %d\n", page));
5188		context->destination_page = (unsigned) page;
5189		map_regis_graphics_pages(context->destination_graphic->xw,
5190					 context);
5191	    }
5192	    break;
5193	case 'S':
5194	case 's':
5195	    TRACE(("found begin unbounded position stack \"%s\"\n",
5196		   fragment_to_tempstr(&optionarg)));
5197	    skip_regis_whitespace(&optionarg);
5198	    if (fragment_len(&optionarg) > 0U) {
5199		TRACE(("DATA_ERROR: ignoring unexpected arguments to end position option '%c' arg \"%s\"\n",
5200		       state->option, fragment_to_tempstr(&optionarg)));
5201	    }
5202	    if (state->stack_next >= POSITION_STACK_SIZE) {
5203		/* FIXME: ignore, error, update counter? */
5204		TRACE(("unable to push dummy position to full stack\n"));
5205		break;
5206	    }
5207
5208	    TRACE(("pushing dummy positions instead of %d,%d\n",
5209		   context->graphics_output_cursor_x,
5210		   context->graphics_output_cursor_y));
5211	    state->stack_x[state->stack_next] = DUMMY_STACK_X;
5212	    state->stack_y[state->stack_next] = DUMMY_STACK_Y;
5213	    state->stack_next++;
5214	    break;
5215	case 'W':
5216	case 'w':
5217	    TRACE(("found temporary write options \"%s\"\n",
5218		   fragment_to_tempstr(&optionarg)));
5219	    if (!load_regis_write_control_set(state, context,
5220					      context->graphics_output_cursor_x,
5221					      context->graphics_output_cursor_y,
5222					      &optionarg,
5223					      &context->temporary_write_controls)) {
5224		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
5225		       fragment_to_tempstr(&optionarg)));
5226	    }
5227	    break;
5228	default:
5229	    TRACE(("DATA_ERROR: ignoring unknown ReGIS position command option '%c' arg \"%s\"\n",
5230		   state->option, fragment_to_tempstr(&optionarg)));
5231	    break;
5232	}
5233	break;
5234    case 'r':
5235	TRACE(("inspecting report option \"%c\" with value \"%s\"\n",
5236	       state->option, fragment_to_tempstr(&optionarg)));
5237	switch (state->option) {
5238	case 'E':
5239	case 'e':
5240	    TRACE(("found parse error report \"%s\" FIXME\n",
5241		   fragment_to_tempstr(&optionarg)));
5242	    skip_regis_whitespace(&optionarg);
5243	    if (fragment_len(&optionarg) > 0U) {
5244		TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
5245		       state->option, fragment_to_tempstr(&optionarg)));
5246		break;
5247	    } {
5248		char reply[64];
5249
5250		TRACE(("got report last error condition\n"));
5251		/* FIXME: verify no CSI */
5252		/* FIXME: implement after adding error tracking */
5253		sprintf(reply, "\"%u, %u\"\r", 0U, 0U);
5254		unparseputs(context->display_graphic->xw, reply);
5255		unparse_end(context->display_graphic->xw);
5256	    }
5257	    break;
5258	case 'I':
5259	case 'i':
5260	    TRACE(("found set input mode \"%s\"\n",
5261		   fragment_to_tempstr(&optionarg)));
5262	    {
5263		RegisDataFragment modearg;
5264		int mode;
5265
5266		if (!extract_regis_num(&optionarg, &modearg)) {
5267		    TRACE(("DATA_ERROR: expected int in report input mode option: \"%s\"\n",
5268			   fragment_to_tempstr(&modearg)));
5269		    break;
5270		}
5271
5272		TRACE(("input mode: %s\n", fragment_to_tempstr(&modearg)));
5273		if (!regis_num_to_int(&modearg, &mode)) {
5274		    TRACE(("DATA_ERROR: unable to parse int in report input mode option: \"%s\"\n",
5275			   fragment_to_tempstr(&modearg)));
5276		    break;
5277		}
5278		if (mode != 0 && mode != 1) {
5279		    TRACE(("DATA_ERROR: ignoring invalid input mode: \"%d\"\n",
5280			   mode));
5281		    break;
5282		}
5283
5284		TRACE(("using input mode: %d (%s)\n", mode,
5285		       (mode == 0)
5286		       ? "one-shot"
5287		       : "multiple"));
5288		context->multi_input_mode = mode;
5289		if (context->multi_input_mode) {
5290		    TRACE(("ERROR: multi-mode input not implemented FIXME\n"));
5291		    /* FIXME: enable input cursor, send location on mouse clicks or non-arrowkey keypresses */
5292		} else {
5293		    /* FIXME: if in multi-mode, disable input cursor, stop tracking mouse clicks and keypresses */
5294		    /* FIXME: enable input cursor and disable drawing until location report request command is received */
5295		    /* FIXME: upon mouse click or keypress respond with location report */
5296		}
5297		/* FIXME: implement input cursor */
5298		/* FIXME: implement mouse tracking */
5299		/* FIXME: implement arrow key movement */
5300		/* FIXME: implement button/key collection */
5301
5302		unparseputs(context->display_graphic->xw, "\r");
5303		unparse_end(context->display_graphic->xw);
5304
5305		skip_regis_whitespace(&optionarg);
5306		if (fragment_len(&optionarg) > 0U) {
5307		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
5308			   state->option, fragment_to_tempstr(&optionarg)));
5309		}
5310	    }
5311	    break;
5312	case 'L':
5313	case 'l':
5314	    TRACE(("found character set load report \"%s\"\n",
5315		   fragment_to_tempstr(&optionarg)));
5316	    if (fragment_len(&optionarg) > 0U) {
5317		TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
5318		       state->option, fragment_to_tempstr(&optionarg)));
5319		break;
5320	    }
5321	    if (state->load_index == MAX_REGIS_ALPHABETS) {
5322		TRACE(("DATA_ERROR: unable to report alphabet name because no alphabet is loading\n"));
5323		/* FIXME: how should errors be handled? */
5324		unparseputs(context->display_graphic->xw, "\033A''\r");
5325		unparse_end(context->display_graphic->xw);
5326		break;
5327	    }
5328
5329	    /* FIXME: also send CSI here? */
5330	    unparseputs(context->display_graphic->xw, "\033A'");
5331	    unparseputs(context->display_graphic->xw,
5332			context->alphabets[state->load_index].name);
5333	    unparseputs(context->display_graphic->xw, "'\r");
5334	    unparse_end(context->display_graphic->xw);
5335	    break;
5336	case 'M':
5337	case 'm':
5338	    TRACE(("found macrograph report \"%s\"\n",
5339		   fragment_to_tempstr(&optionarg)));
5340	    {
5341		RegisDataFragment suboptionarg;
5342		char name = '\0';
5343
5344		if (extract_regis_parenthesized_data(&optionarg,
5345						     &suboptionarg)) {
5346		    skip_regis_whitespace(&suboptionarg);
5347		    TRACE(("got macrograph report character: \"%s\"\n",
5348			   fragment_to_tempstr(&suboptionarg)));
5349		    if (fragment_len(&suboptionarg) > 0U) {
5350			name = pop_fragment(&suboptionarg);
5351
5352			skip_regis_whitespace(&suboptionarg);
5353			if (fragment_len(&optionarg) > 0U) {
5354			    TRACE(("DATA_ERROR: unexpected content in ReGIS macrograph report suboptions: \"%s\"\n",
5355				   fragment_to_tempstr(&suboptionarg)));
5356			    break;
5357			}
5358		    }
5359		}
5360		skip_regis_whitespace(&optionarg);
5361		if (fragment_len(&optionarg) > 0U) {
5362		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
5363			   state->option, fragment_to_tempstr(&optionarg)));
5364		    break;
5365		}
5366
5367		if (name == '\0') {
5368		    TRACE(("DATA_ERROR: no macro name given to ReGIS macrograph report command\n"));
5369		    break;
5370		}
5371
5372		if (name == '=') {
5373		    char reply[64];
5374
5375		    TRACE(("got report macrograph storage\n"));
5376		    /* FIXME: verify no CSI */
5377		    /* FIXME: implement when macrographs are supported */
5378		    sprintf(reply, "\"%u, %u\"\r", 1000U, 1000U);
5379		    unparseputs(context->display_graphic->xw, reply);
5380		    unparse_end(context->display_graphic->xw);
5381		} else {
5382		    TRACE(("got report macrograph name '%c'\n", name));
5383		    /*
5384		     * FIXME: Implement when macrographs are supported (and
5385		     * allow it to be disabled for security reasons).
5386		     */
5387		    /* FIXME: also send CSI here? */
5388		    unparseputs(context->display_graphic->xw, "@;\r");
5389		    unparse_end(context->display_graphic->xw);
5390		}
5391	    }
5392	    break;
5393	case 'P':
5394	case 'p':
5395	    TRACE(("found cursor position report \"%s\"\n",
5396		   fragment_to_tempstr(&optionarg)));
5397	    {
5398		RegisDataFragment suboptionarg;
5399		int output = 1;
5400
5401		if (extract_regis_parenthesized_data(&optionarg,
5402						     &suboptionarg)) {
5403		    skip_regis_whitespace(&suboptionarg);
5404		    TRACE(("got cursor position report suboption: \"%s\"\n",
5405			   fragment_to_tempstr(&suboptionarg)));
5406		    if (fragment_len(&suboptionarg) > 0U) {
5407			char suboption;
5408
5409			suboption = pop_fragment(&suboptionarg);
5410			if (suboption == 'i' || suboption == 'I') {
5411			    output = 0;		/* input location report */
5412			} else {
5413			    TRACE(("DATA_ERROR: unknown ReGIS postion report suboption '%c'\n",
5414				   suboption));
5415			    break;
5416			}
5417
5418			skip_regis_whitespace(&suboptionarg);
5419			if (fragment_len(&optionarg) > 0U) {
5420			    TRACE(("DATA_ERROR: unexpected content in ReGIS postion report suboptions: \"%s\"\n",
5421				   fragment_to_tempstr(&suboptionarg)));
5422			    break;
5423			}
5424		    }
5425		}
5426		skip_regis_whitespace(&optionarg);
5427		if (fragment_len(&optionarg) > 0U) {
5428		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
5429			   state->option, fragment_to_tempstr(&optionarg)));
5430		    break;
5431		}
5432
5433		TRACE(("got report cursor position (output=%d)\n", output));
5434
5435		/* FIXME: look into supporting ANSI locator reports (DECLRP) */
5436		if (output == 1) {
5437		    char reply[64];
5438
5439		    /* FIXME: verify no leading char or button sequence */
5440		    /* FIXME: should we ever send an eight-bit CSI? */
5441		    /* FIXME: verify in absolute, not user, coordinates */
5442		    sprintf(reply, "\033[[%d,%d]\r",
5443			    context->graphics_output_cursor_x,
5444			    context->graphics_output_cursor_y);
5445		    unparseputs(context->display_graphic->xw, reply);
5446		    unparse_end(context->display_graphic->xw);
5447		} else {
5448		    char reply[64];
5449		    int x, y;
5450
5451		    if (context->multi_input_mode) {
5452			/* FIXME: track input coordinates */
5453			x = y = 0;	/* placeholders */
5454
5455			/* send CSI240~[x,y]\r with current input cursor location */
5456
5457			/* FIXME: verify no leading char or button sequence */
5458			/* FIXME: should we ever send an eight-bit CSI? */
5459			/* FIXME: verify in absolute, not user, coordinates */
5460			TRACE(("sending multi-mode input report at %d,%d\n",
5461			       x, y));
5462			sprintf(reply, "\033[[%d,%d]\r", x, y);
5463			unparseputs(context->display_graphic->xw, reply);
5464			unparse_end(context->display_graphic->xw);
5465			break;
5466		    } else {
5467			char ch;
5468
5469			/* FIXME: wait for first non-arrow keypress or mouse click, and don't update graphics while waiting */
5470			ch = ' ';	/* placeholder */
5471			x = y = 0;	/* placeholders */
5472
5473			/* send <key or button>[x,y]\r to report input cursor location */
5474
5475			/* null button: CSI240~ */
5476			/* left button: CSI241~ */
5477			/* middle button: CSI243~ */
5478			/* right button: CSI245~ */
5479			/* extra button: CSI247~ */
5480			/* FIXME: support DECLBD to change button assignments */
5481			/* FIXME: verify no leading char or button sequence */
5482			/* FIXME: should we ever send an eight-bit CSI? */
5483			TRACE(("sending one-shot input report with %c at %d,%d\n",
5484			       ch, x, y));
5485			sprintf(reply, "\033[%c[%d,%d]\r", ch, x, y);
5486			unparseputs(context->display_graphic->xw, reply);
5487			unparse_end(context->display_graphic->xw);
5488			break;
5489		    }
5490		}
5491	    }
5492	    break;
5493	default:
5494	    TRACE(("DATA_ERROR: ignoring unknown ReGIS report command option '%c' arg \"%s\"\n",
5495		   state->option, fragment_to_tempstr(&optionarg)));
5496	    break;
5497	}
5498	break;
5499    case 's':
5500	TRACE(("inspecting screen option \"%c\" with value \"%s\"\n",
5501	       state->option, fragment_to_tempstr(&optionarg)));
5502	switch (state->option) {
5503	case 'A':
5504	case 'a':
5505	    TRACE(("found address definition \"%s\"\n",
5506		   fragment_to_tempstr(&optionarg)));
5507	    {
5508		RegisDataFragment address_extent;
5509		int got_ul = 0;
5510		int got_lr = 0;
5511		int ulx = 0, uly = 0, lrx = 0, lry = 0;
5512
5513		while (fragment_len(&optionarg)) {
5514		    if (skip_regis_whitespace(&optionarg))
5515			continue;
5516
5517		    if (extract_regis_extent(&optionarg, &address_extent)) {
5518			int x, y;
5519
5520			/* FIXME: are relative values supposed to be handled? */
5521			if (!load_regis_pixel_extent(fragment_to_tempstr(&address_extent),
5522						     0, 0, &x, &y)) {
5523			    TRACE(("DATA_ERROR: unable to parse extent in address definition: \"%s\"\n",
5524				   fragment_to_tempstr(&address_extent)));
5525			    break;
5526			}
5527
5528			if (!got_ul) {
5529			    ulx = x;
5530			    uly = y;
5531			    got_ul = 1;
5532			} else if (!got_lr) {
5533			    lrx = x;
5534			    lry = y;
5535			    got_lr = 1;
5536			} else {
5537			    TRACE(("DATA_ERROR: ignoring extra extent argument in address definition: \"%s\"\n",
5538				   fragment_to_tempstr(&address_extent)));
5539			}
5540			continue;
5541		    }
5542
5543		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: expected extent argument but found: \"%s\"\n",
5544			   fragment_to_tempstr(&optionarg)));
5545		    return 1;
5546		}
5547
5548		if (!got_ul || !got_lr) {
5549		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: one or both locations missing in definition\n"));
5550		    return 1;
5551		}
5552		if (ulx == lrx || uly == lry) {
5553		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: one or both dimensions are zero: ul=%d,%d lr=%d,%d\n",
5554			   ulx, uly, lrx, lry));
5555		    return 1;
5556		} {
5557		    const int cw = abs(ulx - lrx) + 1;
5558		    const int ch = abs(uly - lry) + 1;
5559		    int width, height;
5560
5561		    /*
5562		     * FIXME: Should we attempt to resize existing contents?
5563		     * We are actually changing the output size, but terminals
5564		     * just changed coordinates.
5565		     */
5566#if 1
5567		    int scale;
5568		    const int mw = context->destination_graphic->max_width;
5569		    const int mh = context->destination_graphic->max_height;
5570
5571		    TRACE(("custom screen size pre scaling: %dx%d\n", cw, ch));
5572
5573		    width = cw;
5574		    height = ch;
5575
5576		    scale = 1;
5577		    while (width * scale < 200 ||
5578			   height * scale < 200) {
5579			scale++;
5580		    }
5581		    width *= scale;
5582		    height *= scale;
5583
5584		    scale = 1;
5585		    while (width / scale > mw ||
5586			   height / scale > mh) {
5587			scale++;
5588		    }
5589		    width /= scale;
5590		    height /= scale;
5591#else
5592		    width = context->width;
5593		    height = context->height;
5594#endif
5595
5596		    TRACE(("custom screen address: ul=%d,%d lr=%d,%d\n",
5597			   ulx, uly, lrx, lry));
5598
5599		    context->x_off = ulx;
5600		    context->y_off = uly;
5601		    context->x_div = lrx - ulx;
5602		    context->y_div = lry - uly;
5603		    context->width = width;
5604		    context->height = height;
5605		    context->destination_graphic->actual_width = width;
5606		    context->destination_graphic->actual_height = height;
5607		    context->destination_graphic->dirty = 1;
5608
5609		    TRACE(("conversion factors: off=%+d,%+d div=%+d,%+d width=%d, height=%d\n",
5610			   context->x_off, context->y_off,
5611			   context->x_div, context->y_div,
5612			   context->width, context->height));
5613		}
5614	    }
5615	    break;
5616	case 'C':
5617	case 'c':
5618	    TRACE(("found cursor control \"%s\" FIXME\n",
5619		   fragment_to_tempstr(&optionarg)));
5620	    /* FIXME: handle */
5621	    /* C0 == output cursor off, C1 == output cursor on */
5622	    /* C(H0) == output cursor diamond (default), C(H1) == output cursor diamond, C(H2) == output cursor crosshair */
5623	    /* C(I) == input cursor crosshair (default), C(I0) == input cursor crosshair, C(I1) == input cursor diamond, C(I2) == input cursor crosshair, C(I3) == input cursor rubber band line, C(I4) == input cursor rubber band rectangle */
5624	    /* C(I[X,Y]"FB")) == set input cursor to F in foreground B in background with hotspot at X,Y (using current text settings, trimmed to 16x24 max) */
5625	    if (!fragment_len(&optionarg)) {
5626		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen cursor control option value \"%s\"\n",
5627		       fragment_to_tempstr(&optionarg)));
5628		return 1;
5629	    }
5630	    break;
5631	case 'E':
5632	case 'e':
5633	    TRACE(("found erase request \"%s\"\n",
5634		   fragment_to_tempstr(&optionarg)));
5635	    if (fragment_len(&optionarg)) {
5636		TRACE(("DATA_ERROR: ignoring unexpected argument to ReGIS erase request \"%s\"\n",
5637		       fragment_to_tempstr(&optionarg)));
5638		return 1;
5639	    }
5640	    DRAW_ALL(context, context->background);
5641	    context->destination_graphic->dirty = 1;
5642	    context->force_refresh = 1;
5643	    break;
5644	case 'F':
5645	case 'f':
5646	    TRACE(("found page eject request \"%s\"\n",
5647		   fragment_to_tempstr(&optionarg)));
5648	    if (fragment_len(&optionarg)) {
5649		TRACE(("DATA_ERROR: ignoring unexpected argument to ReGIS page eject request \"%s\"\n",
5650		       fragment_to_tempstr(&optionarg)));
5651		return 1;
5652	    }
5653	    /* We aren't going to print anything so no need to deduplicate. */
5654	    DRAW_ALL(context, context->background);
5655	    context->destination_graphic->dirty = 1;
5656	    context->force_refresh = 1;
5657	    break;
5658	case 'H':
5659	case 'h':
5660	    TRACE(("found hardcopy control \"%s\" FIXME\n",
5661		   fragment_to_tempstr(&optionarg)));
5662	    /* FIXME: handle */
5663	    /* screen S(H), area to (input?/output?) cursor S(H[X,Y]), or area S(H[X,Y][X,Y]) */
5664	    if (!fragment_len(&optionarg)) {
5665		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen hardcopy control option value \"%s\"\n",
5666		       fragment_to_tempstr(&optionarg)));
5667		return 1;
5668	    }
5669	    break;
5670	case 'I':
5671	case 'i':
5672	    TRACE(("found screen background color index \"%s\"\n",
5673		   fragment_to_tempstr(&optionarg)));
5674	    if (!load_regis_regnum_or_colorspec(context, &optionarg,
5675						&context->background)) {
5676		TRACE(("DATA_ERROR: screen background color specifier not recognized: \"%s\"\n",
5677		       fragment_to_tempstr(&optionarg)));
5678		return 1;
5679	    }
5680	    break;
5681	case 'M':
5682	case 'm':
5683	    TRACE(("found screen color register mapping \"%s\"\n",
5684		   fragment_to_tempstr(&optionarg)));
5685	    {
5686		RegisDataFragment regnum;
5687		RegisDataFragment colorspec;
5688
5689		while (fragment_len(&optionarg)) {
5690		    if (skip_regis_whitespace(&optionarg))
5691			continue;
5692
5693		    if (extract_regis_num(&optionarg, &regnum)) {
5694			int register_num;
5695			int color_only;
5696			short r, g, b;
5697
5698			if (!regis_num_to_int(&regnum, &register_num)) {
5699			    TRACE(("DATA_ERROR: unable to parse int in screen color register mapping option: \"%s\"\n",
5700				   fragment_to_tempstr(&regnum)));
5701			    return 1;
5702			}
5703			if (register_num < 0 ||
5704			    register_num > (int) context->destination_graphic->valid_registers) {
5705			    TRACE(("interpreting out of range register number %d as 0 FIXME\n",
5706				   register_num));
5707			    register_num = 0;
5708			}
5709			skip_regis_whitespace(&optionarg);
5710			if (!extract_regis_parenthesized_data(&optionarg,
5711							      &colorspec)) {
5712			    TRACE(("DATA_ERROR: expected to find parentheses after register number: \"%s\"\n",
5713				   fragment_to_tempstr(&optionarg)));
5714			    return 1;
5715			}
5716
5717			skip_regis_whitespace(&colorspec);
5718			switch (peek_fragment(&colorspec)) {
5719			case 'A':
5720			case 'a':
5721			    pop_fragment(&colorspec);
5722			    color_only = 1;
5723			    break;
5724			default:
5725			    color_only = 0;
5726			    break;
5727			}
5728
5729			TRACE(("mapping register %d to color spec: \"%s\"\n",
5730			       register_num, fragment_to_tempstr(&colorspec)));
5731			if (!load_regis_colorspec(context, &colorspec,
5732						  &r, &g, &b)) {
5733			    TRACE(("DATA_ERROR: unable to use colorspec for mapping of register %d\n",
5734				   register_num));
5735			    return 1;
5736			}
5737
5738			if (color_only &&
5739			    (context->terminal_id == 240 ||
5740			     context->terminal_id == 330)) {
5741			    TRACE(("NOT setting color register %d to %hd,%hd,%hd\n",
5742				   register_num, r, g, b));
5743			} else {
5744			    TRACE(("setting color register %d to %hd,%hd,%hd\n",
5745				   register_num, r, g, b));
5746			    update_color_register(context->destination_graphic,
5747						  (RegisterNum) register_num,
5748						  r, g, b);
5749			}
5750			continue;
5751		    } {
5752			char skip;
5753
5754			skip_regis_whitespace(&optionarg);
5755			skip = pop_fragment(&optionarg);
5756			(void) skip;	/* variable needed only if tracing */
5757			TRACE(("DATA_ERROR: ignoring mapping request with unexpected character \"%c\"\n",
5758			       skip));
5759			return 1;
5760		    }
5761		}
5762	    }
5763	    break;
5764	case 'P':
5765	case 'p':
5766	    TRACE(("found graphics page display option \"%s\"\n",
5767		   fragment_to_tempstr(&optionarg)));
5768	    {
5769		RegisDataFragment pagearg;
5770		int page;
5771
5772		if (!extract_regis_num(&optionarg, &pagearg)) {
5773		    TRACE(("DATA_ERROR: expected int in page display option: \"%s\"\n",
5774			   fragment_to_tempstr(&optionarg)));
5775		    break;
5776		}
5777		TRACE(("page option arg: %s\n", fragment_to_tempstr(&pagearg)));
5778		if (!regis_num_to_int(&pagearg, &page)) {
5779		    TRACE(("DATA_ERROR: unable to parse int in page display option: \"%s\"\n",
5780			   fragment_to_tempstr(&pagearg)));
5781		    break;
5782		}
5783		if (page < 0 || (unsigned) page >= MAX_REGIS_PAGES) {
5784		    TRACE(("DATA_ERROR: invalid page: \"%d\"\n", page));
5785		    break;
5786		}
5787
5788		TRACE(("using display page number: %d\n", page));
5789		context->display_page = (unsigned) page;
5790		map_regis_graphics_pages(context->display_graphic->xw, context);
5791	    }
5792	    break;
5793	case 'T':
5794	case 't':
5795	    TRACE(("found time delay \"%s\" FIXME\n",
5796		   fragment_to_tempstr(&optionarg)));
5797	    /* FIXME: handle (maybe -- might not be worth it, plus it is an easy DoS vector) */
5798	    if (!fragment_len(&optionarg)) {
5799		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen time delay option value \"%s\"\n",
5800		       fragment_to_tempstr(&optionarg)));
5801		return 1;
5802	    }
5803	    break;
5804	case 'W':
5805	case 'w':
5806	    /* Only M (pixel vector multiplier) is useful -- for the scrolling multiplier. */
5807	    TRACE(("found temporary write options \"%s\"\n",
5808		   fragment_to_tempstr(&optionarg)));
5809	    if (!load_regis_write_control_set(state, context,
5810					      context->graphics_output_cursor_x,
5811					      context->graphics_output_cursor_y,
5812					      &optionarg,
5813					      &context->temporary_write_controls)) {
5814		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
5815		       fragment_to_tempstr(&optionarg)));
5816		break;
5817	    }
5818	    break;
5819	default:
5820	    TRACE(("DATA_ERROR: ignoring unknown ReGIS screen command option '%c' arg \"%s\"\n",
5821		   state->option, fragment_to_tempstr(&optionarg)));
5822	    break;
5823	}
5824	break;
5825    case 't':
5826	TRACE(("inspecting text option \"%c\" with value \"%s\"\n",
5827	       state->option, fragment_to_tempstr(&optionarg)));
5828	if (!fragment_len(&optionarg)) {
5829	    TRACE(("DATA_ERROR: ignoring malformed ReGIS text command option value \"%s\"\n",
5830		   fragment_to_tempstr(&optionarg)));
5831	    return 1;
5832	}
5833	switch (state->option) {
5834	case 'A':
5835	case 'a':
5836	    TRACE(("found alphabet specifier option \"%s\"\n",
5837		   fragment_to_tempstr(&optionarg)));
5838	    {
5839		RegisDataFragment alphabetarg;
5840		int alphabet;
5841
5842		if (!extract_regis_num(&optionarg, &alphabetarg)) {
5843		    TRACE(("DATA_ERROR: expected int in text alphabet option: \"%s\"\n",
5844			   fragment_to_tempstr(&optionarg)));
5845		    break;
5846		}
5847		TRACE(("alphabet: %s\n", fragment_to_tempstr(&alphabetarg)));
5848		if (!regis_num_to_int(&alphabetarg, &alphabet)) {
5849		    TRACE(("DATA_ERROR: unable to parse int in text alphabet option: \"%s\"\n",
5850			   fragment_to_tempstr(&alphabetarg)));
5851		    break;
5852		}
5853		if (alphabet < 0 ||
5854		    (unsigned) alphabet >= MAX_REGIS_ALPHABETS) {
5855		    TRACE(("DATA_ERROR: invalid alphabet: \"%d\"\n", alphabet));
5856		    break;
5857		}
5858
5859		TRACE(("using alphabet number: %d\n", alphabet));
5860		context->current_text_controls->alphabet_num = (unsigned) alphabet;
5861
5862		skip_regis_whitespace(&optionarg);
5863		if (fragment_len(&optionarg)) {
5864		    TRACE(("DATA_ERROR: ignoring trailing junk in text alphabet option \"%s\"\n",
5865			   fragment_to_tempstr(&alphabetarg)));
5866		    break;
5867		}
5868	    }
5869	    break;
5870	case 'B':
5871	case 'b':
5872	    TRACE(("found beginning of temporary text control \"%s\"\n",
5873		   fragment_to_tempstr(&optionarg)));
5874	    copy_regis_text_controls(&context->persistent_text_controls,
5875				     &context->temporary_text_controls);
5876	    context->current_text_controls = &context->temporary_text_controls;
5877	    break;
5878	case 'D':
5879	case 'd':
5880	    TRACE(("found text tilt control \"%s\"\n",
5881		   fragment_to_tempstr(&optionarg)));
5882	    {
5883		RegisDataFragment rotationarg;
5884		int rotation;
5885
5886		if (!extract_regis_num(&optionarg, &rotationarg)) {
5887		    TRACE(("DATA_ERROR: expected int in text tilt option: \"%s\"\n",
5888			   fragment_to_tempstr(&optionarg)));
5889		    break;
5890		}
5891		TRACE(("tilt: %s\n", fragment_to_tempstr(&rotationarg)));
5892		if (!regis_num_to_int(&rotationarg, &rotation)) {
5893		    TRACE(("DATA_ERROR: unable to parse int in text tilt option: \"%s\"\n",
5894			   fragment_to_tempstr(&rotationarg)));
5895		    break;
5896		}
5897		while (rotation < 0) {
5898		    rotation += 360;
5899		}
5900		while (rotation >= 360) {
5901		    rotation -= 360;
5902		}
5903		/* FIXME: we don't have to be this restrictive, though the
5904		 * VT3x0 apparently was. What might depend on this?
5905		 */
5906#ifndef ENABLE_FREE_ROTATION
5907		/* Use closest value which is a multiple of 45 degrees. */
5908		rotation = 45 * ((rotation + 22) / 45);
5909#endif
5910
5911		/* For some reason ReGIS reused the "D" option for the text
5912		 * command to represent two different attributes.  Character
5913		 * tilt can only be modified if a string tilt option has
5914		 * already been given.
5915		 */
5916		/* FIXME: handle character size prameter */
5917		if (state->string_rot_set) {
5918		    TRACE(("using character rotation (tilt): %d\n", rotation));
5919		    context->current_text_controls->character_rotation =
5920			rotation;
5921		} else {
5922		    TRACE(("using string rotation (tilt): %d\n", rotation));
5923		    context->current_text_controls->string_rotation =
5924			rotation;
5925		    context->current_text_controls->character_rotation =
5926			rotation;
5927		    state->string_rot_set = 1;
5928		}
5929
5930		skip_regis_whitespace(&optionarg);
5931		if (fragment_len(&optionarg)) {
5932		    TRACE(("DATA_ERROR: ignoring trailing junk in text tilt option \"%s\"\n",
5933			   fragment_to_tempstr(&rotationarg)));
5934		    break;
5935		}
5936	    }
5937	    break;
5938	case 'E':
5939	case 'e':
5940	    TRACE(("found end of temporary text control \"%s\"\n",
5941		   fragment_to_tempstr(&optionarg)));
5942	    context->current_text_controls = &context->persistent_text_controls;
5943	    break;
5944	case 'H':
5945	case 'h':
5946	    TRACE(("found height multiplier \"%s\"\n",
5947		   fragment_to_tempstr(&optionarg)));
5948	    {
5949		RegisDataFragment multiarg;
5950		int multiplier;
5951		unsigned height;
5952
5953		if (!extract_regis_num(&optionarg, &multiarg)) {
5954		    TRACE(("DATA_ERROR: expected int in text height multiplier option: \"%s\"\n",
5955			   fragment_to_tempstr(&optionarg)));
5956		    break;
5957		}
5958		TRACE(("multiplier: %s\n", fragment_to_tempstr(&multiarg)));
5959		if (!regis_num_to_int(&multiarg, &multiplier)) {
5960		    TRACE(("DATA_ERROR: unable to parse int in text height multiplier option: \"%s\"\n",
5961			   fragment_to_tempstr(&multiarg)));
5962		    break;
5963		}
5964		if (multiplier < 0) {
5965		    TRACE(("DATA_ERROR: out of range height multiplier: \"%d\", using 0 FIXME\n",
5966			   multiplier));
5967		    multiplier = 0;	/* FIXME: verify zero is accepted */
5968		}
5969		if (multiplier > 256) {
5970		    TRACE(("DATA_ERROR: out of range height multiplier: \"%d\", using 256 FIXME\n",
5971			   multiplier));
5972		    multiplier = 256;
5973		}
5974		TRACE(("using height multiplier: %d\n", multiplier));
5975		height = (unsigned) multiplier *10U;	/* base character height */
5976		context->current_text_controls->character_display_h = height;
5977		context->current_text_controls->character_unit_cell_h = height;
5978
5979		skip_regis_whitespace(&optionarg);
5980		if (fragment_len(&optionarg)) {
5981		    TRACE(("DATA_ERROR: ignoring trailing junk in text multiplier option \"%s\"\n",
5982			   fragment_to_tempstr(&multiarg)));
5983		    break;
5984		}
5985	    }
5986	    break;
5987	case 'I':
5988	case 'i':
5989	    TRACE(("found italic control \"%s\"\n",
5990		   fragment_to_tempstr(&optionarg)));
5991	    {
5992		RegisDataFragment italicarg;
5993		int italic;
5994
5995		if (!extract_regis_num(&optionarg, &italicarg)) {
5996		    TRACE(("DATA_ERROR: expected int in text italic option: \"%s\"\n",
5997			   fragment_to_tempstr(&optionarg)));
5998		    break;
5999		}
6000		TRACE(("italic angle: %s\n", fragment_to_tempstr(&italicarg)));
6001		if (!regis_num_to_int(&italicarg, &italic)) {
6002		    TRACE(("DATA_ERROR: unable to parse int in text italic option: \"%s\"\n",
6003			   fragment_to_tempstr(&italicarg)));
6004		    break;
6005		}
6006
6007		/*
6008		 * This is overly-restrictive but matches what the docs say
6009		 * should happen.  Add an option to allow exact angles?
6010		 */
6011#ifndef ENABLE_VARIABLE_ITALICS
6012		if (italic <= -31) {
6013		    italic = -45;
6014		} else if (italic < 0) {
6015		    italic = -27;	/* docs say 22, but that gives .404 x:y ratio */
6016		} else if (italic >= 31) {
6017		    italic = 45;
6018		} else if (italic > 0) {
6019		    italic = 27;	/* docs say 22, but that gives .404 x:y ratio */
6020		}
6021#else
6022		if (italic <= -72) {
6023		    italic = -72;
6024		} else if (italic >= 72) {
6025		    italic = 72;
6026		}
6027#endif
6028
6029		TRACE(("using italic angle: %d\n", italic));
6030		context->current_text_controls->slant = italic;
6031
6032		skip_regis_whitespace(&optionarg);
6033		if (fragment_len(&optionarg)) {
6034		    TRACE(("DATA_ERROR: ignoring trailing junk in text italic option \"%s\"\n",
6035			   fragment_to_tempstr(&italicarg)));
6036		    break;
6037		}
6038	    }
6039	    break;
6040	case 'M':
6041	case 'm':
6042	    TRACE(("found text command size multiplier \"%s\"\n",
6043		   fragment_to_tempstr(&optionarg)));
6044	    {
6045		RegisDataFragment sizemultiplierarg;
6046		int sizemultiplier;
6047		int ww, hh;
6048
6049		if (!extract_regis_extent(&optionarg, &sizemultiplierarg)) {
6050		    TRACE(("DATA_ERROR: expected extent in size multiplier option: \"%s\"\n",
6051			   fragment_to_tempstr(&optionarg)));
6052		    break;
6053		}
6054		TRACE(("size multiplier: %s\n",
6055		       fragment_to_tempstr(&sizemultiplierarg)));
6056		/* FIXME: verify this is in pixels, not user coordinates */
6057		if (!load_regis_pixel_extent(fragment_to_tempstr(&sizemultiplierarg),
6058					     0, 0, &ww, &hh)) {
6059		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6060			   state->option, fragment_to_tempstr(&sizemultiplierarg)));
6061		    break;
6062		}
6063		if (!regis_num_to_int(&sizemultiplierarg, &sizemultiplier)) {
6064		    TRACE(("DATA_ERROR: unable to parse extent in size multiplier option: \"%s\"\n",
6065			   fragment_to_tempstr(&sizemultiplierarg)));
6066		    break;
6067		}
6068		if (ww < 1 || hh < 1) {
6069		    TRACE(("DATA_ERROR: invalid size multiplier: %d,%d FIXME\n",
6070			   ww, hh));
6071		    break;
6072		}
6073		if (ww > 16) {
6074		    ww = 16;
6075		}
6076		if (hh > 16) {
6077		    hh = 16;
6078		}
6079
6080		TRACE(("using size multiplier: %d,%d\n", ww, hh));
6081
6082		/* times the S1 character unit cell dimensions */
6083		context->current_text_controls->character_unit_cell_w =
6084		    (unsigned) ww *8U;
6085		context->current_text_controls->character_unit_cell_h =
6086		    (unsigned) hh *20U;
6087
6088		skip_regis_whitespace(&optionarg);
6089		if (fragment_len(&optionarg)) {
6090		    TRACE(("DATA_ERROR: ignoring trailing junk in text unit cell size option \"%s\"\n",
6091			   fragment_to_tempstr(&sizemultiplierarg)));
6092		    break;
6093		}
6094	    }
6095	    break;
6096	case 'S':
6097	case 's':
6098	    TRACE(("found display size or standard character cell size \"%s\"\n",
6099		   fragment_to_tempstr(&optionarg)));
6100	    for (;;) {
6101		RegisDataFragment displaysizearg;
6102
6103		skip_regis_whitespace(&optionarg);
6104		if (extract_regis_extent(&optionarg, &displaysizearg)) {
6105		    int disp_w, disp_h;
6106
6107		    TRACE(("custom display size: %s\n",
6108			   fragment_to_tempstr(&displaysizearg)));
6109		    /* FIXME: verify this is in pixels, not user coordinates */
6110		    if (!load_regis_pixel_extent(fragment_to_tempstr(&displaysizearg),
6111						 0, 0, &disp_w, &disp_h)) {
6112			TRACE(("DATA_ERROR: unable to parse extent in text display size option: \"%s\"\n",
6113			       fragment_to_tempstr(&displaysizearg)));
6114			break;
6115		    }
6116		    if (disp_w < 1 || disp_h < 1) {
6117			TRACE(("DATA_ERROR: invalid text display size: %dx%d FIXME\n",
6118			       disp_w, disp_h));
6119			break;
6120		    }
6121
6122		    TRACE(("using display cell size: %d,%d\n", disp_w, disp_h));
6123		    context->current_text_controls->character_display_w =
6124			(unsigned) disp_w;
6125		    context->current_text_controls->character_display_h =
6126			(unsigned) disp_h;
6127		    TRACE(("using offset: %d,%d\n", disp_w, 0));
6128		    context->current_text_controls->character_inc_x = disp_w;
6129		    context->current_text_controls->character_inc_y = 0;
6130
6131		    continue;
6132		}
6133
6134		if (extract_regis_num(&optionarg, &displaysizearg)) {
6135		    int standard;
6136		    unsigned disp_w, disp_h, unit_w, unit_h;
6137		    int off_x, off_y;
6138
6139		    TRACE(("standard display cell size: %s\n",
6140			   fragment_to_tempstr(&displaysizearg)));
6141		    if (!regis_num_to_int(&displaysizearg, &standard)) {
6142			TRACE(("DATA_ERROR: unable to parse int in text standard cell size option: \"%s\"\n",
6143			       fragment_to_tempstr(&displaysizearg)));
6144			break;
6145		    }
6146		    if (get_standard_character_size(standard, &disp_w, &disp_h,
6147						    &unit_w, &unit_h,
6148						    &off_x, &off_y)) {
6149			TRACE(("DATA_ERROR: unrecognized standard cell size: \"%d\"\n",
6150			       standard));
6151			break;
6152		    }
6153
6154		    TRACE(("using display cell size: %u,%u\n", disp_w, disp_h));
6155		    context->current_text_controls->character_display_w = disp_w;
6156		    context->current_text_controls->character_display_h = disp_h;
6157		    TRACE(("using offset: %d,%d\n", off_x, off_y));
6158		    context->current_text_controls->character_inc_x = off_x;
6159		    context->current_text_controls->character_inc_y = off_y;
6160
6161		    /*
6162		     * Some ReGIS documentation implies that the "S" option only
6163		     * affects character spacing after a rotation option ("ReGIS
6164		     * uses the spacing value associated with the cell size to
6165		     * space the characters in the tilted string").  The 7-13
6166		     * example in the VT330/VT340 Programmer Reference Manual vol 2
6167		     * appears to say otherwise.  FIXME: verify
6168		     */
6169		    if (1 || !state->string_rot_set) {	/* forced for now */
6170			TRACE(("using unit cell size: %u,%u\n", unit_w, unit_h));
6171			context->current_text_controls->character_unit_cell_w =
6172			    unit_w;
6173			context->current_text_controls->character_unit_cell_h =
6174			    unit_h;
6175		    }
6176
6177		    continue;
6178		}
6179
6180		if (skip_regis_whitespace(&optionarg)) {
6181		    continue;
6182		}
6183
6184		if (!fragment_len(&optionarg)) {
6185		    break;
6186		}
6187
6188		TRACE(("DATA_ERROR: expected int or extent in text display size option: \"%s\"\n",
6189		       fragment_to_tempstr(&optionarg)));
6190		break;
6191	    }
6192	    break;
6193	case 'U':
6194	case 'u':
6195	    TRACE(("found text command custom unit cell size \"%s\"\n",
6196		   fragment_to_tempstr(&optionarg)));
6197	    {
6198		RegisDataFragment unitsizearg;
6199		int unitsize;
6200		int unit_w, unit_h;
6201
6202		if (!extract_regis_extent(&optionarg, &unitsizearg)) {
6203		    TRACE(("DATA_ERROR: expected extent in text unit cell size option: \"%s\"\n",
6204			   fragment_to_tempstr(&optionarg)));
6205		    break;
6206		}
6207		TRACE(("unitsize cell size: %s\n",
6208		       fragment_to_tempstr(&unitsizearg)));
6209		/* FIXME: verify this is in pixels, not user coordinates */
6210		if (!load_regis_pixel_extent(fragment_to_tempstr(&unitsizearg),
6211					     0, 0,
6212					     &unit_w, &unit_h)) {
6213		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6214			   state->option, fragment_to_tempstr(&unitsizearg)));
6215		    break;
6216		}
6217		if (!regis_num_to_int(&unitsizearg, &unitsize)) {
6218		    TRACE(("DATA_ERROR: unable to parse extent in text unit cell size option: \"%s\"\n",
6219			   fragment_to_tempstr(&unitsizearg)));
6220		    break;
6221		}
6222		if (unit_w < 1 || unit_h < 1) {
6223		    TRACE(("DATA_ERROR: invalid text unit cell size: %dx%d FIXME\n",
6224			   unit_w, unit_h));
6225		    break;
6226		}
6227
6228		TRACE(("using unit cell size: %d,%d\n", unit_w, unit_h));
6229
6230		context->current_text_controls->character_unit_cell_w =
6231		    (unsigned) unit_w;
6232		context->current_text_controls->character_unit_cell_h =
6233		    (unsigned) unit_h;
6234
6235		skip_regis_whitespace(&optionarg);
6236		if (fragment_len(&optionarg)) {
6237		    TRACE(("DATA_ERROR: ignoring trailing junk in text unit cell size option \"%s\"\n",
6238			   fragment_to_tempstr(&unitsizearg)));
6239		    break;
6240		}
6241	    }
6242	    break;
6243	case 'W':
6244	case 'w':
6245	    TRACE(("found temporary write options \"%s\"\n",
6246		   fragment_to_tempstr(&optionarg)));
6247	    if (!load_regis_write_control_set(state, context,
6248					      context->graphics_output_cursor_x,
6249					      context->graphics_output_cursor_y,
6250					      &optionarg,
6251					      &context->temporary_write_controls)) {
6252		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
6253		       fragment_to_tempstr(&optionarg)));
6254		break;
6255	    }
6256	    break;
6257	default:
6258	    TRACE(("DATA_ERROR: ignoring unknown ReGIS text command option '%c' arg \"%s\"\n",
6259		   state->option, fragment_to_tempstr(&optionarg)));
6260	    break;
6261	}
6262	break;
6263    case 'v':
6264	TRACE(("inspecting vector option \"%c\" with value \"%s\"\n",
6265	       state->option, fragment_to_tempstr(&optionarg)));
6266	switch (state->option) {
6267	case 'B':
6268	case 'b':
6269	    TRACE(("found begin bounded position stack \"%s\"\n",
6270		   fragment_to_tempstr(&optionarg)));
6271	    skip_regis_whitespace(&optionarg);
6272	    if (fragment_len(&optionarg) > 0U) {
6273		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
6274		       state->option, fragment_to_tempstr(&optionarg)));
6275	    }
6276	    if (state->stack_next >= POSITION_STACK_SIZE) {
6277		/* FIXME: ignore, error, update counter? */
6278		TRACE(("unable to push position to full stack\n"));
6279		break;
6280	    }
6281	    TRACE(("pushing location: %d,%d\n",
6282		   context->graphics_output_cursor_x,
6283		   context->graphics_output_cursor_y));
6284
6285	    state->stack_x[state->stack_next] =
6286		context->graphics_output_cursor_x;
6287	    state->stack_y[state->stack_next] =
6288		context->graphics_output_cursor_y;
6289	    state->stack_next++;
6290	    break;
6291	case 'E':
6292	case 'e':
6293	    TRACE(("found end vector position stack \"%s\"\n",
6294		   fragment_to_tempstr(&optionarg)));
6295	    skip_regis_whitespace(&optionarg);
6296	    if (fragment_len(&optionarg) > 0U) {
6297		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
6298		       state->option, fragment_to_tempstr(&optionarg)));
6299	    }
6300	    if (state->stack_next == 0U) {
6301		TRACE(("DATA_ERROR: unable to pop position from empty stack\n"));
6302		break;
6303	    }
6304
6305	    state->stack_next--;
6306	    if (state->stack_x[state->stack_next] != DUMMY_STACK_X ||
6307		state->stack_y[state->stack_next] != DUMMY_STACK_Y) {
6308		int orig_x, orig_y;
6309
6310		orig_x = context->graphics_output_cursor_x;
6311		orig_y = context->graphics_output_cursor_y;
6312		context->graphics_output_cursor_x =
6313		    state->stack_x[state->stack_next];
6314		context->graphics_output_cursor_y =
6315		    state->stack_y[state->stack_next];
6316		TRACE(("popped location: %d,%d\n",
6317		       context->graphics_output_cursor_x,
6318		       context->graphics_output_cursor_y));
6319
6320		TRACE(("drawing line to popped location\n"));
6321		draw_patterned_line(context,
6322				    orig_x, orig_y,
6323				    context->graphics_output_cursor_x,
6324				    context->graphics_output_cursor_y);
6325	    } else {
6326		TRACE(("not popping location\n"));
6327	    }
6328	    break;
6329	case 'S':
6330	case 's':
6331	    TRACE(("found begin unbounded position stack \"%s\"\n",
6332		   fragment_to_tempstr(&optionarg)));
6333	    skip_regis_whitespace(&optionarg);
6334	    if (fragment_len(&optionarg) > 0U) {
6335		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
6336		       state->option, fragment_to_tempstr(&optionarg)));
6337	    }
6338	    if (state->stack_next >= POSITION_STACK_SIZE) {
6339		/* FIXME: ignore, error, update counter? */
6340		TRACE(("unable to push dummy position to full stack\n"));
6341		break;
6342	    }
6343
6344	    TRACE(("pushing dummy vector positions instead of %d,%d\n",
6345		   context->graphics_output_cursor_x,
6346		   context->graphics_output_cursor_y));
6347	    state->stack_x[state->stack_next] = DUMMY_STACK_X;
6348	    state->stack_y[state->stack_next] = DUMMY_STACK_Y;
6349	    state->stack_next++;
6350	    break;
6351	case 'W':
6352	case 'w':
6353	    TRACE(("found temporary write options \"%s\"\n",
6354		   fragment_to_tempstr(&optionarg)));
6355	    if (!load_regis_write_control_set(state, context,
6356					      context->graphics_output_cursor_x,
6357					      context->graphics_output_cursor_y,
6358					      &optionarg,
6359					      &context->temporary_write_controls)) {
6360		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
6361		       fragment_to_tempstr(&optionarg)));
6362	    }
6363	    break;
6364	default:
6365	    TRACE(("DATA_ERROR: ignoring unknown ReGIS vector command option '%c' arg \"%s\"\n",
6366		   state->option, fragment_to_tempstr(&optionarg)));
6367	    break;
6368	}
6369	break;
6370    case 'w':
6371	skip_regis_whitespace(&optionarg);
6372	TRACE(("inspecting permanent write option \"%c\" with value \"%s\"\n",
6373	       state->option, fragment_to_tempstr(&optionarg)));
6374	if (!load_regis_write_control(state, context,
6375				      context->graphics_output_cursor_x,
6376				      context->graphics_output_cursor_y,
6377				      state->option, &optionarg,
6378				      &context->persistent_write_controls)) {
6379	    TRACE(("DATA_ERROR: invalid write options\n"));
6380	    return 1;
6381	}
6382	break;
6383    default:
6384	TRACE(("DATA_ERROR: unexpected option in \"%c\" command: \"%s\"\n",
6385	       state->command, fragment_to_tempstr(&optionarg)));
6386	return 1;
6387    }
6388
6389    return 1;
6390}
6391
6392static int
6393parse_regis_items(RegisParseState *state, RegisGraphicsContext *context)
6394{
6395    RegisDataFragment *const input = &state->input;
6396    RegisDataFragment item;
6397
6398    if (input->pos >= input->len)
6399	return 0;
6400
6401    if (extract_regis_extent(input, &item)) {
6402	TRACE(("found extent \"%s\"\n", fragment_to_tempstr(&item)));
6403	switch (state->command) {
6404	case 'c':
6405	    {
6406		int orig_x, orig_y;
6407		int new_x, new_y;
6408
6409		if (state->num_points > 0) {
6410		    orig_x = state->x_points[state->num_points - 1];
6411		    orig_y = state->y_points[state->num_points - 1];
6412		} else {
6413		    orig_x = context->graphics_output_cursor_x;
6414		    orig_y = context->graphics_output_cursor_y;
6415		}
6416		if (!load_regis_coord_extent(context,
6417					     fragment_to_tempstr(&item),
6418					     orig_x, orig_y,
6419					     &new_x, &new_y)) {
6420		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6421			   state->command, fragment_to_tempstr(&item)));
6422		    break;
6423		}
6424
6425		switch (state->curve_mode) {
6426		case CURVE_POSITION_ARC_CENTER:
6427		case CURVE_POSITION_ARC_EDGE:
6428		    {
6429			double radians;
6430			int tenthdegs;
6431			int c_x, c_y;
6432			int e_x, e_y;
6433			int e_x_final = 0, e_y_final = 0;
6434
6435			TRACE(("drawing arc: curve_mode=%d\n", state->curve_mode));
6436			TRACE(("drawing arc: new=%d,%d orig=%d,%d\n",
6437			       new_x, new_y, orig_x, orig_y));
6438			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
6439			    c_x = new_x;
6440			    c_y = new_y;
6441			    e_x = orig_x;
6442			    e_y = orig_y;
6443			} else {
6444			    c_x = orig_x;
6445			    c_y = orig_y;
6446			    e_x = new_x;
6447			    e_y = new_y;
6448			}
6449
6450			radians = atan2((double) (c_y - e_y),
6451					(double) (e_x - c_x));
6452			tenthdegs = (int) (0.5 + 3600.0 * radians / (2.0 * M_PI));
6453			if (tenthdegs < 0)
6454			    tenthdegs += 3600;
6455
6456			TRACE(("drawing arc centered at location %d,%d to location %d,%d from %g degrees (%g radians) for %d degrees\n",
6457			       c_x, c_y,
6458			       e_x, e_y,
6459			       tenthdegs / 10., radians, state->arclen));
6460			draw_patterned_arc(context,
6461					   c_x, c_y,
6462					   e_x, e_y,
6463					   tenthdegs, state->arclen * 10,
6464					   &e_x_final, &e_y_final);
6465
6466#ifdef DEBUG_ARC_CENTER
6467			DRAW_PIXEL(context, c_x + 1, c_y, 3U);
6468			DRAW_PIXEL(context, c_x - 1, c_y, 3U);
6469			DRAW_PIXEL(context, c_x, c_y + 1, 3U);
6470			DRAW_PIXEL(context, c_x, c_y - 1, 3U);
6471			DRAW_PIXEL(context, c_x, c_y, 3U);
6472#endif
6473
6474#ifdef DEBUG_ARC_START
6475			DRAW_PIXEL(context, e_x + 1, e_y, 2U);
6476			DRAW_PIXEL(context, e_x - 1, e_y, 2U);
6477			DRAW_PIXEL(context, e_x, e_y + 1, 2U);
6478			DRAW_PIXEL(context, e_x, e_y - 1, 2U);
6479			DRAW_PIXEL(context, e_x, e_y, 2U);
6480#endif
6481
6482#ifdef DEBUG_ARC_END
6483			DRAW_PIXEL(context, e_x_final + 1, e_y_final + 1, 1U);
6484			DRAW_PIXEL(context, e_x_final + 1, e_y_final - 1, 1U);
6485			DRAW_PIXEL(context, e_x_final - 1, e_y_final + 1, 1U);
6486			DRAW_PIXEL(context, e_x_final - 1, e_y_final - 1, 1U);
6487			DRAW_PIXEL(context, e_x_final, e_y_final, 1U);
6488#endif
6489
6490			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
6491			    TRACE(("moving cursor to final point on arc %d,%d\n",
6492				   e_x_final, e_y_final));
6493			    if (state->num_points > 0) {
6494				state->x_points[state->num_points - 1] =
6495				    e_x_final;
6496				state->y_points[state->num_points - 1] =
6497				    e_y_final;
6498			    }
6499			    context->graphics_output_cursor_x = e_x_final;
6500			    context->graphics_output_cursor_y = e_y_final;
6501			}
6502		    }
6503		    break;
6504		case CURVE_POSITION_OPEN_CURVE:
6505		case CURVE_POSITION_CLOSED_CURVE:
6506		    if (state->num_points >= MAX_INPUT_CURVE_POINTS) {
6507			TRACE(("DATA_ERROR: got curve point, but already have max points (%d)\n",
6508			       state->num_points));
6509			break;
6510		    }
6511		    state->x_points[state->num_points] = new_x;
6512		    state->y_points[state->num_points] = new_y;
6513		    state->num_points++;
6514		    TRACE(("adding point to curve with location %d,%d\n",
6515			   new_x, new_y));
6516		    break;
6517		default:
6518		    TRACE(("ERROR: got position, but curve mode %d is unknown\n",
6519			   state->curve_mode));
6520		    break;
6521		}
6522	    }
6523	    break;
6524	case 'p':
6525	    if (!load_regis_coord_extent(context,
6526					 fragment_to_tempstr(&item),
6527					 context->graphics_output_cursor_x,
6528					 context->graphics_output_cursor_y,
6529					 &context->graphics_output_cursor_x,
6530					 &context->graphics_output_cursor_y)) {
6531		TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6532		       state->command, fragment_to_tempstr(&item)));
6533		break;
6534	    }
6535	    TRACE(("moving pen to location %d,%d\n",
6536		   context->graphics_output_cursor_x,
6537		   context->graphics_output_cursor_y));
6538	    break;
6539	case 's':
6540	    TRACE(("extent scroll argument to screen command: \"%s\"\n",
6541		   fragment_to_tempstr(&item)));
6542	    {
6543		int old_ul_x, old_ul_y;
6544		int new_ul_x, new_ul_y;
6545		int copy_w, copy_h;
6546
6547		old_ul_x = 0;
6548		old_ul_y = 0;
6549		TRACE(("current upper-left coordinate in pixel coordinates: %d,%d\n",
6550		       old_ul_x, old_ul_y));
6551		/* FIXME: verify this is in user coordinates, not pixels */
6552		if (!load_regis_coord_extent(context,
6553					     fragment_to_tempstr(&item),
6554					     old_ul_x, old_ul_y,
6555					     &new_ul_x, &new_ul_y)) {
6556		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6557			   state->command, fragment_to_tempstr(&item)));
6558		    break;
6559		}
6560		TRACE(("scrolling image to updated upper-left coordinate in pixel coordinates: %d,%d\n",
6561		       new_ul_x, new_ul_y));
6562
6563		/* FIXME: does any write mode affect revealed background? */
6564		if (new_ul_x > 0)
6565		    copy_w = context->width - new_ul_x;
6566		else
6567		    copy_w = context->width;
6568		if (new_ul_y > 0)
6569		    copy_h = context->height - new_ul_y;
6570		else
6571		    copy_h = context->height;
6572		/* FIXME: verify this applies to write page, not display page */
6573		copy_overlapping_area(context->destination_graphic,
6574				      new_ul_x, new_ul_y,
6575				      0, 0,
6576				      (unsigned) copy_w, (unsigned) copy_h,
6577				      context->background);
6578		context->destination_graphic->dirty = 1;
6579		context->force_refresh = 1;
6580	    }
6581	    break;
6582	case 't':
6583	    /* FIXME: verify this is in pixels, not user coordinates */
6584	    if (!load_regis_pixel_extent(fragment_to_tempstr(&item),
6585					 0, 0,
6586					 &context->current_text_controls->character_inc_x,
6587					 &context->current_text_controls->character_inc_y)) {
6588		TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6589		       state->command, fragment_to_tempstr(&item)));
6590		break;
6591	    }
6592	    TRACE(("setting character spacing to %d,%d\n",
6593		   context->current_text_controls->character_inc_x,
6594		   context->current_text_controls->character_inc_y));
6595	    break;
6596	case 'v':
6597	    {
6598		int orig_x, orig_y;
6599
6600		orig_x = context->graphics_output_cursor_x;
6601		orig_y = context->graphics_output_cursor_y;
6602		if (!load_regis_coord_extent(context,
6603					     fragment_to_tempstr(&item),
6604					     orig_x, orig_y,
6605					     &context->graphics_output_cursor_x,
6606					     &context->graphics_output_cursor_y)) {
6607		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
6608			   state->command, fragment_to_tempstr(&item)));
6609		    break;
6610		}
6611		TRACE(("drawing line to location %d,%d\n",
6612		       context->graphics_output_cursor_x,
6613		       context->graphics_output_cursor_y));
6614		draw_patterned_line(context,
6615				    orig_x, orig_y,
6616				    context->graphics_output_cursor_x,
6617				    context->graphics_output_cursor_y);
6618	    }
6619	    break;
6620	default:
6621	    TRACE(("DATA_ERROR: unexpected extent in \"%c\" command: \"%s\"\n",
6622		   state->command, fragment_to_tempstr(&item)));
6623	    break;
6624	}
6625	return 1;
6626    }
6627
6628    if (state->command != 'l' && extract_regis_pixelvector(input, &item)) {
6629	TRACE(("found pixel vector \"%s\"\n", fragment_to_tempstr(&item)));
6630	switch (state->command) {
6631	case 'c':
6632	    /* FIXME: parse, handle */
6633	    TRACE(("pixelvector in curve command FIXME\n"));
6634	    break;
6635	    /* FIXME: not sure if 'f' supports PVs */
6636	case 'p':
6637	    /* FIXME: error checking */
6638	    if (!load_regis_coord_pixelvector(context,
6639					      fragment_to_tempstr(&item),
6640					      context->graphics_output_cursor_x,
6641					      context->graphics_output_cursor_y,
6642					      &context->graphics_output_cursor_x,
6643					      &context->graphics_output_cursor_y)) {
6644		TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
6645		       state->command, fragment_to_tempstr(&item)));
6646		break;
6647	    }
6648	    TRACE(("moving pen to location %d,%d\n",
6649		   context->graphics_output_cursor_x,
6650		   context->graphics_output_cursor_y));
6651	    break;
6652	case 's':
6653	    TRACE(("pixelvector scroll argument to screen command: \"%s\"\n",
6654		   fragment_to_tempstr(&item)));
6655	    {
6656		int old_ul_x, old_ul_y;
6657		int new_ul_x, new_ul_y;
6658		int copy_w, copy_h;
6659
6660		old_ul_x = 0;
6661		old_ul_y = 0;
6662		TRACE(("current upper-left coordinate in pixel coordinates: %d,%d\n",
6663		       old_ul_x, old_ul_y));
6664		/* FIXME: verify this is in user coordinates, not pixels */
6665		/* FIXME: verify that multiple PV digits result in a single
6666		 * copy (this changes the contents of any exposed edge)
6667		 */
6668		if (!load_regis_coord_pixelvector(context,
6669						  fragment_to_tempstr(&item),
6670						  old_ul_x, old_ul_y,
6671						  &new_ul_x, &new_ul_y)) {
6672		    TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
6673			   state->command, fragment_to_tempstr(&item)));
6674		    break;
6675		}
6676		TRACE(("scrolling image to updated upper-left coordinate in pixel coordinates: %d,%d\n",
6677		       new_ul_x, new_ul_y));
6678
6679		/* FIXME: does any write mode affect revealed background? */
6680		if (new_ul_x > 0)
6681		    copy_w = context->width - new_ul_x;
6682		else
6683		    copy_w = context->width;
6684		if (new_ul_y > 0)
6685		    copy_h = context->height - new_ul_y;
6686		else
6687		    copy_h = context->height;
6688		/* FIXME: verify this applies to write page, not display page */
6689		copy_overlapping_area(context->destination_graphic,
6690				      new_ul_x, new_ul_y,
6691				      0, 0,
6692				      (unsigned) copy_w, (unsigned) copy_h,
6693				      context->background);
6694		context->destination_graphic->dirty = 1;
6695		context->force_refresh = 1;
6696	    }
6697	    break;
6698	case 't':
6699	    {
6700		int dx, dy;
6701
6702		/* FIXME: verify this does not use user coordinates */
6703		if (!load_regis_pixel_pixelvector(fragment_to_tempstr(&item),
6704						  1, 0, 0, &dx, &dy)) {
6705		    TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
6706			   state->command, fragment_to_tempstr(&item)));
6707		    break;
6708		}
6709
6710		dx *= (int) (
6711				context->current_text_controls->character_display_w
6712				>> 1U);
6713		dy *= (int) (
6714				context->current_text_controls->character_display_h
6715				>> 1U);
6716		TRACE(("adding character offset %d,%d\n", dx, dy));
6717		context->graphics_output_cursor_x += dx;
6718		context->graphics_output_cursor_y += dy;
6719	    }
6720	    break;
6721	case 'v':
6722	    {
6723		char const *const pixelvector = fragment_to_tempstr(&item);
6724		unsigned offset;
6725
6726		for (offset = 0U; pixelvector[offset] != '\0';) {
6727		    int orig_x = context->graphics_output_cursor_x;
6728		    int orig_y = context->graphics_output_cursor_y;
6729		    if (!load_regis_coord_pixelvector_step(context, pixelvector,
6730							   &offset,
6731							   orig_x, orig_y,
6732							   &context->graphics_output_cursor_x,
6733							   &context->graphics_output_cursor_y)) {
6734			TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
6735			       state->command, fragment_to_tempstr(&item)));
6736			break;
6737		    }
6738		    TRACE(("drawing line to location %d,%d\n",
6739			   context->graphics_output_cursor_x,
6740			   context->graphics_output_cursor_y));
6741		    draw_patterned_line(context, orig_x, orig_y,
6742					context->graphics_output_cursor_x,
6743					context->graphics_output_cursor_y);
6744		}
6745	    }
6746	    break;
6747	default:
6748	    TRACE(("DATA_ERROR: unexpected pixel vector in \"%c\" command: \"%s\"\n",
6749		   state->command, fragment_to_tempstr(&item)));
6750	    break;
6751	}
6752	return 1;
6753    }
6754
6755    if (extract_regis_string(input, state->temp, state->templen)) {
6756	switch (state->command) {
6757	case 'l':
6758	    /* FIXME: confirm that extra characters are ignored */
6759	    TRACE(("found character to load: \"%s\"\n", state->temp));
6760	    state->load_glyph = (unsigned) (unsigned char) state->temp[0];
6761	    state->load_row = 0U;
6762	    break;
6763	case 't':
6764	    TRACE(("found string to draw: \"%s\"\n", state->temp));
6765	    draw_text(context, state->temp);
6766	    break;
6767	default:
6768	    TRACE(("DATA_ERROR: unexpected string argument to \"%c\" command: \"%s\"\n",
6769		   state->command, state->temp));
6770	    break;
6771	}
6772	return 1;
6773    }
6774
6775    /* hex values */
6776    if (state->command == 'l') {
6777	unsigned digit;
6778
6779	for (digit = 0U; digit < (state->load_w + 3U) >> 2U; digit++) {
6780	    char ch = peek_fragment(input);
6781
6782	    if (!IS_HEX_DIGIT(ch)) {
6783		if (ch != ',' && ch != ';' &&
6784		    ch != ' ' && ch != '\r' &&
6785		    ch != '\n') {
6786		    TRACE(("found end of hexadecimal string witch '%c' on digit %u\n",
6787			   ch, digit));
6788		    /* FIXME: need to unput the digits up to this point */
6789		    /*
6790		     * Report success since we ate some characters,
6791		     * and the new char needs to be compared with commands
6792		     * and other top-level things.
6793		     */
6794		    if (digit != 0U)
6795			return 1;
6796		    return 0;
6797		}
6798		pop_fragment(input);
6799		break;
6800	    }
6801
6802	    state->temp[digit] = ch;
6803	    pop_fragment(input);
6804	}
6805	state->temp[digit] = '\0';
6806
6807	if (strlen(state->temp) > 0) {
6808	    unsigned long val;
6809	    unsigned glyph_size;
6810
6811	    val = strtoul(state->temp, NULL, 16);
6812	    TRACE(("found row %u for glyph %u: \"%s\" value %02lx (%lu)\n",
6813		   state->load_row, state->load_glyph, state->temp, val, val));
6814
6815	    if (state->load_row >= state->load_h) {
6816		TRACE(("DATA_ERROR: ignoring extra row for glyph %u\n",
6817		       state->load_glyph));
6818		return 0;
6819	    }
6820
6821	    if (state->load_index == MAX_REGIS_ALPHABETS) {
6822		state->load_index = find_free_alphabet_index(context,
6823							     state->load_alphabet,
6824							     state->load_w,
6825							     state->load_h);
6826		TRACE(("current alphabet is %u and size is %ux%u; assigning alphabet index %u\n",
6827		       state->load_alphabet, state->load_w, state->load_h, state->load_index));
6828	    }
6829
6830	    glyph_size = GLYPH_WIDTH_BYTES(
6831					      context->alphabets[state->load_index].pixw) *
6832		context->alphabets[state->load_index].pixh;
6833	    if (context->alphabets[state->load_index].bytes == NULL) {
6834		if (!(context->alphabets[state->load_index].bytes =
6835		      calloc(MAX_GLYPHS * glyph_size, sizeof(unsigned char)))) {
6836		    TRACE(("ERROR: unable to allocate %u bytes for glyph storage\n",
6837			   MAX_GLYPHS * glyph_size));
6838		    return 0;
6839		}
6840	    } {
6841		unsigned char *glyph;
6842		unsigned bytew;
6843		unsigned byte;
6844		unsigned unused_bits;
6845
6846		glyph = &context->alphabets[state->load_index]
6847		    .bytes[state->load_glyph * glyph_size];
6848		bytew = GLYPH_WIDTH_BYTES(context->alphabets[state->load_index]
6849					  .pixw);
6850		unused_bits = 8U - (context->alphabets[state->load_index].pixw
6851				    & 3U);
6852		if (unused_bits == 8U) {
6853		    unused_bits = 0U;
6854		}
6855		for (byte = 0U; byte < bytew; byte++) {
6856		    glyph[state->load_row * bytew + byte] =
6857			(unsigned char) (((val << unused_bits) >>
6858					  ((bytew - (byte + 1U)) << 3U)) & 255U);
6859#ifdef DEBUG_LOAD
6860		    TRACE(("bytew=%u val=%lx byte=%u output=%x\n", bytew, val,
6861			   byte,
6862			   (unsigned) glyph[state->load_row * bytew + byte]));
6863#endif
6864		}
6865
6866		state->load_row++;
6867		context->alphabets[state->load_index]
6868		    .loaded[state->load_glyph] = 1;
6869#ifdef DEBUG_LOAD
6870		TRACE(("marking alphabet %u at index %u glyph %u as loaded\n",
6871		       state->load_alphabet, state->load_index,
6872		       state->load_glyph));
6873#endif
6874		return 1;
6875	    }
6876	}
6877    }
6878
6879    return 0;
6880}
6881
6882static int
6883parse_regis_toplevel(RegisParseState *state, RegisGraphicsContext *context)
6884{
6885    RegisDataFragment parenthesized;
6886    char ch;
6887#if 0
6888    TRACE(("reference line: shading=%d ref=%u loc=%d\n",
6889	   context->temporary_write_controls.shading_enabled,
6890	   context->temporary_write_controls.shading_reference_dim,
6891	   context->temporary_write_controls.shading_reference));
6892#endif
6893
6894#ifdef DEBUG_PARSING
6895    TRACE(("parsing top level: char %d of %d (next char '%c')\n",
6896	   state->input.pos,
6897	   state->input.len,
6898	   peek_fragment(&state->input)));
6899#endif
6900    if (skip_regis_whitespace(&state->input))
6901	return 0;
6902    /* FIXME: the semicolon terminates the current command even if inside of an optionset or extent */
6903    if (peek_fragment(&state->input) == ';') {
6904	pop_fragment(&state->input);
6905	TRACE(("ending '%c' command\n", state->command));
6906	state->command = '_';
6907	state->option = '_';
6908	return 1;
6909    }
6910    /* Load statements contain hex values which may look like commands. */
6911    ch = peek_fragment(&state->input);
6912    if (state->command != 'l' || !IS_HEX_DIGIT(ch)) {
6913#ifdef DEBUG_PARSING
6914	TRACE(("checking for top level command...\n"));
6915#endif
6916	if (parse_regis_command(state)) {
6917	    /* FIXME: verify that these are the things reset on a new command */
6918	    TRACE(("resetting temporary write controls and pattern state\n"));
6919	    copy_regis_write_controls(&context->persistent_write_controls,
6920				      &context->temporary_write_controls);
6921	    context->pattern_count = 0U;
6922	    context->pattern_bit = 1U;
6923
6924	    /* FIXME: what happens if temporary text controls aren't closed? */
6925	    /* FIXME: what if temporary text controls are nested? */
6926	    context->current_text_controls = &context->persistent_text_controls;
6927	    return 1;
6928	}
6929    }
6930#ifdef DEBUG_PARSING
6931    TRACE(("checking for top level parentheses...\n"));
6932#endif
6933    if (extract_regis_parenthesized_data(&state->input, &parenthesized)) {
6934	RegisDataFragment orig_input;
6935
6936	if (state->command == 'f') {	/* Fill */
6937	    TRACE(("found commands in fill mode \"%s\"\n",
6938		   fragment_to_tempstr(&parenthesized)));
6939	    orig_input = state->input;
6940	    state->input = parenthesized;
6941	    state->command = '_';
6942	    state->option = '_';
6943	    context->fill_mode = 1;
6944	    context->fill_point_count = 0U;
6945	    while (state->input.pos < state->input.len)
6946		parse_regis_toplevel(state, context);
6947	    draw_filled_polygon(context);
6948	    context->fill_point_count = 0U;
6949	    context->fill_mode = 0;
6950	    state->command = 'f';
6951	    state->input = orig_input;
6952	    return 1;
6953	} else {
6954	    orig_input = state->input;
6955	    state->input = parenthesized;
6956	    state->option = '_';
6957	    TRACE(("parsing at optionset level: %d of %d\n",
6958		   state->input.pos,
6959		   state->input.len));
6960	    for (;;) {
6961		if (state->input.pos >= state->input.len)
6962		    break;
6963		TRACE(("looking at optionset character: \"%c\"\n",
6964		       peek_fragment(&state->input)));
6965		if (skip_regis_whitespace(&state->input))
6966		    continue;
6967		if (parse_regis_option(state, context))
6968		    continue;
6969		if (parse_regis_items(state, context))
6970		    continue;
6971		if (state->input.pos >= state->input.len)
6972		    break;
6973		{
6974		    char skip;
6975
6976		    skip = pop_fragment(&state->input);
6977		    (void) skip;	/* variable needed only if tracing */
6978		    TRACE(("DATA_ERROR: skipping unexpected character in optionset: \"%c\"\n",
6979			   skip));
6980		}
6981		/* FIXME: suboptions */
6982	    }
6983	    state->option = '_';
6984	    state->input = orig_input;
6985	    return 1;
6986	}
6987    }
6988    if (state->command == 'f') {	/* Fill */
6989	RegisDataFragment optionarg;
6990	if (extract_regis_option(&state->input, &state->option, &optionarg)) {
6991	    skip_regis_whitespace(&optionarg);
6992
6993	    TRACE(("found temporary write options \"%s\"\n",
6994		   fragment_to_tempstr(&optionarg)));
6995	    if (!load_regis_write_control_set(state, context,
6996					      context->graphics_output_cursor_x,
6997					      context->graphics_output_cursor_y,
6998					      &optionarg,
6999					      &context->temporary_write_controls)) {
7000		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
7001		       fragment_to_tempstr(&optionarg)));
7002	    }
7003	    return 1;
7004	}
7005	TRACE(("checking for top level items (though none should be present)...\n"));
7006	if (parse_regis_items(state, context))
7007	    return 1;
7008    } else {
7009#ifdef DEBUG_PARSING
7010	TRACE(("checking for top level items...\n"));
7011#endif
7012	if (parse_regis_items(state, context))
7013	    return 1;
7014    }
7015    if (state->input.pos >= state->input.len)
7016	return 0;
7017
7018    {
7019	char skip;
7020
7021	skip = pop_fragment(&state->input);
7022	(void) skip;		/* variable needed only if tracing */
7023	TRACE(("DATA_ERROR: skipping unexpected character at top level: \"%c\"\n", ch));
7024    }
7025    return 0;
7026}
7027
7028static void
7029init_regis_parse_state(RegisParseState *state)
7030{
7031    state->command = '_';
7032    state->option = '_';
7033    state->stack_next = 0U;
7034    state->load_index = MAX_REGIS_ALPHABETS;
7035}
7036
7037void
7038parse_regis(XtermWidget xw, ANSI *params, char const *string)
7039{
7040    TScreen *screen = TScreenOf(xw);
7041    RegisGraphicsContext *const context = &persistent_context;
7042    RegisParseState *const state = &persistent_state;
7043    struct timeval prev_tv;
7044    struct timeval curr_tv;
7045    unsigned iterations;
7046    int Pmode;
7047
7048    if (params->a_nparam > 0)
7049	Pmode = params->a_param[0];
7050    else
7051	Pmode = 0;
7052
7053    TRACE(("ReGIS vector graphics mode, param_count=%d mode=%d\n",
7054	   params->a_nparam, Pmode));
7055
7056    init_fragment(&state->input, string);
7057    state->templen = (unsigned) strlen(string) + 1U;
7058    if (!(state->temp = malloc((size_t) state->templen))) {
7059	TRACE(("Unable to allocate temporary buffer of size %u\n",
7060	       state->templen));
7061	return;
7062    }
7063
7064    /* Update the screen scrolling and do a refresh.
7065     * The refresh may not cover the whole graphic.
7066     */
7067    if (screen->scroll_amt)
7068	FlushScroll(xw);
7069
7070    /* Only reset on the first ReGIS image unless it is being requested. */
7071    if (context->width == 0 || context->height == 0 ||
7072	Pmode == 1 || Pmode == 3) {
7073	init_regis_parse_state(state);
7074	init_regis_graphics_context(screen->terminal_id,
7075				    screen->graphics_regis_def_wide,
7076				    screen->graphics_regis_def_high,
7077				    get_color_register_count(screen),
7078				    screen->graphics_regis_default_font,
7079				    context);
7080    }
7081
7082    map_regis_graphics_pages(xw, context);
7083
7084    X_GETTIMEOFDAY(&prev_tv);
7085    iterations = 0U;
7086    refresh_modified_displayed_graphics(xw);
7087
7088    for (;;) {
7089	if (skip_regis_whitespace(&state->input))
7090	    continue;
7091	if (parse_regis_toplevel(state, context)) {
7092	    int need_refresh = 0;
7093
7094	    /* FIXME: Move refresh logic out of the top level so that long
7095	     * sequences of filled drawing commands can be refreshed before the
7096	     * end of the fill command.
7097	     */
7098	    iterations++;
7099	    if (context->force_refresh) {
7100		X_GETTIMEOFDAY(&curr_tv);
7101		need_refresh = 1;
7102	    } else if (iterations > MIN_ITERATIONS_BEFORE_REFRESH) {
7103		X_GETTIMEOFDAY(&curr_tv);
7104		if ((Time) curr_tv.tv_sec > (Time) prev_tv.tv_sec + 1UL) {
7105		    need_refresh = 1;
7106		} else {
7107#define DiffTime(TV) (TV.tv_sec * 1000L + TV.tv_usec / 1000L)
7108		    long diff = (long) (DiffTime(curr_tv) - DiffTime(prev_tv));
7109		    if (diff > MIN_MS_BEFORE_REFRESH)
7110			need_refresh = 1;
7111		}
7112	    }
7113
7114	    if (need_refresh) {
7115		TRACE(("refreshing after %u iterations and %ldms\n",
7116		       iterations,
7117		       DiffTime(curr_tv) - DiffTime(prev_tv)));
7118		context->force_refresh = 0;
7119		/* FIXME: pre-ANSI compilers need memcpy() */
7120		prev_tv = curr_tv;
7121		iterations = 0U;
7122		refresh_modified_displayed_graphics(xw);
7123#if OPT_DOUBLE_BUFFER
7124		{
7125		    XdbeSwapInfo swap;
7126
7127		    swap.swap_window = VWindow(screen);
7128		    swap.swap_action = XdbeCopied;
7129		    XdbeSwapBuffers(XtDisplay(term), &swap, 1);
7130		    XFlush(XtDisplay(xw));
7131		}
7132#endif
7133	    }
7134
7135	    continue;
7136	}
7137
7138	if (state->input.pos >= state->input.len)
7139	    break;
7140    }
7141
7142    free(state->temp);
7143
7144    refresh_modified_displayed_graphics(xw);
7145    TRACE(("DONE! Successfully parsed ReGIS data.\n"));
7146}
7147