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