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