graphics_regis.c revision 5104ee6e
1/* $XTermId: graphics_regis.c,v 1.153 2024/12/01 20:21:19 tom Exp $ */
2
3/*
4 * Copyright 2014-2023,2024 by Thomas E. Dickey
5 * Copyright 2014-2022,2023 by Ross Combs
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#ifdef __CPPCHECK__
1381    memset(m, 0, sizeof(m));	/* work around false-positive */
1382#endif
1383
1384#ifdef DEBUG_SPLINE_POINTS
1385    {
1386	unsigned save_pattern;
1387
1388	i = 0;
1389	global_context->temporary_write_controls.foreground = 11;
1390	save_pattern = global_context->temporary_write_controls.pattern;
1391	global_context->temporary_write_controls.pattern = 0xff;
1392	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
1393			   3600, NULL, NULL);
1394	i++;
1395	global_context->temporary_write_controls.foreground = 15;
1396	for (; i < n; i++) {
1397	    draw_patterned_arc(global_context,
1398			       x[i], y[i],
1399			       x[i] + 2, y[i],
1400			       0, 3600, NULL, NULL);
1401	}
1402	global_context->temporary_write_controls.foreground = 10;
1403	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
1404			   3600, NULL, NULL);
1405	global_context->temporary_write_controls.pattern = save_pattern;
1406    }
1407#endif
1408
1409    x3 = x[n - 1];
1410    y3 = y[n - 1];
1411    x4 = x[n];
1412    y4 = y[n];
1413
1414    x[1] = x0 = 12 * x[1] - 3 * x[0];	/* first row of matrix */
1415    y[1] = y0 = 12 * y[1] - 3 * y[0];
1416
1417    for (i = 2; i < n; i++) {	/* forward sweep */
1418	if (i - 2 < M_MAX)
1419	    m[i - 2] = mi = 0.25 / (2.0 - mi);
1420	x[i] = x0 = ifloor(12 * x[i] - 2 * x0 * mi + 0.5);
1421	y[i] = y0 = ifloor(12 * y[i] - 2 * y0 * mi + 0.5);
1422    }
1423    x2 = ifloor((x0 - 3 * x4) / (7 - 4 * mi) + 0.5);	/* correct last row */
1424    /* printf("y0=%d, y4=%d mi=%g\n", y0, y4, mi); */
1425    y2 = ifloor((y0 - 3 * y4) / (7 - 4 * mi) + 0.5);
1426    /* printf("y2=%d, y3=%d, y4=%d\n", y2, y3, y4); */
1427#ifdef DEBUG_SPLINE_SEGMENTS
1428    color++;
1429    global_context->temporary_write_controls.foreground = color;
1430#endif
1431    if (!skip_first_last)
1432	plotCubicBezier(x3, y3, (x2 + x4) / 2, (y2 + y4) / 2, x4, y4, x4, y4);
1433
1434    if (n - 3 < M_MAX)
1435	mi = m[n - 3];
1436    x1 = ifloor((x[n - 2] - 2 * x2) * mi + 0.5);
1437    y1 = ifloor((y[n - 2] - 2 * y2) * mi + 0.5);
1438    for (i = n - 3; i > 0; i--) {	/* back substitution */
1439	if (i <= M_MAX)
1440	    mi = m[i - 1];
1441	x0 = ifloor((x[i] - 2 * x1) * mi + 0.5);
1442	y0 = ifloor((y[i] - 2 * y1) * mi + 0.5);
1443	x4 = ifloor((x0 + 4 * x1 + x2 + 3) / 6.0);	/* reconstruct P[i] */
1444	y4 = ifloor((y0 + 4 * y1 + y2 + 3) / 6.0);
1445#ifdef DEBUG_SPLINE_SEGMENTS
1446	color++;
1447	global_context->temporary_write_controls.foreground = color;
1448#endif
1449#define CB_PARM(num) ifloor((num) / 3.0 + 0.5)
1450	plotCubicBezier(x4, y4,
1451			CB_PARM(2 * x1 + x2),
1452			CB_PARM(2 * y1 + y2),
1453			CB_PARM(x1 + 2 * x2),
1454			CB_PARM(y1 + 2 * y2),
1455			x3, y3);
1456	x3 = x4;
1457	y3 = y4;
1458	x2 = x1;
1459	y2 = y1;
1460	x1 = x0;
1461	y1 = y0;
1462    }
1463    x0 = x[0];
1464    x4 = ifloor((3 * x0 + 7 * x1 + 2 * x2 + 6) / 12.0);		/* reconstruct P[1] */
1465    y0 = y[0];
1466    y4 = ifloor((3 * y0 + 7 * y1 + 2 * y2 + 6) / 12.0);
1467#ifdef DEBUG_SPLINE_SEGMENTS
1468    global_context->temporary_write_controls.foreground = 4;
1469#endif
1470    plotCubicBezier(x4, y4,
1471		    CB_PARM(2 * x1 + x2),
1472		    CB_PARM(2 * y1 + y2),
1473		    CB_PARM(x1 + 2 * x2),
1474		    CB_PARM(y1 + 2 * y2),
1475		    x3, y3);
1476#ifdef DEBUG_SPLINE_SEGMENTS
1477    color++;
1478    global_context->temporary_write_controls.foreground = color;
1479#endif
1480    if (!skip_first_last)
1481	plotCubicBezier(x0, y0, x0, y0, (x0 + x1) / 2, (y0 + y1) / 2, x4, y4);
1482}
1483
1484static unsigned
1485find_free_alphabet_index(RegisGraphicsContext *context, unsigned alphabet,
1486			 unsigned pixw, unsigned pixh)
1487{
1488    unsigned ii, jj;
1489
1490    /* try an exact match */
1491    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1492	if (context->alphabets[ii].alphabet_num == alphabet &&
1493	    context->alphabets[ii].pixw == pixw &&
1494	    context->alphabets[ii].pixh == pixh) {
1495	    return ii;
1496	}
1497    }
1498
1499    /* otherwise use any empty slot */
1500    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1501	if (context->alphabets[ii].alphabet_num == INVALID_ALPHABET_NUM) {
1502	    context->alphabets[ii].alphabet_num = alphabet;
1503	    context->alphabets[ii].pixw = pixw;
1504	    context->alphabets[ii].pixh = pixh;
1505	    return ii;
1506	}
1507    }
1508
1509    /* otherwise recycle a slot with a different font size */
1510    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
1511	if (context->alphabets[ii].alphabet_num == alphabet) {
1512	    context->alphabets[ii].pixw = pixw;
1513	    context->alphabets[ii].pixh = pixh;
1514	    context->alphabets[ii].name[0] = '\0';
1515	    context->alphabets[ii].fontname[0] = '\0';
1516	    context->alphabets[ii].use_font = 0;
1517	    if (context->alphabets[ii].bytes != NULL) {
1518		free(context->alphabets[ii].bytes);
1519		context->alphabets[ii].bytes = NULL;
1520	    }
1521	    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
1522		context->alphabets[ii].loaded[jj] = 0;
1523	    }
1524	    return ii;
1525	}
1526    }
1527
1528    /* finally just recycle this arbitrary slot */
1529    context->alphabets[0U].alphabet_num = alphabet;
1530    context->alphabets[0U].pixw = pixw;
1531    context->alphabets[0U].pixh = pixh;
1532    context->alphabets[0U].name[0] = '\0';
1533    context->alphabets[0U].fontname[0] = '\0';
1534    context->alphabets[0U].use_font = 0;
1535    if (context->alphabets[0U].bytes != NULL) {
1536	free(context->alphabets[0U].bytes);
1537	context->alphabets[0U].bytes = NULL;
1538    }
1539    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
1540	context->alphabets[0U].loaded[jj] = 0;
1541    }
1542
1543    return 0U;
1544}
1545
1546#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1547static void
1548dump_bitmap_pixels(Char const *pixels, unsigned w, unsigned h)
1549{
1550    unsigned yy, xx;
1551
1552    for (yy = 0U; yy < h; yy++) {
1553	printf(" ");
1554	for (xx = 0U; xx < w; xx++) {
1555	    if (pixels[yy * w + xx]) {
1556		printf("#");
1557	    } else {
1558		printf("_");
1559	    }
1560	}
1561	printf("\n");
1562    }
1563}
1564#endif
1565
1566#if OPT_RENDERFONT && defined(HAVE_TYPE_FCCHAR32)
1567static int
1568copy_bitmap_from_xft_font(XtermWidget xw, XftFont *font, FcChar32 ch,
1569			  Char *pixels, unsigned w, unsigned h,
1570			  unsigned xmin, unsigned ymin)
1571{
1572    /*
1573     * FIXME: cache:
1574     * - the bitmap for the last M characters and target dimensions
1575     * - reuse the pixmap object where possible
1576     */
1577    Display *display = XtDisplay(xw);
1578    Screen *screen = XtScreen(xw);
1579    XftColor bg, fg;
1580    Pixmap bitmap;
1581    XftDraw *draw;
1582    XImage *image;
1583    GC glyph_gc;
1584    unsigned bmw, bmh;
1585    unsigned xx, yy;
1586
1587    bmw = w + xmin;
1588    bmh = h;
1589    if (bmw < 1 || bmh < 1) {
1590	TRACE(("refusing impossible bitmap size w=%d h=%d xmin=%d ymin=%d for ch='%c'\n",
1591	       bmw, bmh, xmin, ymin, ch));
1592	return 0;
1593    }
1594    bitmap = XCreatePixmap(display,
1595			   DefaultRootWindow(display),
1596			   bmw, bmh, (unsigned) getVisualDepth(xw));
1597    if (bitmap == None) {
1598	TRACE(("unable to create Pixmap for Xft\n"));
1599	return 0;
1600    }
1601    draw = XftDrawCreate(display, bitmap, xw->visInfo->visual,
1602			 XDefaultColormap(display,
1603					  XScreenNumberOfScreen(screen)));
1604    if (!draw) {
1605	TRACE(("unable to create XftDraw\n"));
1606	XFreePixmap(display, bitmap);
1607	return 0;
1608    }
1609
1610    bg.pixel = 0UL;
1611    bg.color.red = 0;
1612    bg.color.green = 0;
1613    bg.color.blue = 0;
1614    bg.color.alpha = 0x0;
1615    XftDrawRect(draw, &bg, 0, 0, bmw, bmh);
1616
1617    fg.pixel = 1UL;
1618    fg.color.red = 0xffff;
1619    fg.color.green = 0xffff;
1620    fg.color.blue = 0xffff;
1621    fg.color.alpha = 0xffff;
1622    XftDrawString32(draw, &fg, font, -(int) xmin, font->ascent - (int) ymin,
1623		    &ch, 1);
1624
1625    glyph_gc = XCreateGC(display, bitmap, 0UL, NULL);
1626    if (!glyph_gc) {
1627	TRACE(("unable to create GC\n"));
1628	XftDrawDestroy(draw);
1629	XFreePixmap(display, bitmap);
1630	return 0;
1631    }
1632    XSetForeground(display, glyph_gc, 1UL);
1633    XSetBackground(display, glyph_gc, 0UL);
1634    image = XGetImage(display, bitmap, 0, 0, w, h, 1UL, XYPixmap);
1635    if (!image) {
1636	TRACE(("unable to create XImage\n"));
1637	XFreeGC(display, glyph_gc);
1638	XftDrawDestroy(draw);
1639	XFreePixmap(display, bitmap);
1640	return 0;
1641    }
1642
1643    for (yy = 0U; yy < h; yy++) {
1644#ifdef DEBUG_XFT_GLYPH_COPY
1645	TRACE(("'%c'[%02u]:", ch, yy));
1646#endif
1647	for (xx = 0U; xx < w; xx++) {
1648	    unsigned long pix;
1649	    pix = XGetPixel(image, (int) xx, (int) yy);
1650	    pixels[yy * w + xx] = (unsigned char) pix;
1651#ifdef DEBUG_XFT_GLYPH_COPY
1652	    TRACE((" %lu", pix));
1653#endif
1654	}
1655#ifdef DEBUG_XFT_GLYPH_COPY
1656	TRACE(("\n"));
1657#endif
1658    }
1659
1660    XFreeGC(display, glyph_gc);
1661    XDestroyImage(image);
1662    XftDrawDestroy(draw);
1663    XFreePixmap(display, bitmap);
1664    return 1;
1665}
1666
1667static void
1668get_xft_glyph_dimensions(XtermWidget xw, XftFont *font, unsigned *w,
1669			 unsigned *h, unsigned *xmin, unsigned *ymin)
1670{
1671    unsigned workw, workh;
1672    FcChar32 ch;
1673    Char *pixels;
1674    Char *pixelp;
1675    unsigned yy, xx;
1676    unsigned char_count, pixel_count;
1677    unsigned real_minx, real_maxx, real_miny, real_maxy;
1678    unsigned char_minx, char_maxx, char_miny, char_maxy;
1679
1680    /*
1681     * For each ASCII or ISO-8859-1 printable code, find out what its
1682     * dimensions are.
1683     *
1684     * We actually render the glyphs and determine the extents ourselves
1685     * because the font library can lie by several pixels, and since we are
1686     * doing manual character placement in fixed areas the glyph boundary needs
1687     * to be accurate.
1688     *
1689     * Ignore control characters and spaces - their extent information is
1690     * misleading.
1691     */
1692
1693    /* Our "work area" is just a buffer which should be big enough to hold the
1694     * largest glyph even if its size is under-reported by a couple of pixels
1695     * in each dimension.
1696     */
1697    workw = (unsigned) font->max_advance_width + 2U;
1698    if (font->ascent + font->descent > font->height) {
1699	workh = (unsigned) (font->ascent + font->descent) + 2U;
1700    } else {
1701	workh = (unsigned) font->height + 2U;
1702    }
1703
1704    if (!(pixels = TypeMallocN(Char, (size_t) (workw * workh)))) {
1705	*w = 0U;
1706	*h = 0U;
1707#ifdef DEBUG_COMPUTED_FONT_METRICS
1708	TRACE(("reported metrics:\n"));
1709	TRACE((" %ux%u ascent=%u descent=%u\n", font->max_advance_width,
1710	       font->height, font->ascent, font->descent));
1711	TRACE(("computed metrics:\n"));
1712	TRACE((" (unable to allocate pixel array)\n"));
1713#endif
1714	return;
1715    }
1716
1717    /* FIXME: ch is in UCS32 -- try to support non-ASCII characters */
1718    char_count = 0U;
1719    real_minx = workw - 1U;
1720    real_maxx = 0U;
1721    real_miny = workh - 1U;
1722    real_maxy = 0U;
1723    for (ch = 33; ch < 256; ++ch) {
1724	if (ch >= 127 && ch <= 160) {
1725#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1726	    if (IS_DEBUG_CHAR(ch))
1727		printf("char: '%c' not in interesting range; ignoring\n",
1728		       (char) ch);
1729#endif
1730	    continue;
1731	}
1732	if (!FcCharSetHasChar(font->charset, ch)) {
1733#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1734	    if (IS_DEBUG_CHAR(ch))
1735		printf("char: '%c' not in charset; ignoring\n", (char) ch);
1736#endif
1737	    continue;
1738	}
1739
1740	if (!copy_bitmap_from_xft_font(xw, font, ch, pixels,
1741				       workw, workh, 0U, 0U)) {
1742#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1743	    if (IS_DEBUG_CHAR(ch))
1744		printf("char: '%c' bitmap could not be copied; ignoring\n",
1745		       (char) ch);
1746#endif
1747	    continue;
1748	}
1749
1750	pixel_count = 0U;
1751	char_minx = workh - 1U;
1752	char_maxx = 0U;
1753	char_miny = workh - 1U;
1754	char_maxy = 0U;
1755	pixelp = pixels;
1756	for (yy = 0U; yy < workh; yy++) {
1757	    for (xx = 0U; xx < workw; xx++) {
1758		if (*pixelp++) {
1759		    if (xx < char_minx)
1760			char_minx = xx;
1761		    else if (xx > char_maxx)
1762			char_maxx = xx;
1763		    if (yy < char_miny)
1764			char_miny = yy;
1765		    else if (yy > char_maxy)
1766			char_maxy = yy;
1767		    pixel_count++;
1768		}
1769	    }
1770	}
1771	if (pixel_count < 1U) {
1772#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1773	    if (IS_DEBUG_CHAR(ch))
1774		printf("char: '%c' has no pixels; ignoring\n", (char) ch);
1775#endif
1776	    continue;
1777	}
1778#ifdef DEBUG_SPECIFIC_CHAR_METRICS
1779	if (IS_DEBUG_CHAR(ch)) {
1780	    printf("char: '%c' (%d)\n", (char) ch, ch);
1781	    printf(" minx: %u\n", char_minx);
1782	    printf(" maxx: %u\n", char_maxx);
1783	    printf(" miny: %u\n", char_miny);
1784	    printf(" maxy: %u\n", char_maxy);
1785	    dump_bitmap_pixels(pixels, workw, workh);
1786	    printf("\n");
1787	}
1788#endif
1789
1790	if (char_minx < real_minx)
1791	    real_minx = char_minx;
1792	if (char_maxx > real_maxx)
1793	    real_maxx = char_maxx;
1794	if (char_miny < real_miny)
1795	    real_miny = char_miny;
1796	if (char_maxy > real_maxy)
1797	    real_maxy = char_maxy;
1798	char_count++;
1799    }
1800
1801    free(pixels);
1802
1803    if (char_count < 1U) {
1804#ifdef DEBUG_COMPUTED_FONT_METRICS
1805	TRACE(("reported metrics:\n"));
1806	TRACE((" %ux%u ascent=%u descent=%u\n", font->max_advance_width,
1807	       font->height, font->ascent, font->descent));
1808	TRACE(("computed metrics:\n"));
1809	TRACE((" (no characters found)\n"));
1810#endif
1811	*w = 0U;
1812	*h = 0U;
1813	return;
1814    }
1815
1816    *w = (unsigned) (1 + real_maxx - real_minx);
1817    *h = (unsigned) (1 + real_maxy - real_miny);
1818    *xmin = real_minx;
1819    *ymin = real_miny;
1820
1821#ifdef DEBUG_COMPUTED_FONT_METRICS
1822    printf("reported metrics:\n");
1823    printf(" %ux%u ascent=%u descent=%u\n", font->max_advance_width,
1824	   font->height, font->ascent, font->descent);
1825    printf("computed metrics:\n");
1826    printf(" real_minx=%u real_maxx=%u real_miny=%u real_maxy=%u\n",
1827	   real_minx, real_maxx, real_miny, real_maxy);
1828    printf(" final: %ux%u xmin=%u ymin=%u\n", *w, *h, *xmin, *ymin);
1829#endif
1830}
1831
1832#define FONT_SIZE_CACHE_SIZE 32U
1833
1834typedef struct {
1835    XftFont *font_data;
1836    char fontname[REGIS_FONTNAME_LEN];
1837    unsigned maxw, maxh, max_pixels;
1838    unsigned targeth;
1839    unsigned w, h;
1840    unsigned xmin;
1841    unsigned ymin;
1842} FONT_CACHE;
1843
1844static FONT_CACHE font_cache[FONT_SIZE_CACHE_SIZE];
1845
1846static void
1847close_xft_font(XtermWidget xw, XftFont *font)
1848{
1849    if (font != NULL) {
1850	Display *display = XtDisplay(xw);
1851	unsigned ii;
1852	for (ii = 0; ii < FONT_SIZE_CACHE_SIZE; ++ii) {
1853	    if (font == font_cache[ii].font_data) {
1854		font_cache[ii].font_data = NULL;
1855		break;
1856	    }
1857	}
1858	XftFontClose(display, font);
1859    }
1860}
1861
1862/* Find the font pixel size which returns the font which is closest to the given
1863 * maxw and maxh without overstepping either dimension.
1864 */
1865static XftFont *
1866find_best_xft_font_size(XtermWidget xw,
1867			char const *fontname,
1868			unsigned maxw,
1869			unsigned maxh,
1870			unsigned max_pixels,
1871			unsigned *w,
1872			unsigned *h,
1873			unsigned *xmin,
1874			unsigned *ymin)
1875{
1876    Display *display = XtDisplay(xw);
1877    Screen *screen = XtScreen(xw);
1878    XftFont *font;
1879    unsigned targeth;
1880    unsigned ii;
1881    FONT_CACHE *cp = NULL;
1882
1883    assert(display);
1884    assert(screen);
1885    assert(fontname);
1886    assert(w);
1887    assert(h);
1888    assert(xmin);
1889    assert(ymin);
1890
1891#ifdef DEBUG_FONT_SIZE_SEARCH
1892    TRACE(("determining best size of font \"%s\" for %ux%u glyph with max_pixels=%u\n",
1893	   fontname, maxw, maxh, max_pixels));
1894#endif
1895    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
1896	if (font_cache[ii].maxw == maxw &&
1897	    font_cache[ii].maxh == maxh &&
1898	    font_cache[ii].max_pixels == max_pixels &&
1899	    strcmp(font_cache[ii].fontname, fontname) == 0) {
1900	    cp = &font_cache[ii];
1901	    if (cp->font_data) {
1902		*w = cp->w;
1903		*h = cp->h;
1904		*xmin = cp->xmin;
1905		*ymin = cp->ymin;
1906		return cp->font_data;
1907	    }
1908	    break;
1909	}
1910    }
1911
1912    if (cp != NULL) {
1913	targeth = cp->targeth;
1914    } else {
1915	targeth = maxh * 8U + 5U;
1916    }
1917
1918#define MAX_TARGETH 720U	/* above this level, fontconfig chokes */
1919    if (targeth > MAX_TARGETH)
1920	targeth = MAX_TARGETH;
1921
1922    for (;;) {
1923	if (targeth <= 5U) {
1924	    TRACE(("Giving up finding suitable Xft font size for \"%s\" at %ux%u.\n",
1925		   fontname, maxw, maxh));
1926	    return NULL;
1927	}
1928
1929	/*
1930	 * Xft does a bad job at:
1931	 *  - two-color low-resolution anti-aliased fonts
1932	 *  - non-anti-aliased fonts at low resolution unless a font size is
1933	 *    given (pixel size does not help, and the value of the font size
1934	 *    doesn't appear to matter).
1935	 *
1936	 * In those two cases it literally drops pixels, sometimes whole
1937	 * columns, making the glyphs unreadable and at least ugly even when
1938	 * readable.
1939	 */
1940	font = NULL;
1941	/*
1942	 * FIXME:
1943	 * Also, we need to scale the width and height separately.  The
1944	 * CHAR_WIDTH and CHAR_HEIGHT attributes would seem to be ideal, but
1945	 * don't appear to have any effect if set.  Instead we will manually
1946	 * scale the bitmap later, which may be very ugly because we won't try
1947	 * to identify different parts of glyphs or preserve density.
1948	 */
1949	{
1950	    XftPattern *pat;
1951	    XftPattern *match;
1952	    XftResult status;
1953
1954	    if ((pat = XftNameParse(fontname)) != NULL) {
1955#ifdef DEBUG_FONT_SIZE_SEARCH
1956		TRACE(("trying targeth=%g\n", targeth / 10.0));
1957#endif
1958		XftPatternBuild(pat,
1959				XFT_PIXEL_SIZE, XftTypeDouble, (double)
1960				targeth / 10.0,
1961				XFT_SPACING, XftTypeInteger, XFT_MONO,
1962				XFT_SLANT, XftTypeInteger, 0,
1963				XFT_ANTIALIAS, XftTypeBool, False,
1964				NULL);
1965		if ((match = XftFontMatch(display,
1966					  XScreenNumberOfScreen(screen),
1967					  pat, &status)) != NULL) {
1968		    font = XftFontOpenPattern(display, match);
1969		    maybeXftCache(xw, font);
1970		}
1971		XftPatternDestroy(pat);
1972	    }
1973	}
1974	if (!font) {
1975	    TRACE(("unable to open a monospaced Xft font matching \"%s\" with pixelsize %g\n",
1976		   fontname, targeth / 10.0));
1977	    return NULL;
1978	}
1979#ifdef DEBUG_FONT_SIZE_SEARCH
1980	{
1981	    char buffer[1024];
1982
1983	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
1984		TRACE(("Testing font named \"%s\"\n", buffer));
1985	    } else {
1986		TRACE(("Testing unknown font\n"));
1987	    }
1988	}
1989#endif
1990
1991	if (cp != NULL &&
1992	    targeth == cp->targeth) {
1993	    *w = cp->w;
1994	    *h = cp->h;
1995	    *xmin = cp->xmin;
1996	    *ymin = cp->ymin;
1997	} else {
1998	    get_xft_glyph_dimensions(xw, font, w, h, xmin, ymin);
1999
2000	    if (*w < 1 || *h < 1) {
2001#ifdef DEBUG_FONT_SIZE_SEARCH
2002		TRACE(("got %ux%u dimensions for target size targeth=%d; trying reduced target size\n",
2003		       *w, *h, targeth));
2004#endif
2005		targeth--;
2006		continue;
2007	    }
2008	}
2009#ifdef DEBUG_FONT_SIZE_SEARCH
2010	TRACE(("checking max=%ux%u targeth=%u.%u\n", maxw, maxh, targeth /
2011	       10U, targeth % 10U));
2012#endif
2013
2014	if (*h > maxh) {
2015	    float ratio = (float) (*h) / (float) maxh;
2016	    XftFontClose(display, font);
2017#ifdef DEBUG_FONT_SIZE_SEARCH
2018	    TRACE(("got %ux%u glyph; too tall; reducing target size\n", *w, *h));
2019#endif
2020	    if (targeth >= 10U && ratio > 1.1) {
2021		targeth = (unsigned) ((float) targeth / ratio);
2022	    } else {
2023		targeth--;
2024	    }
2025	    continue;
2026	}
2027	if (*w > maxw) {
2028	    float ratio = (float) (*w) / (float) maxw;
2029	    XftFontClose(display, font);
2030#ifdef DEBUG_FONT_SIZE_SEARCH
2031	    TRACE(("got %ux%u glyph; too wide; reducing target size\n", *w, *h));
2032#endif
2033	    if (targeth >= 10U && ratio > 1.1) {
2034		targeth = (unsigned) ((float) targeth / ratio);
2035	    } else {
2036		targeth--;
2037	    }
2038	    continue;
2039	}
2040	if (*w * *h > max_pixels) {
2041	    XftFontClose(display, font);
2042#ifdef DEBUG_FONT_SIZE_SEARCH
2043	    TRACE(("got %ux%u glyph; too many pixels; reducing target size\n",
2044		   *w, *h));
2045#endif
2046	    if (*w * *h > 2U * max_pixels) {
2047		unsigned min = *w < *h ? *w : *h;
2048		unsigned divisor = (*w * *h) / (max_pixels * min);
2049		if (divisor > 1U) {
2050		    targeth /= divisor;
2051		} else if (targeth > 10U) {
2052		    targeth -= 10U;
2053		} else {
2054		    targeth--;
2055		}
2056	    } else {
2057		targeth--;
2058	    }
2059	    continue;
2060	}
2061#ifdef DEBUG_FONT_NAME
2062	{
2063	    char buffer[1024];
2064
2065	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
2066		TRACE(("Final font for \"%s\" max %dx%d is \"%s\"\n",
2067		       fontname, maxw, maxh, buffer));
2068	    } else {
2069		TRACE(("Final font for \"%s\" max %dx%d is unknown\n",
2070		       fontname, maxw, maxh));
2071	    }
2072	}
2073#endif
2074
2075	if (cp == NULL) {
2076	    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
2077		if (font_cache[ii].maxw == 0U ||
2078		    font_cache[ii].maxh == 0U ||
2079		    font_cache[ii].max_pixels == 0U) {
2080		    cp = &font_cache[ii];
2081		    break;
2082		}
2083	    }
2084	    if (cp == NULL) {
2085		ii = targeth % FONT_SIZE_CACHE_SIZE;
2086		cp = &font_cache[ii];
2087		close_xft_font(xw, cp->font_data);
2088	    }
2089	    CopyFontname(cp->fontname, fontname);
2090	    cp->maxw = maxw;
2091	    cp->maxh = maxh;
2092	    cp->max_pixels = max_pixels;
2093	    cp->targeth = targeth;
2094	    cp->w = *w;
2095	    cp->h = *h;
2096	    cp->xmin = *xmin;
2097	    cp->ymin = *ymin;
2098	}
2099	if (cp != NULL) {
2100	    cp->font_data = font;
2101	}
2102	return font;
2103    }
2104}
2105#endif
2106
2107static int
2108get_xft_bitmap_of_character(RegisGraphicsContext const *context,
2109			    char const *fontname, int ch,
2110			    unsigned maxw, unsigned maxh, Char *pixels,
2111			    unsigned max_pixels, unsigned *w, unsigned *h)
2112{
2113    /*
2114     * See Xft / RENDERFONT stuff in fontutils.c and used in utils.c
2115     * Add a separate configuration for ReGIS.
2116     */
2117    /*
2118     * FIXME: cache:
2119     * - reuse the font where possible
2120     */
2121#ifdef XRENDERFONT
2122    XtermWidget xw = context->destination_graphic->xw;
2123    XftFont *font;
2124    unsigned xmin = 0U, ymin = 0U;
2125
2126# ifdef DEBUG_XFT_GLYPH_LOADING
2127    TRACE(("trying to load glyph '%c' at max size %dx%d\n", ch, maxw, maxh));
2128# endif
2129    if (!(font = find_best_xft_font_size(xw, fontname, maxw, maxh,
2130					 max_pixels, w, h, &xmin, &ymin))) {
2131	TRACE(("Unable to find suitable Xft font\n"));
2132	return 0;
2133    }
2134
2135    if (*w == 0U || *h == 0U) {
2136	TRACE(("empty glyph found for '%c'\n", ch));
2137	close_xft_font(xw, font);
2138	return 1;
2139    }
2140
2141    if (!copy_bitmap_from_xft_font(xw, font, CharOf(ch), pixels, *w, *h,
2142				   xmin, ymin)) {
2143	TRACE(("Unable to create bitmap for '%c'\n", ch));
2144	close_xft_font(xw, font);
2145	return 0;
2146    }
2147# ifdef DEBUG_XFT_GLYPH_LOADING
2148    TRACE(("loaded glyph '%c' at max size %dx%d\n", ch, maxw, maxh));
2149# endif
2150
2151    return 1;
2152#else
2153    (void) context;
2154    (void) fontname;
2155    (void) ch;
2156    (void) maxw;
2157    (void) maxh;
2158    (void) pixels;
2159    (void) max_pixels;
2160    (void) w;
2161    (void) h;
2162
2163    TRACE(("Not rendering Xft font for ReGIS (support not compiled in).\n"));
2164    return 0;
2165#endif
2166}
2167
2168static unsigned
2169find_best_alphabet_index(RegisGraphicsContext const *context,
2170			 unsigned minw, unsigned minh,
2171			 unsigned targetw, unsigned targeth,
2172			 unsigned max_pixels)
2173{
2174    unsigned ii;
2175    unsigned bestmatch;
2176    unsigned bestw, besth;
2177
2178    assert(context);
2179    assert(targetw);
2180    assert(targeth);
2181    assert(max_pixels);
2182
2183    bestmatch = MAX_REGIS_ALPHABETS;
2184    bestw = 0U;
2185    besth = 0U;
2186    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
2187	if (context->alphabets[ii].alphabet_num ==
2188	    context->current_text_controls->alphabet_num &&
2189	    context->alphabets[ii].pixw >= minw &&
2190	    context->alphabets[ii].pixh >= minh &&
2191	    context->alphabets[ii].pixw <= targetw &&
2192	    context->alphabets[ii].pixh <= targeth &&
2193	    ((context->alphabets[ii].pixw >= bestw &&
2194	      context->alphabets[ii].pixh > besth) ||
2195	     (context->alphabets[ii].pixw > bestw &&
2196	      context->alphabets[ii].pixh >= besth)) &&
2197	    context->alphabets[ii].pixw *
2198	    context->alphabets[ii].pixh <= max_pixels) {
2199	    bestmatch = ii;
2200	    bestw = context->alphabets[ii].pixw;
2201	    besth = context->alphabets[ii].pixh;
2202	}
2203    }
2204
2205    /* If we can't find one to scale up, look for one to scale down. */
2206    if (bestmatch == MAX_REGIS_ALPHABETS) {
2207	bestw = max_pixels;
2208	besth = max_pixels;
2209	for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
2210	    if (context->alphabets[ii].alphabet_num ==
2211		context->current_text_controls->alphabet_num &&
2212		context->alphabets[ii].pixw >= minw &&
2213		context->alphabets[ii].pixh >= minh &&
2214		((context->alphabets[ii].pixw <= bestw &&
2215		  context->alphabets[ii].pixh < besth) ||
2216		 (context->alphabets[ii].pixw < bestw &&
2217		  context->alphabets[ii].pixh <= besth)) &&
2218		context->alphabets[ii].pixw *
2219		context->alphabets[ii].pixh <= max_pixels) {
2220		bestmatch = ii;
2221		bestw = context->alphabets[ii].pixw;
2222		besth = context->alphabets[ii].pixh;
2223	    }
2224	}
2225    }
2226#ifdef DEBUG_ALPHABET_LOOKUP
2227    if (bestmatch < MAX_REGIS_ALPHABETS) {
2228	TRACE(("for target size %ux%u alphabet %u found index %u size %ux%u font=%s\n",
2229	       targetw, targeth, context->current_text_controls->alphabet_num,
2230	       bestmatch,
2231	       bestw, besth,
2232	       context->alphabets[bestmatch].use_font ?
2233	       context->alphabets[bestmatch].fontname : "(none)"));
2234    } else {
2235	TRACE(("for target size %ux%u alphabet %u found no suitable alphabets\n",
2236	       targetw, targeth, context->current_text_controls->alphabet_num));
2237    }
2238#endif
2239
2240    return bestmatch;
2241}
2242
2243#define GLYPH_WIDTH_BYTES(PIXW) ( ((PIXW) + 7U) >> 3U )
2244
2245static int
2246get_user_bitmap_of_character(RegisGraphicsContext const *context,
2247			     int ch,
2248			     unsigned alphabet_index,
2249			     Char *pixels,
2250			     unsigned int max_pixels)
2251{
2252    const Char *glyph;
2253    unsigned w, h;
2254    unsigned xx, yy;
2255    unsigned byte, bit;
2256
2257    assert(context);
2258    assert(pixels);
2259
2260    if (!context->alphabets[alphabet_index].loaded[(Char) ch]) {
2261	TRACE(("BUG: in alphabet %u with alphabet index %u user glyph for '%c' not loaded\n",
2262	       context->current_text_controls->alphabet_num, alphabet_index,
2263	       ch));
2264	return 0;
2265    }
2266
2267    assert(context->alphabets[alphabet_index].bytes);
2268
2269    w = context->alphabets[alphabet_index].pixw;
2270    h = context->alphabets[alphabet_index].pixh;
2271    glyph = &context->alphabets[alphabet_index]
2272	.bytes[(Char) ch * GLYPH_WIDTH_BYTES(w) * h];
2273
2274    if (w * h > max_pixels) {
2275	TRACE(("in alphabet %u with alphabet index %u user glyph for '%c' is too large: %ux%u (max_pixels=%u)\n",
2276	       context->current_text_controls->alphabet_num, alphabet_index,
2277	       ch, w, h, max_pixels));
2278	return 0;
2279    }
2280
2281    for (yy = 0U; yy < h; yy++) {
2282	for (xx = 0U; xx < w; xx++) {
2283	    byte = yy * GLYPH_WIDTH_BYTES(w) + (xx >> 3U);
2284	    bit = xx & 7U;
2285	    pixels[yy * w + xx] = (Char) (((unsigned) glyph[byte]
2286					   >> (7U - bit)) & 1U);
2287	}
2288    }
2289
2290    return 1;
2291}
2292
2293/*
2294 * alphabets
2295 *  0    built-in
2296 *  1-N  custom (max is 3 on VT3X0 -- up to MAX_REGIS_ALPHABETS with xterm)
2297 *
2298 * built-in 7-bit charsets
2299 *  (B    ASCII
2300 *  (0    DEC special graphics
2301 *  (>    DEC technical
2302 *  (A    NCR British
2303 *  (4    NCR Dutch
2304 *  (5    NCR Finnish
2305 *  (R    NCR French
2306 *  (9    NCR French Canadian
2307 *  (K    NCR German
2308 *  (Y    NCR Italian
2309 *  ('    NCR Norwegian/Danish
2310 *  (!6   NCR Portuguese
2311 *  (Z    NCR Spanish
2312 *  (7    NCR Swedish
2313 *  (-    NCR Swiss
2314 *
2315 * -@   ???
2316 *
2317 * built-in 8-bit charsets
2318 *  )%5   DEC supplemental graphics
2319 *  -A    ISO Latin-1 supplemental
2320 *  )<    user-preferred supplemental (94 chars)
2321 *
2322 * defaults
2323 *  terminal  char cell size   charsets      angle
2324 *  VT3x0     S1               0:ASCII(94)   0 (positive)
2325 *
2326 */
2327static void
2328get_bitmap_of_character(RegisGraphicsContext const *context, int ch,
2329			unsigned maxw, unsigned maxh, Char *pixels,
2330			unsigned *w, unsigned *h, unsigned max_pixels)
2331{
2332    unsigned bestmatch;
2333    char const *fontname = NULL;
2334
2335    assert(context);
2336    assert(w);
2337    assert(h);
2338
2339#ifdef DEBUG_GLYPH_RETRIEVAL
2340    TRACE(("getting bitmap of glyph %d, current alphabet %d\n", ch,
2341	   context->current_text_controls->alphabet_num));
2342#endif
2343
2344    if (maxw < 1U || maxh < 1U || max_pixels < 1U) {
2345	*w = 0U;
2346	*h = 0U;
2347	return;
2348    }
2349
2350    if (context->current_text_controls->alphabet_num == 0)
2351	fontname = context->builtin_font;
2352
2353    *w = 0U;
2354    *h = 0U;
2355
2356    bestmatch = find_best_alphabet_index(context, 1U, 1U, maxw, maxh,
2357					 max_pixels);
2358    if (bestmatch < MAX_REGIS_ALPHABETS) {
2359	RegisAlphabet const *alpha = &context->alphabets[bestmatch];
2360
2361#ifdef DEBUG_GLYPH_RETRIEVAL
2362	TRACE(("checking user glyph for slot=%u alphabet=%d use_font=%d loaded=%d\n",
2363	       bestmatch, alpha->alphabet_num, alpha->use_font,
2364	       alpha->loaded[ch]));
2365#endif
2366	if (!alpha->use_font &&
2367	    get_user_bitmap_of_character(context, ch, bestmatch, pixels,
2368					 max_pixels)) {
2369#ifdef DEBUG_GLYPH_RETRIEVAL
2370	    TRACE(("found user glyph for alphabet number %d (index %u)\n\n",
2371		   alpha->alphabet_num, bestmatch));
2372#endif
2373	    *w = alpha->pixw;
2374	    *h = alpha->pixh;
2375	    return;
2376	}
2377
2378	if (alpha->use_font)
2379	    fontname = alpha->fontname;
2380    }
2381
2382    if (fontname) {
2383#ifdef DEBUG_GLYPH_RETRIEVAL
2384	TRACE(("using xft font \"%s\"\n", fontname));
2385#endif
2386	if (get_xft_bitmap_of_character(context, fontname, ch,
2387					maxw, maxh, pixels,
2388					max_pixels, w, h)) {
2389	    if (*w > maxw) {
2390		TRACE(("BUG: Xft glyph is too wide: %ux%u but max is %ux%u\n",
2391		       *w, *h, maxw, maxh));
2392	    } else if (*h > maxh) {
2393		TRACE(("BUG: Xft glyph is too tall: %ux%u but max is %ux%u\n",
2394		       *w, *h, maxw, maxh));
2395	    } else if (*w * *h > max_pixels) {
2396		TRACE(("BUG: Xft glyph has too many pixels: %u but max is %u\n",
2397		       *w * *h, max_pixels));
2398	    } else {
2399		TRACE(("got glyph from \"%s\" for alphabet number %d\n",
2400		       fontname, context->current_text_controls->alphabet_num));
2401#ifdef DEBUG_SPECIFIC_CHAR_METRICS
2402		if (IS_DEBUG_CHAR(ch)) {
2403		    printf("got %ux%u Xft bitmap for '%c' target size %ux%u:\n",
2404			   *w, *h,
2405			   ch, maxw, maxh);
2406		    dump_bitmap_pixels(pixels, *w, *h);
2407		    printf("\n");
2408		}
2409#endif
2410		return;
2411	    }
2412	}
2413    }
2414
2415    TRACE(("unable to load any bitmap for character '%c' in alphabet number %u at %ux%u\n",
2416	   ch, context->current_text_controls->alphabet_num, maxw, maxh));
2417
2418    /*
2419     * The VT3x0 series (and probably earlier ReGIS implementations) use a solid
2420     * block glyph for unknown glyphs.
2421     */
2422    {
2423	unsigned xx, yy;
2424
2425	*w = MIN2(8U, maxh);
2426	*h = MIN2(10U, maxw);
2427	for (yy = 0U; yy < *h; yy++)
2428	    for (xx = 0U; xx < *w; xx++)
2429		pixels[yy * *w + xx] = '\1';
2430    }
2431}
2432
2433#define ROT_SHEAR_SCALE 8192
2434#define SIGNED_UNSIGNED_MOD(VAL, BASE) ( (((VAL) % (int) (BASE)) + (int) (BASE)) % (int) (BASE) )
2435
2436static unsigned
2437get_shade_character_pixel(Char const *pixels, unsigned w, unsigned h,
2438			  unsigned smaxf, unsigned scale, int slant_dx,
2439			  int px, int py)
2440{
2441    unsigned wx, wy;
2442    unsigned fx, fy;
2443
2444    wx = (unsigned) SIGNED_UNSIGNED_MOD(px -
2445					(slant_dx * SIGNED_UNSIGNED_MOD(py, smaxf))
2446					/ ROT_SHEAR_SCALE, smaxf);
2447    wy = (unsigned) SIGNED_UNSIGNED_MOD(py, smaxf);
2448
2449    fx = (wx * scale) >> SCALE_FIXED_POINT;
2450    fy = (wy * scale) >> SCALE_FIXED_POINT;
2451    if (fx < w && fy < h) {
2452	return (unsigned) pixels[fy * w + fx];
2453    }
2454    return 0U;
2455}
2456
2457static void
2458draw_character(RegisGraphicsContext *context, int ch,
2459	       int slant_dx, int rot_shear_x,
2460	       int rot_shear_y, int x_sign_x, int x_sign_y,
2461	       int y_sign_x, int y_sign_y)
2462{
2463    const unsigned xmaxd = context->current_text_controls->character_display_w;
2464    const unsigned ymaxd = context->current_text_controls->character_display_h;
2465    const unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
2466    const unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
2467    unsigned w, h;
2468    unsigned xscale, yscale;
2469    unsigned fx, fy;
2470    unsigned px, py;
2471    int sx;
2472    int rx, ry;
2473    int ox, oy;
2474    unsigned pad_left, pad_right;
2475    unsigned pad_top, pad_bottom;
2476    Char pixels[MAX_GLYPH_PIXELS];
2477    unsigned value;
2478
2479    get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
2480			    MAX_GLYPH_PIXELS);
2481    if (w < 1 || h < 1) {
2482	return;
2483    }
2484
2485    if (xmaxd > xmaxf) {
2486	pad_left = (xmaxd - xmaxf) / 2U;
2487	pad_right = (xmaxd - xmaxf) - pad_left;
2488    } else {
2489	pad_left = 0U;
2490	pad_right = 0U;
2491    }
2492    if (ymaxd > ymaxf) {
2493	pad_top = (ymaxd - ymaxf) / 2U;
2494	pad_bottom = (ymaxd - ymaxf) - pad_top;
2495    } else {
2496	pad_top = 0U;
2497	pad_bottom = 0U;
2498    }
2499
2500    xscale = (w << SCALE_FIXED_POINT) / xmaxf;
2501    yscale = (h << SCALE_FIXED_POINT) / ymaxf;
2502
2503    for (py = 0U; py < ymaxd; py++) {
2504	for (px = 0U; px < xmaxd; px++) {
2505	    if (py < pad_top || px < pad_left ||
2506		py >= ymaxd - pad_bottom || px >= xmaxd - pad_right) {
2507		value = 0U;
2508	    } else {
2509		fx = ((px - pad_left) * xscale) >> SCALE_FIXED_POINT;
2510		fy = ((py - pad_top) * yscale) >> SCALE_FIXED_POINT;
2511		if (fx < w && fy < h) {
2512		    value = (unsigned) pixels[fy * w + fx];
2513		} else {
2514		    value = 0U;
2515		}
2516	    }
2517
2518	    sx = (int) px + (slant_dx * (int) py) / ROT_SHEAR_SCALE;
2519	    rx = x_sign_x * sx + x_sign_y * (int) py;
2520	    ry = y_sign_x * sx + y_sign_y * (int) py;
2521	    ox = rx + (rot_shear_x * ry) / ROT_SHEAR_SCALE;
2522	    oy = ry + (rot_shear_y * ox) / ROT_SHEAR_SCALE;
2523	    ox += (rot_shear_x * oy) / ROT_SHEAR_SCALE;
2524
2525	    draw_regis_pixel(context,
2526			     (int) context->graphics_output_cursor_x + ox,
2527			     (int) context->graphics_output_cursor_y + oy,
2528			     value);
2529	}
2530    }
2531}
2532
2533static void
2534move_text(RegisGraphicsContext *context, int dx, int dy)
2535{
2536    double total_rotation;
2537    int str_invert;
2538    int str_shear_x, str_shear_y;
2539    int ox, oy;
2540
2541    total_rotation = 2.0 * M_PI *
2542	context->current_text_controls->string_rotation / 360.0;
2543    while (total_rotation > 1.5 * M_PI) {
2544	total_rotation -= 2.0 * M_PI;
2545    }
2546    if (total_rotation > 0.5 * M_PI) {
2547	total_rotation -= M_PI;
2548	str_invert = -1;
2549    } else {
2550	str_invert = 1;
2551    }
2552    str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
2553    str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
2554
2555    total_rotation = 2.0 * M_PI *
2556	context->current_text_controls->character_rotation / 360.0;
2557    while (total_rotation > 1.5 * M_PI) {
2558	total_rotation -= 2.0 * M_PI;
2559    }
2560
2561    TRACE(("str_shear: %.5f, %.5f (sign=%d)\n",
2562	   str_shear_x / (double) ROT_SHEAR_SCALE,
2563	   str_shear_y / (double) ROT_SHEAR_SCALE,
2564	   str_invert));
2565
2566    ox = str_invert * dx + (str_shear_x * dy) / ROT_SHEAR_SCALE;
2567    oy = str_invert * dy + (str_shear_y * ox) / ROT_SHEAR_SCALE;
2568    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
2569
2570    TRACE(("after pv output updating position %+d,%+d\n", ox, oy));
2571    context->graphics_output_cursor_x += ox;
2572    context->graphics_output_cursor_y += oy;
2573
2574    return;
2575}
2576
2577#define UPSCALE_TEXT_DIMENSION(D) do { \
2578	    *(D) = (unsigned)((double)(*(D)) * M_SQRT2); \
2579	} while (0)
2580
2581static void
2582draw_text(RegisGraphicsContext *context, char const *str)
2583{
2584#ifndef ENABLE_DISTORTIONLESS_ROTATION
2585    RegisTextControls *old_text_controls = NULL;
2586    static RegisTextControls scratch_text_controls;
2587#endif
2588    double total_rotation;
2589    size_t ii;
2590    int str_invert;
2591    int str_shear_x, str_shear_y;
2592    int slant_dx;
2593    int chr_x_sign_x, chr_x_sign_y;
2594    int chr_y_sign_x, chr_y_sign_y;
2595    int chr_shear_x, chr_shear_y;
2596    int begin_x, begin_y;
2597    int rx, ry;
2598    int ox, oy;
2599
2600#ifdef DEBUG_ALPHABETS
2601    {
2602	unsigned n;
2603
2604	for (n = 0U; n < MAX_REGIS_ALPHABETS; n++) {
2605	    printf("alphabet index %u\n", n);
2606	    if (context->alphabets[n].alphabet_num != INVALID_ALPHABET_NUM) {
2607		printf(" alphabet_num=%u\n", context->alphabets[n].alphabet_num);
2608		printf(" pixw=%d\n", context->alphabets[n].pixw);
2609		printf(" pixh=%d\n", context->alphabets[n].pixh);
2610		printf(" name=\"%s\"\n", context->alphabets[n].name);
2611		printf(" use_font=%d\n", context->alphabets[n].use_font);
2612		printf(" fontname=\"%s\"\n", context->alphabets[n].fontname);
2613		printf(" bytes=%p\n", context->alphabets[n].bytes);
2614	    }
2615	}
2616    }
2617#endif
2618
2619    if (context->current_text_controls->slant <= -75 ||
2620	context->current_text_controls->slant >= +75) {
2621	TRACE(("ERROR: unsupported character slant angle %d\n",
2622	       context->current_text_controls->slant));
2623	return;
2624    }
2625
2626    /* FIXME: grab when first entering command */
2627    begin_x = context->graphics_output_cursor_x;
2628    begin_y = context->graphics_output_cursor_y;
2629
2630#ifndef ENABLE_DISTORTIONLESS_ROTATION
2631    if (context->current_text_controls->character_rotation != 0 &&
2632	context->current_text_controls->character_rotation != 90 &&
2633	context->current_text_controls->character_rotation != 180 &&
2634	context->current_text_controls->character_rotation != 270) {
2635	old_text_controls = context->current_text_controls;
2636	scratch_text_controls = *context->current_text_controls;
2637	UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_display_w);
2638	UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_display_h);
2639	/* FIXME: Not sure if this is really scaled.  The increment seems to
2640	 * _not_ be scaled.
2641	 */
2642	UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_unit_cell_w);
2643	UPSCALE_TEXT_DIMENSION(&scratch_text_controls.character_unit_cell_h);
2644	context->current_text_controls = &scratch_text_controls;
2645	TRACE(("scaled up text to %dx%d\n",
2646	       scratch_text_controls.character_display_w,
2647	       scratch_text_controls.character_display_h));
2648    }
2649#endif
2650
2651    total_rotation = 2.0 * M_PI *
2652	context->current_text_controls->string_rotation / 360.0;
2653    while (total_rotation > 1.5 * M_PI) {
2654	total_rotation -= 2.0 * M_PI;
2655    }
2656    if (total_rotation > 0.5 * M_PI) {
2657	total_rotation -= M_PI;
2658	str_invert = -1;
2659    } else {
2660	str_invert = 1;
2661    }
2662    str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
2663    str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
2664
2665    total_rotation = 2.0 * M_PI *
2666	context->current_text_controls->character_rotation / 360.0;
2667    while (total_rotation > 1.5 * M_PI) {
2668	total_rotation -= 2.0 * M_PI;
2669    }
2670    if (total_rotation > 0.5 * M_PI) {
2671	total_rotation -= M_PI;
2672	chr_x_sign_x = -1;
2673	chr_x_sign_y = 0;
2674	chr_y_sign_x = 0;
2675	chr_y_sign_y = -1;
2676    } else {
2677	chr_x_sign_x = 1;
2678	chr_x_sign_y = 0;
2679	chr_y_sign_x = 0;
2680	chr_y_sign_y = 1;
2681    }
2682    chr_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
2683    chr_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
2684
2685    {
2686	const int slant = context->current_text_controls->slant;
2687
2688	TRACE(("float version: %.5f\n", tan(2.0 * M_PI * abs(slant) / 360.0)));
2689	/* The slant is negative for forward-leaning characters. */
2690	if (slant > 0) {
2691	    slant_dx = (int) +(tan(2.0 * M_PI * abs(slant) / 360.0) * ROT_SHEAR_SCALE);
2692	} else if (slant < 0) {
2693	    slant_dx = (int) -(tan(2.0 * M_PI * abs(slant) / 360.0) * ROT_SHEAR_SCALE);
2694	} else {
2695	    slant_dx = 0;
2696	}
2697	TRACE(("string rotation: %d\n",
2698	       context->current_text_controls->string_rotation));
2699	TRACE(("character rotation: %d\n",
2700	       context->current_text_controls->character_rotation));
2701	TRACE(("character slant: %d (%.5f pixels per line)\n",
2702	       slant, slant_dx / (double) ROT_SHEAR_SCALE));
2703    }
2704
2705    TRACE(("str_shear: %.5f, %.5f (sign=%d)\n",
2706	   str_shear_x / (double) ROT_SHEAR_SCALE,
2707	   str_shear_y / (double) ROT_SHEAR_SCALE,
2708	   str_invert));
2709    TRACE(("chr_shear: %.5f, %.5f (xsign=%d,%d, ysign=%d,%d)\n",
2710	   chr_shear_x / (double) ROT_SHEAR_SCALE,
2711	   chr_shear_y / (double) ROT_SHEAR_SCALE,
2712	   chr_x_sign_x, chr_x_sign_y,
2713	   chr_y_sign_x, chr_y_sign_y));
2714    TRACE(("character_inc: %d,%d\n",
2715	   context->current_text_controls->character_inc_x, context->current_text_controls->character_inc_y));
2716
2717    rx = 0;
2718    ry = 0;
2719    for (ii = 0U; ii < strlen(str); ii++) {
2720	switch (str[ii]) {
2721	case '\r':
2722	    rx = 0;
2723	    break;
2724	case '\n':
2725	    ry += (int) context->current_text_controls->character_display_h;
2726	    break;
2727	case '\b':
2728	    rx -= context->current_text_controls->character_inc_x;
2729	    ry -= context->current_text_controls->character_inc_y;
2730	    break;
2731	case '\t':
2732	    rx += context->current_text_controls->character_inc_x;
2733	    ry += context->current_text_controls->character_inc_y;
2734	    break;
2735	default:
2736	    ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
2737	    oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
2738	    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
2739	    TRACE(("during text output updating position to %d,%d + %+d,%+d for '%c'\n",
2740		   begin_x, begin_y, ox, oy, str[ii]));
2741	    context->graphics_output_cursor_x = begin_x + ox;
2742	    context->graphics_output_cursor_y = begin_y + oy;
2743	    draw_character(context, str[ii], slant_dx,
2744			   chr_shear_x, chr_shear_y,
2745			   chr_x_sign_x, chr_x_sign_y,
2746			   chr_y_sign_x, chr_y_sign_y);
2747	    rx += context->current_text_controls->character_inc_x;
2748	    ry += context->current_text_controls->character_inc_y;
2749	}
2750    }
2751
2752    ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
2753    oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
2754    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
2755    TRACE(("after text output updating position to %d,%d + %+d,%+d\n",
2756	   begin_x, begin_y, ox, oy));
2757    context->graphics_output_cursor_x = begin_x + ox;
2758    context->graphics_output_cursor_y = begin_y + oy;
2759
2760#ifndef ENABLE_DISTORTIONLESS_ROTATION
2761    if (context->current_text_controls->character_rotation != 0 &&
2762	context->current_text_controls->character_rotation != 90 &&
2763	context->current_text_controls->character_rotation != 180 &&
2764	context->current_text_controls->character_rotation != 270) {
2765	context->current_text_controls = old_text_controls;
2766    }
2767#endif
2768
2769    context->destination_graphic->dirty = True;
2770    return;
2771}
2772
2773/*
2774 * standard character cell sizes
2775 *   number  disp cell   unit cell       offset
2776 *   S0      [  9, 10]   [  8, disp_h]   [disp_w, 0]
2777 *   S1      [  9, 20]   [  8, disp_h]   [disp_w, 0]
2778 *   S2      [ 18, 30]   [ 16, disp_h]   [disp_w, 0]
2779 *   S3      [ 27, 45]   [ 24, disp_h]   [disp_w, 0]
2780 *   S4      [ 36, 60]   [ 32, disp_h]   [disp_w, 0]
2781 *   S5      [ 45, 75]   [ 40, disp_h]   [disp_w, 0]
2782 *   S6      [ 54, 90]   [ 48, disp_h]   [disp_w, 0]
2783 *   S7      [ 63,105]   [ 56, disp_h]   [disp_w, 0]
2784 *   S8      [ 72,120]   [ 64, disp_h]   [disp_w, 0]
2785 *   S9      [ 81,135]   [ 72, disp_h]   [disp_w, 0]
2786 *   S10     [ 90,150]   [ 80, disp_h]   [disp_w, 0]
2787 *   S11     [ 99,165]   [ 88, disp_h]   [disp_w, 0]
2788 *   S12     [108,180]   [ 96, disp_h]   [disp_w, 0]
2789 *   S13     [117,195]   [104, disp_h]   [disp_w, 0]
2790 *   S14     [126,210]   [112, disp_h]   [disp_w, 0]
2791 *   S15     [135,225]   [120, disp_h]   [disp_w, 0]
2792 *   S16     [144,240]   [128, disp_h]   [disp_w, 0]
2793 */
2794static int
2795get_standard_character_size(int standard, unsigned *disp_w, unsigned
2796			    *disp_h, unsigned *unit_w, unsigned *unit_h,
2797			    int *off_x, int *off_y)
2798{
2799    switch (standard) {
2800    case 0:
2801	*disp_w = 9U;
2802	*disp_h = 10U;
2803	*unit_w = 8U;
2804	break;
2805    case 1:
2806	*disp_w = 9U;
2807	*disp_h = 20U;
2808	*unit_w = 8U;
2809	break;
2810    case 2:
2811	*disp_w = 18U;
2812	*disp_h = 30U;
2813	*unit_w = 16U;
2814	break;
2815    case 3:
2816	*disp_w = 27U;
2817	*disp_h = 45U;
2818	*unit_w = 24U;
2819	break;
2820    case 4:
2821	*disp_w = 36U;
2822	*disp_h = 60U;
2823	*unit_w = 32U;
2824	break;
2825    case 5:
2826	*disp_w = 45U;
2827	*disp_h = 75U;
2828	*unit_w = 40U;
2829	break;
2830    case 6:
2831	*disp_w = 54U;
2832	*disp_h = 90U;
2833	*unit_w = 48U;
2834	break;
2835    case 7:
2836	*disp_w = 63U;
2837	*disp_h = 105U;
2838	*unit_w = 56U;
2839	break;
2840    case 8:
2841	*disp_w = 72U;
2842	*disp_h = 120U;
2843	*unit_w = 64U;
2844	break;
2845    case 9:
2846	*disp_w = 81U;
2847	*disp_h = 135U;
2848	*unit_w = 72U;
2849	break;
2850    case 10:
2851	*disp_w = 90U;
2852	*disp_h = 150U;
2853	*unit_w = 80U;
2854	break;
2855    case 11:
2856	*disp_w = 99U;
2857	*disp_h = 165U;
2858	*unit_w = 88U;
2859	break;
2860    case 12:
2861	*disp_w = 108U;
2862	*disp_h = 180U;
2863	*unit_w = 96U;
2864	break;
2865    case 13:
2866	*disp_w = 117U;
2867	*disp_h = 195U;
2868	*unit_w = 104U;
2869	break;
2870    case 14:
2871	*disp_w = 126U;
2872	*disp_h = 210U;
2873	*unit_w = 112U;
2874	break;
2875    case 15:
2876	*disp_w = 135U;
2877	*disp_h = 225U;
2878	*unit_w = 120U;
2879	break;
2880    case 16:
2881	*disp_w = 144U;
2882	*disp_h = 240U;
2883	*unit_w = 128U;
2884	break;
2885    default:
2886	return 1;
2887    }
2888    *unit_h = *disp_h;
2889    *off_x = (int) *disp_w;
2890    *off_y = 0;
2891
2892    return 0;
2893}
2894
2895static void
2896init_fragment(RegisDataFragment *fragment, char const *str)
2897{
2898    assert(fragment);
2899    assert(str);
2900
2901    fragment->start = str;
2902    fragment->len = (unsigned) strlen(str);
2903    fragment->pos = 0U;
2904}
2905
2906static void
2907copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src)
2908{
2909    assert(dst);
2910    assert(src);
2911
2912    dst->start = src->start;
2913    dst->len = src->len;
2914    dst->pos = src->pos;
2915}
2916
2917static char
2918peek_fragment(RegisDataFragment const *fragment)
2919{
2920    assert(fragment);
2921
2922    if (fragment->pos < fragment->len) {
2923	return fragment->start[fragment->pos];
2924    }
2925    return '\0';
2926}
2927
2928static char
2929pop_fragment(RegisDataFragment *fragment)
2930{
2931    assert(fragment);
2932
2933    if (fragment->pos < fragment->len) {
2934	return fragment->start[fragment->pos++];
2935    }
2936    return '\0';
2937}
2938
2939static char
2940get_fragment(RegisDataFragment const *fragment, unsigned pos)
2941{
2942    assert(fragment);
2943
2944    if (fragment->pos + pos < fragment->len) {
2945	return fragment->start[fragment->pos + pos];
2946    }
2947    return '\0';
2948}
2949
2950#define fragment_length(f) (f)->len
2951
2952static unsigned
2953fragment_remaining(RegisDataFragment const *fragment)
2954{
2955    assert(fragment);
2956
2957    if (fragment->pos > fragment->len)
2958	return 0U;
2959    return fragment->len - fragment->pos;
2960}
2961
2962static int
2963fragment_consumed(RegisDataFragment const *fragment)
2964{
2965    assert(fragment);
2966
2967    return fragment->pos >= fragment->len;
2968}
2969
2970static void
2971fragment_to_string(RegisDataFragment const *fragment, char *out,
2972		   unsigned outlen)
2973{
2974    unsigned remaininglen;
2975    unsigned endpos;
2976
2977    assert(fragment);
2978    assert(out);
2979
2980    if (!outlen)
2981	return;
2982    remaininglen = fragment->len - fragment->pos;
2983    if (remaininglen < outlen - 1U) {
2984	endpos = remaininglen;
2985    } else {
2986	endpos = outlen - 1U;
2987    }
2988    strncpy(out, &fragment->start[fragment->pos], (size_t) endpos);
2989    out[endpos] = '\0';
2990}
2991
2992#define MAX_FRAG 1024
2993static char const *
2994fragment_to_tempstr(RegisDataFragment const *fragment)
2995{
2996    static char tempstr[MAX_FRAG];
2997
2998    assert(fragment);
2999
3000    fragment_to_string(fragment, tempstr, MAX_FRAG);
3001    return tempstr;
3002}
3003
3004static int
3005skip_regis_whitespace(RegisDataFragment *input)
3006{
3007    int skipped = 0;
3008
3009    assert(input);
3010
3011    while (!fragment_consumed(input)) {
3012	char ch = peek_fragment(input);
3013	if (ch != ',' && !IsSpace(ch)) {
3014	    break;
3015	}
3016	if (ch == '\n') {
3017	    TRACE(("end of input line\n\n"));
3018	}
3019	skipped = 1;
3020	pop_fragment(input);
3021    }
3022
3023    if (skipped)
3024	return 1;
3025    return 0;
3026}
3027
3028static int
3029extract_regis_extent(RegisDataFragment *input, RegisDataFragment *output)
3030{
3031    char ch;
3032
3033    assert(input);
3034    assert(output);
3035
3036    output->start = &input->start[input->pos];
3037    output->len = 0U;
3038    output->pos = 0U;
3039
3040    if (input->pos >= input->len)
3041	return 0;
3042
3043    ch = input->start[input->pos];
3044    if (ch != '[')
3045	return 0;
3046    input->pos++;
3047    output->start++;
3048
3049    /* FIXME: truncate to 16 bit signed integers */
3050    for (; input->pos < input->len; input->pos++, output->len++) {
3051	ch = input->start[input->pos];
3052	if (ch == ';') {
3053	    TRACE(("DATA_ERROR: end of input before closing bracket\n"));
3054	    break;
3055	}
3056	if (ch == ']')
3057	    break;
3058    }
3059    if (ch == ']')
3060	input->pos++;
3061
3062    return 1;
3063}
3064
3065static int
3066extract_regis_num(RegisDataFragment *input, RegisDataFragment *output)
3067{
3068    char ch = 0;
3069    int has_digits = 0;
3070
3071    assert(input);
3072    assert(output);
3073
3074    output->start = &input->start[input->pos];
3075    output->len = 0U;
3076    output->pos = 0U;
3077
3078    if (input->start[input->pos] == '-' ||
3079	input->start[input->pos] == '+') {
3080	input->pos++;
3081	output->len++;
3082    }
3083
3084    for (; input->pos < input->len; input->pos++, output->len++) {
3085	ch = input->start[input->pos];
3086	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
3087	    ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
3088	    ch != '8' && ch != '9') {
3089	    break;
3090	}
3091	has_digits = 1;
3092    }
3093
3094    /* FIXME: what degenerate forms should be accepted ("E10" "1E" "1e" "1." "1ee10")? */
3095    /* FIXME: the terminal is said to support "floating point values", truncating to int... what do these look like? */
3096    if (has_digits && ch == 'E') {
3097	input->pos++;
3098	output->len++;
3099	for (; input->pos < input->len; input->pos++, output->len++) {
3100	    ch = input->start[input->pos];
3101	    if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
3102		ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
3103		ch != '8' && ch != '9') {
3104		break;
3105	    }
3106	}
3107    }
3108
3109    return has_digits;
3110}
3111
3112static int
3113extract_regis_pixelvector(RegisDataFragment *input, RegisDataFragment *output)
3114{
3115    char ch;
3116    int has_digits;
3117
3118    assert(input);
3119    assert(output);
3120
3121    output->start = &input->start[input->pos];
3122    output->len = 0U;
3123    output->pos = 0U;
3124
3125    if (input->pos < input->len) {
3126	ch = input->start[input->pos];
3127	if (ch == '+' || ch == '-') {
3128	    input->pos++;
3129	    output->len++;
3130	}
3131    }
3132
3133    has_digits = 0;
3134    for (; input->pos < input->len; input->pos++, output->len++) {
3135	ch = input->start[input->pos];
3136	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
3137	    ch != '4' && ch != '5' && ch != '6' && ch != '7') {
3138	    break;
3139	}
3140	has_digits = 1;
3141    }
3142
3143    return has_digits;
3144}
3145
3146static int
3147extract_regis_command(RegisDataFragment *input, char *command)
3148{
3149    char ch;
3150
3151    assert(input);
3152    assert(command);
3153
3154    if (input->pos >= input->len)
3155	return 0;
3156
3157    ch = input->start[input->pos];
3158    if (ch == '\0' || ch == ';') {
3159	return 0;
3160    }
3161    if (!islower(CharOf(ch)) && !isupper(CharOf(ch)) && ch != '@') {
3162	return 0;
3163    }
3164    *command = ch;
3165    input->pos++;
3166
3167    return 1;
3168}
3169
3170/*
3171 * Check a ReGIS alphabet name before reporting it, to pick an appropriate
3172 * delimiter.  If the string is empty, or contains nonreportable characters,
3173 * just return NUL.
3174 */
3175static int
3176pick_quote(const char *value)
3177{
3178    Bool s_quote = False;
3179    Bool d_quote = False;
3180
3181    if (*value != '\0') {
3182	while (*value != '\0') {
3183	    int ch = CharOf(*value++);
3184	    if (ch == D_QUOTE)
3185		d_quote = True;
3186	    else if (ch == S_QUOTE)
3187		s_quote = True;
3188	    else if (!isName(ch))
3189		s_quote = d_quote = True;
3190	}
3191    } else {
3192	s_quote = d_quote = True;
3193    }
3194    return ((s_quote && d_quote)
3195	    ? 0
3196	    : (s_quote
3197	       ? D_QUOTE
3198	       : S_QUOTE));
3199}
3200
3201static int
3202extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen)
3203{
3204    char open_quote_ch;
3205    char ch;
3206    unsigned outlen;
3207
3208    assert(input);
3209    assert(out);
3210    assert(maxlen > 0U);
3211
3212    if (input->pos >= input->len)
3213	return 0;
3214
3215    ch = peek_fragment(input);
3216    if (!isQuote(ch))
3217	return 0;
3218    open_quote_ch = ch;
3219    outlen = 0U;
3220    pop_fragment(input);
3221
3222    ch = '\0';
3223    while (!fragment_consumed(input)) {
3224	char prev_ch = ch;
3225	ch = peek_fragment(input);
3226	/* ';' (resync) and '@' (macrograph) are not recognized in strings */
3227	if (prev_ch == open_quote_ch) {
3228	    if (ch == open_quote_ch) {
3229		if (outlen < maxlen) {
3230		    out[outlen] = ch;
3231		}
3232		outlen++;
3233		pop_fragment(input);
3234		ch = '\0';
3235		continue;
3236	    }
3237	    if (outlen < maxlen)
3238		out[outlen] = '\0';
3239	    else
3240		out[maxlen] = '\0';
3241	    return 1;
3242	}
3243	if (ch == '\0')
3244	    break;
3245	if (ch != open_quote_ch) {
3246	    if (outlen < maxlen)
3247		out[outlen] = ch;
3248	    outlen++;
3249	}
3250	pop_fragment(input);
3251    }
3252    if (ch == open_quote_ch) {
3253	pop_fragment(input);
3254	if (outlen < maxlen)
3255	    out[outlen] = '\0';
3256	else
3257	    out[maxlen] = '\0';
3258	return 1;
3259    }
3260    /* FIXME: handle multiple strings concatenated with commas */
3261
3262    TRACE(("DATA_ERROR: end of input before closing quote\n"));
3263    return 0;
3264}
3265
3266static int
3267extract_regis_parenthesized_data(RegisDataFragment *input,
3268				 RegisDataFragment *output)
3269{
3270    char ch;
3271    char open_quote_ch;
3272    int nesting;
3273
3274    assert(input);
3275    assert(output);
3276
3277    output->start = &input->start[input->pos];
3278    output->len = 0U;
3279    output->pos = 0U;
3280
3281    if (input->pos >= input->len)
3282	return 0;
3283
3284    ch = input->start[input->pos];
3285    if (ch != '(')
3286	return 0;
3287    input->pos++;
3288    output->start++;
3289    nesting = 1;
3290    open_quote_ch = '\0';
3291
3292    ch = '\0';
3293    for (; input->pos < input->len; input->pos++, output->len++) {
3294	char prev_ch = ch;
3295	ch = input->start[input->pos];
3296	if (isQuote(ch)) {
3297	    if (open_quote_ch == '\0') {
3298		open_quote_ch = ch;
3299	    } else {
3300		if (ch == prev_ch && prev_ch == open_quote_ch) {
3301		    ch = '\0';
3302		} else if (ch == open_quote_ch) {
3303		    open_quote_ch = '\0';
3304		}
3305	    }
3306	    continue;
3307	}
3308	if (open_quote_ch != '\0')
3309	    continue;
3310
3311	if (ch == ';') {
3312	    TRACE(("leaving parenthesized data nested %d levels deep due to command termination character\n",
3313		   nesting));
3314	    break;
3315	}
3316	if (ch == '(')
3317	    nesting++;
3318	if (ch == ')') {
3319	    nesting--;
3320	    if (nesting == 0) {
3321		input->pos++;
3322		return 1;
3323	    }
3324	}
3325    }
3326
3327    TRACE(("DATA_ERROR: end of input before closing paren (%d levels deep)\n",
3328	   nesting));
3329    return 0;
3330}
3331
3332static int
3333extract_regis_option(RegisDataFragment *input,
3334		     char *option,
3335		     RegisDataFragment *output)
3336{
3337    char ch;
3338    int paren_level, bracket_level;
3339    char open_quote_ch;
3340
3341    assert(input);
3342    assert(option);
3343    assert(output);
3344
3345    /* LETTER suboptions* value? */
3346    /*
3347     * FIXME: what are the rules for using separate parens vs. sharing between
3348     * options?
3349     */
3350
3351    output->start = &input->start[input->pos];
3352    output->len = 0U;
3353    output->pos = 0U;
3354
3355    if (input->pos >= input->len) {
3356	return 0;
3357    }
3358
3359    ch = input->start[input->pos];
3360    /* FIXME: are options always letters or are some special characters ok? */
3361    if (ch == ';' || ch == ',' ||
3362	ch == '(' || ch == ')' ||
3363	ch == '[' || ch == ']' ||
3364	isQuote(ch) ||
3365	isdigit(CharOf(ch))) {
3366	return 0;
3367    }
3368    *option = ch;
3369    input->pos++;
3370    output->start++;
3371    paren_level = 0;
3372    bracket_level = 0;
3373
3374    open_quote_ch = '\0';
3375    for (; input->pos < input->len; input->pos++, output->len++) {
3376	ch = input->start[input->pos];
3377	TRACE(("looking at char '%c' in option '%c'\n", ch, *option));
3378	/* FIXME: any special rules for commas? */
3379	/* FIXME: handle escaped quotes */
3380	if (isQuote(ch)) {
3381	    if (open_quote_ch == ch) {
3382		open_quote_ch = '\0';
3383	    } else {
3384		open_quote_ch = ch;
3385	    }
3386	    continue;
3387	}
3388	if (open_quote_ch != '\0')
3389	    continue;
3390	if (ch == '(') {
3391	    paren_level++;
3392	}
3393	if (ch == ')') {
3394	    paren_level--;
3395	    if (paren_level < 0) {
3396		TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n",
3397		       *option));
3398		return 0;
3399	    }
3400	}
3401	if (ch == '[') {
3402	    bracket_level++;
3403	}
3404	if (ch == ']') {
3405	    bracket_level--;
3406	    if (bracket_level < 0) {
3407		TRACE(("DATA_ERROR: found ReGIS option has value with too many close brackets \"%c\"\n",
3408		       *option));
3409		return 0;
3410	    }
3411	}
3412	if (paren_level == 0 && bracket_level == 0) {
3413	    /*
3414	     * Top-level commas indicate the end of this option and the start of
3415	     * another.
3416	     */
3417	    if (ch == ',')
3418		break;
3419	    /*
3420	     * Top-level command/option/suboption names also indicate the end of
3421	     * this option.  "E" is valid as the exponent indicator in a numeric
3422	     * parameter.
3423	     */
3424	    if (ch != 'E' && ch != 'e' &&
3425		((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')))
3426		break;
3427	}
3428	if (ch == ';')
3429	    break;
3430    }
3431    if (paren_level != 0) {
3432	TRACE(("DATA_ERROR: mismatched parens in argument to ReGIS option \"%c\"\n",
3433	       *option));
3434	return 0;
3435    }
3436    if (bracket_level != 0) {
3437	TRACE(("DATA_ERROR: mismatched brackets in argument to ReGIS option \"%c\"\n",
3438	       *option));
3439	return 0;
3440    }
3441
3442    TRACE(("found ReGIS option and value \"%c\" \"%s\"\n",
3443	   *option,
3444	   fragment_to_tempstr(output)));
3445    return 1;
3446}
3447
3448static int
3449regis_num_to_int(RegisDataFragment const *input, int *out)
3450{
3451    char ch;
3452
3453    assert(input);
3454    assert(out);
3455
3456    /* FIXME: handle exponential notation and rounding */
3457    /* FIXME: check for junk after the number */
3458    ch = peek_fragment(input);
3459    if (!isdigit(CharOf(ch)) &&
3460	ch != '+' &&
3461	ch != '-') {
3462	*out = 0;
3463	return 0;
3464    }
3465
3466    TRACE(("converting \"%s\" to an int\n", fragment_to_tempstr(input)));
3467    *out = atoi(fragment_to_tempstr(input));
3468    return 1;
3469}
3470
3471#define spec_H xBIT(0)
3472#define spec_L xBIT(1)
3473#define spec_S xBIT(2)
3474
3475#define spec_HLS (spec_H | spec_L | spec_S)
3476
3477#define spec_R xBIT(3)
3478#define spec_G xBIT(4)
3479#define spec_B xBIT(5)
3480
3481#define spec_RGB (spec_R | spec_G | spec_B)
3482
3483static int
3484load_regis_colorspec(RegisGraphicsContext const *context,
3485		     RegisDataFragment const *input,
3486		     ColorRegister *colors)
3487{
3488    RegisDataFragment colorspec;
3489    short r = colors->r;
3490    short g = colors->g;
3491    short b = colors->b;
3492    short l = -1;
3493    int simple;
3494    unsigned len;
3495    unsigned spec = 0;
3496
3497    assert(context);
3498    assert(input);
3499    assert(colors);
3500
3501    copy_fragment(&colorspec, input);
3502    TRACE(("colorspec option: \"%s\"\n", fragment_to_tempstr(&colorspec)));
3503
3504    skip_regis_whitespace(&colorspec);
3505    simple = 0;
3506    if ((len = fragment_remaining(&colorspec)) == 1U) {
3507	simple = 1;
3508    } else if (len > 1U) {
3509	unsigned n;
3510	for (n = 1; n < len; ++n) {
3511	    char after = get_fragment(&colorspec, n);
3512	    /* if no parameters, we might see a right-parenthesis, but on error
3513	     * we can anything */
3514	    if (strchr("[(,)]", after) != NULL) {
3515		simple = 1;
3516		break;
3517	    } else if (!IsSpace(after)) {
3518		break;
3519	    }
3520	}
3521    }
3522    if (simple) {
3523	char ch = pop_fragment(&colorspec);
3524
3525	TRACE(("got ReGIS RGB colorspec pattern '%c' with arguments: \"%s\"\n",
3526	       ch, fragment_to_tempstr(&colorspec)));
3527	switch (ch) {
3528	case 'D':
3529	case 'd':
3530	    r = 0;
3531	    g = 0;
3532	    b = 0;
3533	    l = 0;
3534	    break;
3535	case 'R':
3536	case 'r':
3537	    r = 100;
3538	    g = 0;
3539	    b = 0;
3540	    l = 46;
3541	    break;
3542	case 'G':
3543	case 'g':
3544	    r = 0;
3545	    g = 100;
3546	    b = 0;
3547	    l = 50;
3548	    break;
3549	case 'B':
3550	case 'b':
3551	    r = 0;
3552	    g = 0;
3553	    b = 100;
3554	    l = 50;
3555	    break;
3556	case 'C':
3557	case 'c':
3558	    r = 0;
3559	    g = 100;
3560	    b = 100;
3561	    l = 50;
3562	    break;
3563	case 'Y':
3564	case 'y':
3565	    r = 100;
3566	    g = 100;
3567	    b = 0;
3568	    l = 50;
3569	    break;
3570	case 'M':
3571	case 'm':
3572	    r = 100;
3573	    g = 0;
3574	    b = 100;
3575	    l = 50;
3576	    break;
3577	case 'W':
3578	case 'w':
3579	    r = 100;
3580	    g = 100;
3581	    b = 100;
3582	    l = 100;
3583	    break;
3584	default:
3585	    TRACE(("DATA_ERROR: unknown RGB color name: \"%c\"\n", ch));
3586	    return 0;
3587	}
3588    } else {
3589	RegisDataFragment num;
3590	int max, val;
3591	char comp;
3592	short h = -1;
3593	short s = -1;
3594
3595	while (!fragment_consumed(&colorspec)) {
3596	    if (skip_regis_whitespace(&colorspec))
3597		continue;
3598
3599	    comp = pop_fragment(&colorspec);
3600	    switch (comp) {
3601	    case ',':
3602		/* not sure if this is valid, but it is easy to handle */
3603		continue;
3604	    case 'H':
3605	    case 'h':
3606		max = 360;
3607		comp = 'H';
3608		break;
3609	    case 'L':
3610	    case 'l':
3611		max = 100;
3612		comp = 'L';
3613		break;
3614	    case 'S':
3615	    case 's':
3616		max = 100;
3617		comp = 'S';
3618		break;
3619#ifdef ENABLE_RGB_COLORSPECS
3620	    case 'R':		/* RLogin extension */
3621	    case 'r':
3622		max = 100;
3623		comp = 'R';
3624		break;
3625	    case 'G':		/* RLogin extension */
3626	    case 'g':
3627		max = 100;
3628		comp = 'G';
3629		break;
3630	    case 'B':		/* RLogin extension */
3631	    case 'b':
3632		max = 100;
3633		comp = 'B';
3634		break;
3635#endif
3636	    default:
3637		TRACE(("DATA_ERROR: unrecognized component in colorspec: '%c'\n",
3638		       comp));
3639		return 0;
3640	    }
3641
3642	    skip_regis_whitespace(&colorspec);
3643	    if (!extract_regis_num(&colorspec, &num)) {
3644		TRACE(("DATA_ERROR: expected int after '%c' component in colorspec: \"%s\"\n",
3645		       comp, fragment_to_tempstr(&colorspec)));
3646		return 0;
3647	    }
3648	    if (!regis_num_to_int(&num, &val)) {
3649		TRACE(("DATA_ERROR: component value \"%s\" is not a number\n",
3650		       fragment_to_tempstr(&num)));
3651		return 0;
3652	    }
3653	    /* FIXME: error, truncate, wrap, ...? */
3654	    if (val < 0 || val > max) {
3655		TRACE(("DATA_ERROR: component value %d out of range\n", val));
3656		return 0;
3657	    }
3658
3659	    switch (comp) {
3660	    case 'H':
3661		h = (short) val;
3662		spec |= spec_H;
3663		break;
3664	    case 'L':
3665		l = (short) val;
3666		spec |= spec_L;
3667		break;
3668	    case 'S':
3669		s = (short) val;
3670		spec |= spec_S;
3671		break;
3672	    case 'R':
3673		r = (short) val;
3674		spec |= spec_R;
3675		break;
3676	    case 'G':
3677		g = (short) val;
3678		spec |= spec_G;
3679		break;
3680	    case 'B':
3681		b = (short) val;
3682		spec |= spec_B;
3683		break;
3684	    }
3685	}
3686
3687	if ((spec & spec_HLS) && (spec & spec_RGB)) {
3688	    TRACE(("DATA_ERROR: conflicting colorspec format\n"));
3689	    return 0;
3690	} else if (spec == spec_HLS) {
3691	    TRACE(("found HLS colorspec to be converted: %hd,%hd,%hd\n",
3692		   h, l, s));
3693	    hls2rgb(h, l, s, &r, &g, &b);
3694	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
3695	} else if (spec == spec_RGB) {
3696	    TRACE(("found RGB colorspec: %hd,%hd,%hd\n", r, g, b));
3697	    l = (short) ((MIN3(r, g, b) + MAX3(r, g, b)) / 2);
3698	    TRACE(("calculated L: %d\n", l));
3699	} else if ((spec & spec_HLS)) {
3700	    short old_h, old_l, old_s;
3701	    TRACE(("using partial HLS %hd,%hd,%hd\n", h, l, s));
3702	    rgb2hls(r, g, b, &old_h, &old_l, &old_s);
3703	    if (h < 0)
3704		h = old_h;
3705	    if (l < 0)
3706		l = old_l;
3707	    if (s < 0)
3708		s = old_s;
3709	    TRACE(("resulting HLS colorspec to convert: %hd,%hd,%hd\n",
3710		   h, l, s));
3711	    hls2rgb(h, l, s, &r, &g, &b);
3712	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
3713	} else {
3714	    TRACE(("DATA_ERROR: unrecognized colorspec format\n"));
3715	    return 0;
3716	}
3717    }
3718
3719    /*
3720     * The VT240 and VT330 models convert to the closest grayscale value.
3721     */
3722    if (context->graphics_termid == 240 || context->graphics_termid == 330) {
3723	hls2rgb(0, l, 0, &r, &g, &b);
3724	TRACE(("converted to grayscale: %hd,%hd,%hd\n", r, g, b));
3725    }
3726
3727    colors->r = r;
3728    colors->g = g;
3729    colors->b = b;
3730
3731    skip_regis_whitespace(&colorspec);
3732    if (!fragment_consumed(&colorspec)) {
3733	char skip;
3734
3735	skip = pop_fragment(&colorspec);
3736	(void) skip;		/* variable needed only if tracing */
3737	TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS colorspec \"%c\"\n",
3738	       skip));
3739    }
3740
3741    return 1;
3742}
3743
3744static int
3745load_regis_regnum_or_colorspec(RegisGraphicsContext const *context,
3746			       RegisDataFragment const *input,
3747			       RegisterNum *out)
3748{
3749    int val;
3750    RegisDataFragment colorspec;
3751    RegisDataFragment num;
3752    RegisDataFragment coloroption;
3753
3754    copy_fragment(&colorspec, input);
3755    TRACE(("looking at colorspec pattern: \"%s\"\n",
3756	   fragment_to_tempstr(&colorspec)));
3757
3758    skip_regis_whitespace(&colorspec);
3759
3760    if (extract_regis_num(&colorspec, &num)) {
3761	if (!regis_num_to_int(&num, &val)) {
3762	    TRACE(("DATA_ERROR: colorspec value \"%s\" is not a valid register\n",
3763		   fragment_to_tempstr(&num)));
3764	    return 0;
3765	}
3766	if (val < 0) {
3767	    /* FIXME: error, truncate, wrap, ...? */
3768	    TRACE(("DATA_ERROR: ignoring negative colorspec value: %d\n", val));
3769	    return 0;
3770	}
3771	if (val >= (int) context->destination_graphic->valid_registers) {
3772	    /* FIXME: error, truncate, wrap, ...? */
3773	    TRACE(("DATA_ERROR: colorspec value %d is too big; wrapping\n",
3774		   val));
3775	    val %= (int) context->destination_graphic->valid_registers;
3776	}
3777
3778	TRACE(("colorspec contains index for register %u\n", val));
3779	*out = (RegisterNum) val;
3780
3781	skip_regis_whitespace(&colorspec);
3782	if (!fragment_consumed(&colorspec)) {
3783	    char skip;
3784
3785	    skip = pop_fragment(&colorspec);
3786	    (void) skip;	/* variable needed only if tracing */
3787	    TRACE(("DATA_ERROR: unexpected character after register \"%c\"\n",
3788		   skip));
3789	    return 0;
3790	}
3791
3792	return 1;
3793    }
3794
3795    if (extract_regis_parenthesized_data(&colorspec, &coloroption)) {
3796	ColorRegister find_reg =
3797	{-1, -1, -1};
3798
3799	if (!load_regis_colorspec(context, &coloroption, &find_reg)) {
3800	    TRACE(("unable to parse colorspec\n"));
3801	    return 0;
3802	}
3803
3804	*out = find_color_register(context->destination_graphic->color_registers,
3805				   find_reg.r, find_reg.g, find_reg.b);
3806	TRACE(("colorspec maps to closest register %u\n", *out));
3807
3808	return 1;
3809    }
3810
3811    TRACE(("expected register number or colorspec, but found: \"%s\"\n",
3812	   fragment_to_tempstr(&colorspec)));
3813    return 0;
3814}
3815
3816static int
3817to_scaled_int(char const *num, int scale, int *value)
3818{
3819    unsigned long whole, frac;
3820    char *end;
3821
3822    /* FIXME: handle whitespace? how about trailing junk? */
3823    whole = strtoul(num, &end, 10);
3824    if (end[0] == '.') {
3825	char temp[5] = "0000";
3826
3827	if (end[1] != '\0') {
3828	    temp[0] = end[1];
3829	    if (end[2] != '\0') {
3830		temp[1] = end[2];
3831		if (end[3] != '\0') {
3832		    temp[2] = end[3];
3833		    if (end[4] != '\0') {
3834			temp[3] = end[4];
3835		    }
3836		}
3837	    }
3838	}
3839	frac = strtoul(temp, NULL, 10);
3840    } else if (end[0] == '\0' || end[0] == ',' || IsSpace(end[0])) {
3841	frac = 0;
3842    } else {
3843	TRACE(("unexpected character %c in number %s\n", end[0], num));
3844	return 0;
3845    }
3846
3847    *value = (int) (whole * (unsigned) scale +
3848		    (frac * (unsigned) scale) / 10000);
3849
3850    return 1;
3851}
3852
3853static int
3854load_regis_raw_extent(char const *extent, int *relx, int *rely,
3855		      int *xloc, int *yloc, int scale)
3856{
3857    int xsign, ysign;
3858    char const *xpart;
3859    char const *ypart;
3860
3861    xpart = extent;
3862    if ((ypart = strchr(extent, ',')) != NULL) {
3863	ypart++;
3864    } else {
3865	ypart = "";
3866    }
3867
3868    while (IsSpace(xpart[0]))
3869	xpart++;
3870    while (IsSpace(ypart[0]))
3871	ypart++;
3872
3873    if (xpart[0] == '-') {
3874	xsign = -1;
3875	xpart++;
3876    } else if (xpart[0] == '+') {
3877	xsign = +1;
3878	xpart++;
3879    } else {
3880	xsign = 0;
3881    }
3882    if (ypart[0] == '-') {
3883	ysign = -1;
3884	ypart++;
3885    } else if (ypart[0] == '+') {
3886	ysign = +1;
3887	ypart++;
3888    } else {
3889	ysign = 0;
3890    }
3891
3892    if (xpart[0] == '\0' || xpart[0] == ',') {
3893	*relx = 1;
3894	*xloc = 0;
3895    } else if (xsign == 0) {
3896	int val;
3897
3898	if (!to_scaled_int(xpart, scale, &val))
3899	    return 0;
3900	*relx = 0;
3901	*xloc = val;
3902    } else {
3903	int val;
3904
3905	if (!to_scaled_int(xpart, scale, &val))
3906	    return 0;
3907	*relx = 1;
3908	*xloc = xsign * val;
3909    }
3910    if (ypart[0] == '\0') {
3911	*rely = 1;
3912	*yloc = 0;
3913    } else if (ysign == 0) {
3914	int val;
3915
3916	if (!to_scaled_int(ypart, scale, &val))
3917	    return 0;
3918	*rely = 0;
3919	*yloc = val;
3920    } else {
3921	int val;
3922
3923	if (!to_scaled_int(ypart, scale, &val))
3924	    return 0;
3925	*rely = 1;
3926	*yloc = ysign * val;
3927    }
3928
3929    return 1;
3930}
3931
3932static int
3933load_regis_mult_extent(char const *extent, int *w, int *h)
3934{
3935    int relx, rely;
3936    int px, py;
3937
3938    if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) {
3939	TRACE(("invalid coordinates in extent \"%s\"\n", extent));
3940	return 0;
3941    }
3942    if (relx | rely) {
3943	TRACE(("invalid relative value in multiplier extent \"%s\"\n", extent));
3944	return 0;
3945    }
3946
3947    *w = px;
3948    *h = py;
3949
3950    return 1;
3951}
3952
3953static int
3954load_regis_pixel_extent(char const *extent, int origx, int origy,
3955			int *xloc, int *yloc)
3956{
3957    int relx, rely;
3958    int px, py;
3959
3960    if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) {
3961	TRACE(("invalid coordinates in extent \"%s\"\n", extent));
3962	return 0;
3963    }
3964
3965    *xloc = px;
3966    *yloc = py;
3967
3968    if (relx)
3969	*xloc += origx;
3970    if (rely)
3971	*yloc += origy;
3972
3973    return 1;
3974}
3975
3976#define COORD_SCALE 1000
3977
3978static int
3979load_regis_coord_extent(RegisGraphicsContext const *context, char const *extent,
3980			int origx, int origy, int *xloc, int *yloc)
3981{
3982    int relx, rely;
3983    int ux, uy;
3984
3985    if (!load_regis_raw_extent(extent, &relx, &rely, &ux, &uy, COORD_SCALE)) {
3986	TRACE(("invalid coordinates in extent \"%s\"\n", extent));
3987	return 0;
3988    }
3989
3990    if (relx) {
3991	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
3992	TRACE(("converted relative user X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
3993	       ux / (double) COORD_SCALE, px, context->width,
3994	       context->x_off, context->x_div));
3995	*xloc = origx + px;
3996    } else {
3997	const int px = TRANSLATE_XCOORD(context, ux, COORD_SCALE);
3998	TRACE(("converted absolute user X coord %.03f to absolute pixel X coord %d\n",
3999	       ux / (double) COORD_SCALE, px));
4000	*xloc = px;
4001    }
4002    if (rely) {
4003	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
4004	TRACE(("converted relative user Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
4005	       uy / (double) COORD_SCALE, py, context->height,
4006	       context->y_off, context->y_div));
4007	*yloc = origy + py;
4008    } else {
4009	const int py = TRANSLATE_YCOORD(context, uy, COORD_SCALE);
4010	TRACE(("converted absolute user Y coord %.03f to absolute pixel Y coord %d\n",
4011	       uy / (double) COORD_SCALE, py));
4012	*yloc = py;
4013    }
4014
4015    return 1;
4016}
4017
4018static int
4019load_regis_raw_pixelvector_digit(char const *pixelvector,
4020				 unsigned *offset,
4021				 int *dx, int *dy, int mul)
4022{
4023    switch (pixelvector[*offset]) {
4024    case '0':
4025	*dx += mul;
4026	break;
4027    case '1':
4028	*dx += mul;
4029	*dy -= mul;
4030	break;
4031    case '2':
4032	*dy -= mul;
4033	break;
4034    case '3':
4035	*dx -= mul;
4036	*dy -= mul;
4037	break;
4038    case '4':
4039	*dx -= mul;
4040	break;
4041    case '5':
4042	*dx -= mul;
4043	*dy += mul;
4044	break;
4045    case '6':
4046	*dy += mul;
4047	break;
4048    case '7':
4049	*dx += mul;
4050	*dy += mul;
4051	break;
4052    default:
4053	return 0;
4054    }
4055
4056    (*offset)++;
4057    return 1;
4058}
4059
4060static int
4061load_regis_pixel_pixelvector(char const *pixelvector,
4062			     int mul,
4063			     int origx, int origy,
4064			     int *xloc, int *yloc)
4065{
4066    int found = 0;
4067    int px = 0, py = 0;
4068    unsigned offset = 0U;
4069    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
4070					    &px, &py,
4071					    mul))
4072	found = 1;
4073    if (pixelvector[offset] != '\0') {
4074	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
4075	       &pixelvector[offset]));
4076    }
4077
4078    *xloc = origx + px;
4079    *yloc = origy + py;
4080
4081    return found;
4082}
4083
4084static int
4085load_regis_coord_pixelvector(RegisGraphicsContext const *context,
4086			     char const *pixelvector,
4087			     int origx, int origy,
4088			     int *xloc, int *yloc)
4089{
4090    const int mul = (int) (context->temporary_write_controls.pv_multiplier
4091			   * COORD_SCALE);
4092    int found = 0;
4093    int ux = 0, uy = 0;
4094    unsigned offset = 0U;
4095
4096    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
4097					    &ux, &uy,
4098					    mul))
4099	found = 1;
4100    if (pixelvector[offset] != '\0') {
4101	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
4102	       &pixelvector[offset]));
4103    } {
4104	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
4105	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
4106
4107	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
4108	       ux / (double) COORD_SCALE, px, context->width,
4109	       context->x_off, context->x_div));
4110	*xloc = origx + px;
4111
4112	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
4113	       uy / (double) COORD_SCALE, py, context->height,
4114	       context->y_off, context->y_div));
4115	*yloc = origy + py;
4116    }
4117
4118    return found;
4119}
4120
4121static int
4122load_regis_coord_pixelvector_step(RegisGraphicsContext const *context,
4123				  char const *pixelvector,
4124				  unsigned *offset,
4125				  int origx, int origy,
4126				  int *xloc, int *yloc)
4127{
4128    const int mul = (int) (context->temporary_write_controls.pv_multiplier
4129			   * COORD_SCALE);
4130    int found = 0;
4131    int ux = 0, uy = 0;
4132    if (load_regis_raw_pixelvector_digit(pixelvector, offset, &ux, &uy, mul))
4133	found = 1;
4134    if (!found && pixelvector[*offset] != '\0') {
4135	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
4136	       &pixelvector[*offset]));
4137    } {
4138	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
4139	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
4140
4141	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
4142	       ux / (double) COORD_SCALE, px, context->width,
4143	       context->x_off, context->x_div));
4144	*xloc = origx + px;
4145
4146	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
4147	       uy / (double) COORD_SCALE, py, context->height,
4148	       context->y_off, context->y_div));
4149	*yloc = origy + py;
4150    }
4151
4152    return found;
4153}
4154
4155static int
4156load_regis_write_control(RegisParseState *state,
4157			 RegisGraphicsContext const *context,
4158			 int cur_x, int cur_y,
4159			 int option,
4160			 RegisDataFragment *arg,
4161			 RegisWriteControls *out)
4162{
4163    TRACE(("checking write control option \"%c\" with arg \"%s\"\n",
4164	   option, fragment_to_tempstr(arg)));
4165    switch (option) {
4166    case 'A':
4167    case 'a':
4168	TRACE(("write control alternate display method \"%s\"\n",
4169	       fragment_to_tempstr(arg)));
4170	{
4171	    int val;
4172	    if (!regis_num_to_int(arg, &val) || val < 0 || val >= 1) {
4173		TRACE(("DATA_ERROR: interpreting out of range value as 0 FIXME\n"));
4174		break;
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