/* $XTermId: graphics_regis.c,v 1.80 2016/05/29 16:20:42 tom Exp $ */

/*
 * Copyright 2014-2015,2016 by Ross Combs
 *
 *                         All Rights Reserved
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the name(s) of the above copyright
 * holders shall not be used in advertising or otherwise to promote the
 * sale, use or other dealings in this Software without prior written
 * authorization.
 */

#include <xterm.h>

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>

#if OPT_DOUBLE_BUFFER
#include <X11/extensions/Xdbe.h>
#endif

#include <data.h>
#include <VTparse.h>
#include <ptyx.h>

#include <assert.h>
#include <graphics.h>
#include <graphics_regis.h>

/* get rid of shadowing warnings (we will not draw Bessel functions) */
#define y1 my_y1
#define y0 my_y0

#define IS_HEX_DIGIT(CH) ( \
  (CH) == '0' || \
  (CH) == '1' || \
  (CH) == '2' || \
  (CH) == '3' || \
  (CH) == '4' || \
  (CH) == '5' || \
  (CH) == '6' || \
  (CH) == '7' || \
  (CH) == '8' || \
  (CH) == '9' || \
  (CH) == 'a' || \
  (CH) == 'b' || \
  (CH) == 'c' || \
  (CH) == 'd' || \
  (CH) == 'e' || \
  (CH) == 'f' || \
  (CH) == 'A' || \
  (CH) == 'B' || \
  (CH) == 'C' || \
  (CH) == 'D' || \
  (CH) == 'E' || \
  (CH) == 'F' )

#define SCALE_FIXED_POINT 16U

#undef DEBUG_PARSING
#undef DEBUG_ALPHABET_LOOKUP
#undef DEBUG_ALPHABETS
#undef DEBUG_BEZIER
#undef DEBUG_SPLINE_SEGMENTS
#undef DEBUG_SPLINE_POINTS
#undef DEBUG_SPLINE_WITH_ROTATION
#undef DEBUG_SPLINE_WITH_OVERDRAW
#undef DEBUG_ARC_POINTS
#undef DEBUG_ARC_CENTER
#undef DEBUG_ARC_START
#undef DEBUG_ARC_END
#undef DEBUG_SPECIFIC_CHAR_METRICS
#define IS_DEBUG_CHAR(CH) ((CH) == 'W')		/* glyphs to dump to terminal */
#undef DEBUG_COMPUTED_FONT_METRICS
#undef DEBUG_FONT_NAME
#undef DEBUG_FONT_SIZE_SEARCH
#undef DEBUG_XFT_GLYPH
#undef DEBUG_USER_GLYPH
#undef DEBUG_LOAD

/* controls for extensions over VT3x0 limitations */
#define ENABLE_RGB_COLORSPECS
#define ENABLE_FREE_ROTATION
#define ENABLE_UPLOAD_ALPHABET_FROM_FONT
#define ENABLE_UPLOAD_ALPHABET_ZERO
#define ENABLE_USER_FONT_SIZE
#define ENABLE_VARIABLE_ITALICS

#define MIN_ITERATIONS_BEFORE_REFRESH 10U
#define MIN_MS_BEFORE_REFRESH 33
/* *INDENT-OFF* */
typedef struct RegisPoint {
    int  x, y;
} RegisPoint;

typedef struct RegisWriteControls {
    unsigned     pv_multiplier;
    unsigned     pattern;
    unsigned     pattern_multiplier;
    unsigned     invert_pattern;
    unsigned     plane_mask;
    unsigned     write_style;
    RegisterNum  foreground;
    unsigned     shading_enabled;
    char         shading_character;
    int          shading_reference;
    unsigned     shading_reference_dim;
    unsigned     line_width;
} RegisWriteControls;

typedef struct RegisTextControls {
    unsigned  alphabet_num;
    unsigned  character_set_l; /* default: "(B" (ASCII) */
    unsigned  character_set_r; /* default: "-@" (Latin-1) */
    unsigned  character_display_w;
    unsigned  character_display_h;
    unsigned  character_unit_cell_w;
    unsigned  character_unit_cell_h;
    int       character_inc_x;
    int       character_inc_y;
    int       string_rotation;
    int       character_rotation;
    int       slant; /* for italic/oblique */
} RegisTextControls;

#define FixedCopy(dst, src, len) strncpy(dst, src, len - 1)[len - 1] = '\0'
#define CopyFontname(dst, src) FixedCopy(dst, src, REGIS_FONTNAME_LEN)

#define MAX_REGIS_PAGES 8U

#define MAX_REGIS_ALPHABETS 8U
#define REGIS_ALPHABET_NAME_LEN 11U
#define REGIS_FONTNAME_LEN 256U
/* enough for a 16x24 font (about 100KB) */
#define MAX_REGIS_ALPHABET_BYTES (256U * 16U * 24U)
#define MAX_GLYPH_PIXELS 8192U
#define MAX_GLYPHS 256U
#define INVALID_ALPHABET_NUM ~0U

typedef struct RegisAlphabet {
    unsigned       alphabet_num;
    unsigned       pixw, pixh;
    char           name[REGIS_ALPHABET_NAME_LEN];
    char           fontname[REGIS_FONTNAME_LEN];
    int            use_font;
    int            loaded[MAX_GLYPHS];
    unsigned char *bytes;
} RegisAlphabet;

typedef struct RegisDataFragment {
    char const  *start;
    unsigned     pos;
    unsigned     len;
} RegisDataFragment;
/* *INDENT-ON* */

#define POSITION_STACK_SIZE 16U
#define DUMMY_STACK_X -32768
#define DUMMY_STACK_Y -32768

#define CURVE_POSITION_ARC_EDGE     0U
#define CURVE_POSITION_ARC_CENTER   1U
#define CURVE_POSITION_OPEN_CURVE   2U
#define CURVE_POSITION_CLOSED_CURVE 3U

#define MAX_INPUT_CURVE_POINTS 16U
#define MAX_CURVE_POINTS (MAX_INPUT_CURVE_POINTS + 4U)

#define MAX_FILL_POINTS 2048U

typedef struct RegisParseState {
    RegisDataFragment input;
    char *temp;
    unsigned templen;
    char command;
    char option;
    /* position stack */
    int stack_x[POSITION_STACK_SIZE];
    int stack_y[POSITION_STACK_SIZE];
    unsigned stack_next;	/* next empty position */
    /* curve options */
    int curve_mode;
    int arclen;
    int x_points[MAX_CURVE_POINTS];
    int y_points[MAX_CURVE_POINTS];
    unsigned num_points;
    /* load options */
    char load_name[REGIS_ALPHABET_NAME_LEN];
    unsigned load_alphabet;
    unsigned load_w, load_h;
    unsigned load_index;
    unsigned load_glyph;
    unsigned load_row;
    /* text options */
    int string_rot_set;		/* flag to distinguish string vs. character rotation */
} RegisParseState;

typedef struct RegisGraphicsContext {
    Graphic *destination_graphic;
    Graphic *display_graphic;
    int terminal_id;
    int x_off, y_off;
    int x_div, y_div;
    int width, height;
    unsigned all_planes;
    RegisterNum background;
    char const *builtin_font;
    RegisAlphabet alphabets[MAX_REGIS_ALPHABETS];
    RegisWriteControls persistent_write_controls;
    RegisWriteControls temporary_write_controls;
    RegisTextControls persistent_text_controls;
    RegisTextControls temporary_text_controls;
    RegisTextControls *current_text_controls;
    int multi_input_mode;
    int graphics_output_cursor_x;
    int graphics_output_cursor_y;
    unsigned pattern_count;
    unsigned pattern_bit;
    int fill_mode;
    RegisPoint fill_points[MAX_FILL_POINTS];
    unsigned fill_point_count;
    unsigned destination_page;
    unsigned display_page;
    int force_refresh;
} RegisGraphicsContext;

static RegisGraphicsContext persistent_context;
static RegisParseState persistent_state;

#define MAX_PATTERN_BITS 8U

#define WRITE_STYLE_OVERLAY 1U
#define WRITE_STYLE_REPLACE 2U
#define WRITE_STYLE_COMPLEMENT 3U
#define WRITE_STYLE_ERASE 4U

#define WRITE_SHADING_REF_Y 0U
#define WRITE_SHADING_REF_X 1U
#define WRITE_SHADING_REF_NONE 2U

/* keypress event example: http://iraf.net/forum/viewtopic.php?showtopic=61692 */

#define MIN2(X, Y) ( (X) < (Y) ? (X) : (Y) )
#define MIN3(X, Y, Z) ( MIN2(MIN2((X), (Y)), MIN2((Y), (Z))) )
#define MAX2(X, Y) ( (X) > (Y) ? (X) : (Y) )
#define MAX3(X, Y, Z) ( MAX2(MAX2((X), (Y)), MAX2((Y), (Z))) )

#define ROT_LEFT_N(V, N) ( (((V) << ((N) & 3U )) & 255U) | \
			   ((V) >> (8U - ((N) & 3U))) )
#define ROT_LEFT(V) ( (((V) << 1U) & 255U) | ((V) >> 7U) )

/* convert user coordinates to absolute pixel coordinates */
#define SCALE_XCOORD(C, X, S) ( ( (X) * ((C)->width  - 1) ) / ( (C)->x_div * (S) ) )
#define SCALE_YCOORD(C, Y, S) ( ( (Y) * ((C)->height - 1) ) / ( (C)->y_div * (S) ) )
#define TRANSLATE_XCOORD(C, X, S) SCALE_XCOORD((C), (X) - (C)->x_off * (S), (S) )
#define TRANSLATE_YCOORD(C, Y, S) SCALE_YCOORD((C), (Y) - (C)->y_off * (S), (S) )

#if 0
/* convert absolute pixel coordinate to user coordinates */
#define SCALE_XPIX(C, X, S) ( ( (X) * ((C)->x_div * (S) ) ) / ((C)->width  - 1) )
#define SCALE_YPIX(C, Y, S) ( ( (Y) * ((C)->y_div * (S) ) ) / ((C)->height - 1) )
#define TRANSLATE_XPIX(C, X, S) ( SCALE_XPIX((C), (X), (S) ) + (C)->x_off * (S) )
#define TRANSLATE_YPIX(C, Y, S) ( SCALE_YPIX((C), (Y), (S) ) + (C)->y_off * (S) )
#endif

#define READ_PIXEL(C, X, Y) read_pixel((C)->destination_graphic, (X), (Y))
#define DRAW_PIXEL(C, X, Y, COL) draw_solid_pixel((C)->destination_graphic, (X), (Y), (COL))
#define DRAW_ALL(C, COL) \
    draw_solid_rectangle((C)->destination_graphic, 0, 0, (C)->width, (C)->height, (COL))

static unsigned get_shade_character_pixel(unsigned char const *pixels,
					  unsigned w, unsigned h,
					  unsigned smaxf, unsigned scale,
					  int slant_dx, int px, int py);
static void get_bitmap_of_character(RegisGraphicsContext const *context,
				    int ch, unsigned maxw, unsigned maxh,
				    unsigned char *pixels,
				    unsigned *w, unsigned *h,
				    unsigned max_pixels);

static int
ifloor(double d)
{
    double dl = floor(d);
    return (int) dl;
}

static int
isqrt(double d)
{
    double dl = sqrt(d);
    return (int) dl;
}

static void
draw_regis_pixel(RegisGraphicsContext *context, int x, int y,
		 unsigned value)
{
    unsigned color = 0;

    switch (context->temporary_write_controls.write_style) {
    case WRITE_STYLE_OVERLAY:
	/*
	 * Update pixels with foreground when pattern is 1,
	 * don't change when pattern is 0.
	 */
	if (!value) {
	    return;
	}

	if (context->temporary_write_controls.invert_pattern) {
	    color = context->background;
	} else {
	    color = context->temporary_write_controls.foreground;
	}
	break;

    case WRITE_STYLE_REPLACE:
	/*
	 * Update pixels with foreground when pattern is 1,
	 * set to background when pattern is 0.
	 */
	{
	    unsigned fg, bg;

	    if (context->temporary_write_controls.invert_pattern) {
		fg = context->background;
		bg = context->temporary_write_controls.foreground;
	    } else {
		fg = context->temporary_write_controls.foreground;
		bg = context->background;
	    }
	    color = value ? fg : bg;
	}
	break;

    case WRITE_STYLE_COMPLEMENT:
	/*
	 * Update pixels with background when pattern is 1,
	 * don't change when pattern is 0.
	 */
	if (!value) {
	    return;
	}

	color = READ_PIXEL(context, x, y);
	if (color == COLOR_HOLE)
	    color = context->background;
	color = color ^ context->all_planes;
	break;

    case WRITE_STYLE_ERASE:
	/* Update pixels to foreground. */
	if (context->temporary_write_controls.invert_pattern) {
	    color = context->temporary_write_controls.foreground;
	} else {
	    color = context->background;
	}
	break;
    }

    DRAW_PIXEL(context, x, y, color);
}

static void
shade_pattern_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref,
		       int x, int y)
{
    unsigned value;

    if (dim == WRITE_SHADING_REF_X) {
	int delta = x > ref ? 1 : -1;
	int curr_x;

	context->pattern_bit = 1U << (((unsigned) y) & 7U);
	for (curr_x = ref; curr_x != x + delta; curr_x += delta) {
	    value = context->temporary_write_controls.pattern &
		context->pattern_bit;
	    draw_regis_pixel(context, curr_x, y, value);
	}
    } else if (dim == WRITE_SHADING_REF_Y) {
	int delta = y > ref ? 1 : -1;
	int curr_y;

	for (curr_y = ref; curr_y != y + delta; curr_y += delta) {
	    context->pattern_bit = 1U << (((unsigned) curr_y) & 7U);
	    value = context->temporary_write_controls.pattern &
		context->pattern_bit;
	    draw_regis_pixel(context, x, curr_y, value);
	}
    } else {
	TRACE(("ERROR: shading requested, but there is no reference axis\n"));
    }
}

static void
shade_char_to_pixel(RegisGraphicsContext *context, unsigned char const *pixels,
		    unsigned w, unsigned h, unsigned dim, int ref, int x, int y)
{
    unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
    unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
    unsigned smaxf;
    unsigned s;
    unsigned scale;
    unsigned value;

    if (xmaxf > ymaxf) {
	smaxf = ymaxf;
	s = h;
    } else {
	smaxf = xmaxf;
	s = w;
    }
    scale = (s << SCALE_FIXED_POINT) / smaxf;

    if (dim == WRITE_SHADING_REF_X) {
	int delta = x > ref ? 1 : -1;
	int curr_x;

	for (curr_x = ref; curr_x != x + delta; curr_x += delta) {
	    value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0,
					      curr_x, y);
	    draw_regis_pixel(context, curr_x, y, value);
	}
    } else if (dim == WRITE_SHADING_REF_Y) {
	int delta = y > ref ? 1 : -1;
	int curr_y;

	for (curr_y = ref; curr_y != y + delta; curr_y += delta) {
	    value = get_shade_character_pixel(pixels, w, h, smaxf, scale, 0, x,
					      curr_y);
	    draw_regis_pixel(context, x, curr_y, value);
	}
    } else {
	TRACE(("ERROR: shading requested, but there is no reference axis\n"));
    }
}

static void
draw_patterned_pixel(RegisGraphicsContext *context, int x, int y)
{
    if (context->pattern_count >=
	context->temporary_write_controls.pattern_multiplier) {
	context->pattern_count = 0U;
	context->pattern_bit = ROT_LEFT(context->pattern_bit);
    }
    context->pattern_count++;

    draw_regis_pixel(context, x, y,
		     context->temporary_write_controls.pattern &
		     context->pattern_bit);
}

static void
shade_to_pixel(RegisGraphicsContext *context, unsigned dim, int ref,
	       int x, int y)
{
    if (context->temporary_write_controls.shading_character != '\0') {
	unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
	unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
	char ch = context->temporary_write_controls.shading_character;
	unsigned char pixels[MAX_GLYPH_PIXELS];
	unsigned w, h;

	get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
				MAX_GLYPH_PIXELS);
	if (w > 0 && h > 0) {
	    shade_char_to_pixel(context, pixels, w, h, dim, ref, x, y);
	}
    } else {
	shade_pattern_to_pixel(context, dim, ref, x, y);
    }
}

static void
draw_or_save_patterned_pixel(RegisGraphicsContext *context, int x, int y)
{
    if (context->fill_mode == 1) {
	if (context->fill_point_count >= MAX_FILL_POINTS) {
	    TRACE(("point %d,%d can not be added to filled polygon\n",
		   x, y));
	    return;
	}
	if (context->fill_point_count > 0U &&
	    context->fill_points[context->fill_point_count - 1U].x == x &&
	    context->fill_points[context->fill_point_count - 1U].y == y) {
	    return;
	}
	context->fill_points[context->fill_point_count].x = x;
	context->fill_points[context->fill_point_count].y = y;
	context->fill_point_count++;
	return;
    }

    if (context->temporary_write_controls.shading_enabled) {
	unsigned dim = context->temporary_write_controls.shading_reference_dim;
	int ref = context->temporary_write_controls.shading_reference;

	shade_to_pixel(context, dim, ref, x, y);
	return;
    }

    draw_patterned_pixel(context, x, y);
}

static int
sort_points(void const *l, void const *r)
{
    RegisPoint const *const lp = l;
    RegisPoint const *const rp = r;

    if (lp->y < rp->y)
	return -1;
    if (lp->y > rp->y)
	return +1;
    if (lp->x < rp->x)
	return -1;
    if (lp->x > rp->x)
	return +1;
    return 0;
}

static void
draw_filled_polygon(RegisGraphicsContext *context)
{
    unsigned p;
    int old_x, old_y;
    int inside;
    unsigned char pixels[MAX_GLYPH_PIXELS];
    unsigned w, h;

    if (context->temporary_write_controls.shading_character != '\0') {
	char ch = context->temporary_write_controls.shading_character;
	unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
	unsigned ymaxf = context->current_text_controls->character_unit_cell_h;

	get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
				MAX_GLYPH_PIXELS);
	if (w < 1U || h < 1U) {
	    return;
	}
    }

    qsort(context->fill_points, context->fill_point_count,
	  sizeof(context->fill_points[0]), sort_points);

    old_x = DUMMY_STACK_X;
    old_y = DUMMY_STACK_Y;
    inside = 0;
    for (p = 0U; p < context->fill_point_count; p++) {
	int new_x = context->fill_points[p].x;
	int new_y = context->fill_points[p].y;
#if 0
	printf("got %d,%d (%d,%d) inside=%d\n", new_x, new_y, old_x, old_y, inside);
#endif

	/*
	 * FIXME: This is using pixels to represent lines which loses
	 * information about exact slope and how many lines are present which
	 * causes misbehavior with some inputs (especially complex polygons).
	 * It also takes more room than remembering vertices, but I'd rather
	 * not have to implement line segments for arcs.  Maybe store a count
	 * at each vertex instead (doesn't fix the slope problem).
	 */
	/*
	 * FIXME: Change this to only draw inside of polygons, and round
	 * points in a uniform direction to avoid overlapping drawing.  As an
	 * option we could continue to support drawing the outline.
	 */
	if (new_y != old_y) {
	    if (inside) {
		/*
		 * Just draw the vertical line when there is not a matching
		 * edge on the right side.
		 */
		if (context->temporary_write_controls.shading_character != '\0') {
		    shade_char_to_pixel(context, pixels, w, h,
					WRITE_SHADING_REF_X,
					old_x, old_x, old_y);
		} else {
		    shade_pattern_to_pixel(context, WRITE_SHADING_REF_X,
					   old_x, old_x, old_y);
		}
	    }
	    inside = 1;
	} else {
	    if (inside) {
		if (context->temporary_write_controls.shading_character != '\0') {
		    shade_char_to_pixel(context, pixels, w, h,
					WRITE_SHADING_REF_X,
					old_x, new_x, new_y);
		} else {
		    shade_pattern_to_pixel(context, WRITE_SHADING_REF_X,
					   old_x, new_x, new_y);
		}
	    }
	    if (new_x > old_x + 1) {
		inside = !inside;
	    }
	}

	old_x = new_x;
	old_y = new_y;
    }

    context->destination_graphic->dirty = 1;
}

static void
draw_patterned_line(RegisGraphicsContext *context, int x1, int y1,
		    int x2, int y2)
{
    int x, y;
    int dx, dy;
    int dir, diff;

    dx = abs(x1 - x2);
    dy = abs(y1 - y2);

    if (dx > dy) {
	if (x1 > x2) {
	    int tmp;
	    EXCHANGE(x1, x2, tmp);
	    EXCHANGE(y1, y2, tmp);
	}
	if (y1 < y2)
	    dir = 1;
	else if (y1 > y2)
	    dir = -1;
	else
	    dir = 0;

	diff = 0;
	y = y1;
	for (x = x1; x <= x2; x++) {
	    if (diff >= dx) {
		diff -= dx;
		y += dir;
	    }
	    diff += dy;
	    draw_or_save_patterned_pixel(context, x, y);
	}
    } else {
	if (y1 > y2) {
	    int tmp;
	    EXCHANGE(y1, y2, tmp);
	    EXCHANGE(x1, x2, tmp);
	}
	if (x1 < x2)
	    dir = 1;
	else if (x1 > x2)
	    dir = -1;
	else
	    dir = 0;

	diff = 0;
	x = x1;
	for (y = y1; y <= y2; y++) {
	    if (diff >= dy) {
		diff -= dy;
		x += dir;
	    }
	    diff += dx;
	    draw_or_save_patterned_pixel(context, x, y);
	}
    }

    context->destination_graphic->dirty = 1;
}

typedef struct {
    int dxx;
    int dxy;
    int dyx;
    int dyy;
} quadmap_coords;

static void
draw_patterned_arc(RegisGraphicsContext *context,
		   int cx, int cy,
		   int ex, int ey,
		   int a_start, int a_length,
		   int *ex_final, int *ey_final)
{
    const double third = hypot((double) (cx - ex), (double) (cy - ey));
    const int radius = (int) third;
    const int ra = radius;
    const int rb = radius;
    const quadmap_coords neg_quadmap[4] =
    {
	{-1, 0, 0, +1},
	{0, -1, -1, 0},
	{+1, 0, 0, -1},
	{0, +1, +1, 0},
    };
    const quadmap_coords pos_quadmap[4] =
    {
	{-1, 0, 0, -1},
	{0, -1, +1, 0},
	{+1, 0, 0, +1},
	{0, +1, -1, 0},
    };
    const quadmap_coords *quadmap;
    int total_points;
    int half_degree;
    int points_start, points_stop;
    int points;
    unsigned iterations;
    long rx, ry;
    long dx, dy;
    int x, y;
    long e2;
    long error;

    TRACE(("orig a_length=%d a_start=%d\n", a_length, a_start));
    if (a_length == 0)
	return;
    if (a_length > 0) {
	quadmap = pos_quadmap;
    } else {
	quadmap = neg_quadmap;
	if (a_start != 0)
	    a_start = 3600 - a_start;
	a_length = abs(a_length);
    }
    TRACE(("positive a_length=%d a_start=%d\n", a_length, a_start));

    rx = -ra;
    ry = 0;
    e2 = rb;
    dx = (2 * rx + 1) * e2 * e2;
    dy = rx * rx;
    error = dx + dy;
    total_points = 0;
    do {
	total_points += 4;
	e2 = 2 * error;
	if (e2 >= dx) {
	    rx++;
	    dx += 2 * rb * rb;
	    error += dx;
	}
	if (e2 <= dy) {
	    ry++;
	    dy += 2 * ra * ra;
	    error += dy;
	}
    }
    while (rx <= 0);

    /* FIXME: This is apparently not accurate enough because some arcs start or
     * end a few pixels off.  Maybe compare line slopes in the loop below
     * instead?
     */
    half_degree = total_points * 5;
    points_start = (total_points * a_start - half_degree) / 3600;
    points_stop = (total_points * a_start +
		   total_points * a_length + half_degree) / 3600;
    TRACE(("drawing arc with %d points clockwise from %g degrees for %g degrees (from point %d to %d out of %d)\n",
	   total_points, a_start / 10.0, a_length / 10.0, points_start, points_stop,
	   total_points));

    /* FIXME: The four pixels at the cardinal directions are double-drawn. */
    points = 0;
    for (iterations = 0U; iterations < 8U; iterations++) {
	int q2 = iterations & 0x3;

	rx = -ra;
	ry = 0;
	e2 = rb;
	dx = (2 * rx + 1) * e2 * e2;
	dy = rx * rx;
	error = dx + dy;
	do {
#ifdef DEBUG_ARC_POINTS
	    double rad = atan2(
				  (double) (quadmap[q2].dyx * rx +
					    quadmap[q2].dyy * ry),
				  (double) (quadmap[q2].dxx * rx +
					    quadmap[q2].dxy * ry));
	    double deg = (360.0 * rad / (2.0 * M_PI));
	    if (deg < 0.0)
		deg += 360.0;
#endif

	    if (points >= points_start && points <= points_stop) {
		x = (int) (cx +
			   quadmap[q2].dxx * rx +
			   quadmap[q2].dxy * ry);
		y = (int) (cy +
			   quadmap[q2].dyx * rx +
			   quadmap[q2].dyy * ry);
#ifdef DEBUG_ARC_POINTS
		TRACE(("drawing point %u at %d,%d (%.5g deg)\n",
		       points, x, y, deg));
#endif
		draw_or_save_patterned_pixel(context, x, y);
		if (ex_final)
		    *ex_final = x;
		if (ey_final)
		    *ey_final = y;
	    } else {
#ifdef DEBUG_ARC_POINTS
		x = (int) (cx + quadmap[q2].dxx * rx + quadmap[q2].dxy * ry);
		y = (int) (cy + quadmap[q2].dyx * rx + quadmap[q2].dyy * ry);
		TRACE(("skipping point %u at %d,%d which is outside of range (%.5g deg)\n",
		       points, x, y, deg));
#endif
	    }
	    points++;

	    e2 = 2 * error;
	    if (e2 >= dx) {
		rx++;
		dx += 2 * rb * rb;
		error += dx;
	    }
	    if (e2 <= dy) {
		ry++;
		dy += 2 * ra * ra;
		error += dy;
	    }
	}
	while (rx <= 0);
    }

    context->destination_graphic->dirty = 1;
}

/*
 * The plot* functions are based on optimized rasterization primitves written by Zingl Alois.
 * See http://members.chello.at/easyfilter/bresenham.html
 */

/*
 * FIXME:
 * This is a terrible temporary hack.  The plot functions below can be adapted
 * to work like the other rasterization functions but there's no point in doing
 * that until we know we don't have to write something completely different.
 */
static RegisGraphicsContext *global_context;
static void
setPixel(int x, int y)
{
    draw_or_save_patterned_pixel(global_context, x, y);
}

static void
plotLine(int x0, int y0, int x1, int y1)
{
    int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy;		/* error value e_xy */

    for (;;) {			/* loop */
	int e2;
	setPixel(x0, y0);
	e2 = 2 * err;
	if (e2 >= dy) {		/* e_xy+e_x > 0 */
	    if (x0 == x1)
		break;
	    err += dy;
	    x0 += sx;
	}
	if (e2 <= dx) {		/* e_xy+e_y < 0 */
	    if (y0 == y1)
		break;
	    err += dx;
	    y0 += sy;
	}
    }
}

static void
plotQuadBezierSeg(int x0, int y0, int x1, int y1, int x2, int y2)
{				/* plot a limited quadratic Bezier segment */
    int sx = x2 - x1;
    int sy = y2 - y1;
    long xx = (x0 - x1);	/* relative values for checks */
    long yy = (y0 - y1);
    double cur = (double) (xx * sy - yy * sx);	/* curvature */

    assert(xx * sx <= 0 && yy * sy <= 0);	/* sign of gradient must not change */

    if (sx * (long) sx + sy * (long) sy > xx * xx + yy * yy) {	/* begin with longer part */
	x2 = x0;
	x0 = sx + x1;
	y2 = y0;
	y0 = sy + y1;
	cur = -cur;		/* swap P0 P2 */
    }
    if (cur != 0) {		/* no straight line */
	long xy;
	double dx, dy, err;

	xx += sx;
	xx *= sx = x0 < x2 ? 1 : -1;	/* x step direction */
	yy += sy;
	yy *= sy = y0 < y2 ? 1 : -1;	/* y step direction */
	xy = 2 * xx * yy;
	xx *= xx;
	yy *= yy;		/* differences 2nd degree */
	if (cur * sx * sy < 0) {	/* negated curvature? */
	    xx = -xx;
	    yy = -yy;
	    xy = -xy;
	    cur = -cur;
	}
	/* differences 1st degree */
	dx = ((4.0 * sy * cur * (x1 - x0)) + (double) xx) - (double) xy;
	dy = ((4.0 * sx * cur * (y0 - y1)) + (double) yy) - (double) xy;
	xx += xx;
	yy += yy;
	err = dx + dy + (double) xy;	/* error 1st step */
	do {
	    setPixel(x0, y0);	/* plot curve */
	    if (x0 == x2 && y0 == y2)
		return;		/* last pixel -> curve finished */
	    y1 = (2 * err) < dx;	/* save value for test of y step */
	    if ((2 * err) > dy) {
		x0 += sx;
		dx -= (double) xy;
		dy += (double) yy;
		err += dy;
	    }			/* x step */
	    if (y1) {
		y0 += sy;
		dy -= (double) xy;
		dx += (double) xx;
		err += dx;
	    }			/* y step */
	} while (dy < 0 && dx > 0);	/* gradient negates -> algorithm fails */
    }
    plotLine(x0, y0, x2, y2);	/* plot remaining part to end */
}

#if 0
static void
plotQuadBezier(int x0, int y0, int x1, int y1, int x2, int y2)
{				/* plot any quadratic Bezier curve */
    int x = x0 - x1;
    int y = y0 - y1;
    double t = x0 - 2 * x1 + x2;
    double r;

    if ((long) x * (x2 - x1) > 0) {	/* horizontal cut at P4? */
	if ((long) y * (y2 - y1) > 0)	/* vertical cut at P6 too? */
	    if (fabs((y0 - 2 * y1 + y2) / t * x) > abs(y)) {	/* which first? */
		x0 = x2;
		x2 = x + x1;
		y0 = y2;
		y2 = y + y1;	/* swap points */
	    }			/* now horizontal cut at P4 comes first */
	t = (x0 - x1) / t;
	r = (1 - t) * ((1 - t) * y0 + 2.0 * t * y1) + t * t * y2;	/* By(t=P4) */
	t = (x0 * x2 - x1 * x1) * t / (x0 - x1);	/* gradient dP4/dx=0 */
	x = ifloor(t + 0.5);
	y = ifloor(r + 0.5);
	r = (y1 - y0) * (t - x0) / (x1 - x0) + y0;	/* intersect P3 | P0 P1 */
	plotQuadBezierSeg(x0, y0, x, ifloor(r + 0.5), x, y);
	r = (y1 - y2) * (t - x2) / (x1 - x2) + y2;	/* intersect P4 | P1 P2 */
	x0 = x1 = x;
	y0 = y;
	y1 = ifloor(r + 0.5);	/* P0 = P4, P1 = P8 */
    }
    if ((long) (y0 - y1) * (y2 - y1) > 0) {	/* vertical cut at P6? */
	t = y0 - 2 * y1 + y2;
	t = (y0 - y1) / t;
	r = (1 - t) * ((1 - t) * x0 + 2.0 * t * x1) + t * t * x2;	/* Bx(t=P6) */
	t = (y0 * y2 - y1 * y1) * t / (y0 - y1);	/* gradient dP6/dy=0 */
	x = ifloor(r + 0.5);
	y = ifloor(t + 0.5);
	r = (x1 - x0) * (t - y0) / (y1 - y0) + x0;	/* intersect P6 | P0 P1 */
	plotQuadBezierSeg(x0, y0, ifloor(r + 0.5), y, x, y);
	r = (x1 - x2) * (t - y2) / (y1 - y2) + x2;	/* intersect P7 | P1 P2 */
	x0 = x;
	x1 = ifloor(r + 0.5);
	y0 = y1 = y;		/* P0 = P6, P1 = P7 */
    }
    plotQuadBezierSeg(x0, y0, x1, y1, x2, y2);	/* remaining part */
}
#endif

static void
plotCubicBezierSeg(int x0, int y0,
		   double x1, double y1,
		   double x2, double y2,
		   int x3, int y3)
{				/* plot limited cubic Bezier segment */
    int f, fx, fy, tt;
    int leg = 1;
    int sx = x0 < x3 ? 1 : -1;
    int sy = y0 < y3 ? 1 : -1;	/* step direction */
    double xc = -fabs(x0 + x1 - x2 - x3);
    double xa = xc - 4 * sx * (x1 - x2);
    double xb = sx * (x0 - x1 - x2 + x3);
    double yc = -fabs(y0 + y1 - y2 - y3);
    double ya = yc - 4 * sy * (y1 - y2);
    double yb = sy * (y0 - y1 - y2 + y3);
    double ab, ac, bc, cb, xx, xy, yy, dx, dy, ex, *pxy;
    double EP = 0.01;
    /* check for curve restrains */
    /* slope P0-P1 == P2-P3    and  (P0-P3 == P1-P2      or   no slope change) */
    assert((x1 - x0) * (x2 - x3) < EP &&
	   ((x3 - x0) * (x1 - x2) < EP || xb * xb < xa * xc + EP));
    assert((y1 - y0) * (y2 - y3) < EP &&
	   ((y3 - y0) * (y1 - y2) < EP || yb * yb < ya * yc + EP));

    if (xa == 0 && ya == 0) {	/* quadratic Bezier */
	sx = ifloor((3 * x1 - x0 + 1) / 2);
	sy = ifloor((3 * y1 - y0 + 1) / 2);	/* new midpoint */
	plotQuadBezierSeg(x0, y0, sx, sy, x3, y3);
	return;
    }
    x1 = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0) + 1;	/* line lengths */
    x2 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3) + 1;
    do {			/* loop over both ends */
	ab = xa * yb - xb * ya;
	ac = xa * yc - xc * ya;
	bc = xb * yc - xc * yb;
	ex = ab * (ab + ac - 3 * bc) + ac * ac;		/* P0 part of self-intersection loop? */
	f = ((ex > 0.0)
	     ? 1
	     : isqrt(1 + 1024 / x1));	/* calculate resolution */
	ab *= f;
	ac *= f;
	bc *= f;
	ex *= f * f;		/* increase resolution */
	xy = 9 * (ab + ac + bc) / 8;
	cb = 8 * (xa - ya);	/* init differences of 1st degree */
	dx = 27 * (8 * ab * (yb * yb - ya * yc) +
		   ex * (ya + 2 * yb + yc)) / 64 - ya * ya * (xy - ya);
	dy = 27 * (8 * ab * (xb * xb - xa * xc) -
		   ex * (xa + 2 * xb + xc)) / 64 - xa * xa * (xy + xa);
	/* init differences of 2nd degree */
	xx = 3 * (3 * ab * (3 * yb * yb - ya * ya - 2 * ya * yc) -
		  ya * (3 * ac * (ya + yb) + ya * cb)) / 4;
	yy = 3 * (3 * ab * (3 * xb * xb - xa * xa - 2 * xa * xc) -
		  xa * (3 * ac * (xa + xb) + xa * cb)) / 4;
	xy = xa * ya * (6 * ab + 6 * ac - 3 * bc + cb);
	ac = ya * ya;
	cb = xa * xa;
	xy = 3 * (xy + 9 * f * (cb * yb * yc - xb * xc * ac) -
		  18 * xb * yb * ab) / 8;

	if (ex < 0) {		/* negate values if inside self-intersection loop */
	    dx = -dx;
	    dy = -dy;
	    xx = -xx;
	    yy = -yy;
	    xy = -xy;
	    ac = -ac;
	    cb = -cb;
	}			/* init differences of 3rd degree */
	ab = 6 * ya * ac;
	ac = -6 * xa * ac;
	bc = 6 * ya * cb;
	cb = -6 * xa * cb;
	dx += xy;
	ex = dx + dy;
	dy += xy;		/* error of 1st step */

	for (pxy = &xy, fx = fy = f; x0 != x3 && y0 != y3;) {
	    setPixel(x0, y0);	/* plot curve */
	    do {		/* move sub-steps of one pixel */
		if (dx > *pxy || dy < *pxy)
		    goto exit;	/* confusing values */
		y1 = 2 * ex - dy;	/* save value for test of y step */
		if (2 * ex >= dx) {	/* x sub-step */
		    fx--;
		    ex += dx += xx;
		    dy += xy += ac;
		    yy += bc;
		    xx += ab;
		}
		if (y1 <= 0) {	/* y sub-step */
		    fy--;
		    ex += dy += yy;
		    dx += xy += bc;
		    xx += ac;
		    yy += cb;
		}
	    } while (fx > 0 && fy > 0);		/* pixel complete? */
	    if (2 * fx <= f) {
		x0 += sx;
		fx += f;
	    }			/* x step */
	    if (2 * fy <= f) {
		y0 += sy;
		fy += f;
	    }			/* y step */
	    if (pxy == &xy && dx < 0 && dy > 0)
		pxy = &EP;	/* pixel ahead valid */
	}
      exit:
	EXCHANGE(x0, x3, tt);
	sx = -sx;
	xb = -xb;		/* swap legs */
	EXCHANGE(y0, y3, tt);
	sy = -sy;
	yb = -yb;
	x1 = x2;
    } while (leg--);		/* try other end */
    plotLine(x0, y0, x3, y3);	/* remaining part in case of cusp or crunode */
}

static void
plotCubicBezier(int x0, int y0, int x1, int y1,
		int x2, int y2, int x3, int y3)
{				/* plot any cubic Bezier curve */
    int n = 0, i = 0;
    long xc = x0 + x1 - x2 - x3;
    long xa = xc - 4 * (x1 - x2);
    long xb = x0 - x1 - x2 + x3;
    long xd = xb + 4 * (x1 + x2);
    long yc = y0 + y1 - y2 - y3;
    long ya = yc - 4 * (y1 - y2);
    long yb = y0 - y1 - y2 + y3;
    long yd = yb + 4 * (y1 + y2);
    double fx0 = x0;
    double fy0 = y0;
    double t1 = (double) (xb * xb - xa * xc), t2, t[5];

#ifdef DEBUG_BEZIER
    printf("plotCubicBezier(%d,%d, %d,%d, %d,%d, %d,%d\n",
	   x0, y0, x1, y1, x2, y2, x3, y3);
#endif
    /* sub-divide curve at gradient sign changes */
    if (xa == 0) {		/* horizontal */
	if (labs(xc) < 2 * labs(xb))
	    t[n++] = (double) xc / (2.0 * (double) xb);		/* one change */
    } else if (t1 > 0.0) {	/* two changes */
	t2 = sqrt(t1);
	t1 = ((double) xb - t2) / (double) xa;
	if (fabs(t1) < 1.0)
	    t[n++] = t1;
	t1 = ((double) xb + t2) / (double) xa;
	if (fabs(t1) < 1.0)
	    t[n++] = t1;
    }
    t1 = (double) (yb * yb - ya * yc);
    if (ya == 0) {		/* vertical */
	if (labs(yc) < 2 * labs(yb))
	    t[n++] = (double) yc / (2.0 * (double) yb);		/* one change */
    } else if (t1 > 0.0) {	/* two changes */
	t2 = sqrt(t1);
	t1 = ((double) yb - t2) / (double) ya;
	if (fabs(t1) < 1.0)
	    t[n++] = t1;
	t1 = ((double) yb + t2) / (double) ya;
	if (fabs(t1) < 1.0)
	    t[n++] = t1;
    }
    for (i = 1; i < n; i++)	/* bubble sort of 4 points */
	if ((t1 = t[i - 1]) > t[i]) {
	    t[i - 1] = t[i];
	    t[i] = t1;
	    i = 0;
	}

    t1 = -1.0;
    t[n] = 1.0;			/* begin / end point */
    for (i = 0; i <= n; i++) {	/* plot each segment separately */
	double fx1, fx2, fx3;
	double fy1, fy2, fy3;

	t2 = t[i];		/* sub-divide at t[i-1], t[i] */
	fx1 = (t1 * (t1 * (double) xb - (double) (2 * xc)) -
	       t2 * (t1 * (t1 * (double) xa - (double) (2 * xb)) + (double)
		     xc) + (double) xd) / 8 - fx0;
	fy1 = (t1 * (t1 * (double) yb - (double) (2 * yc)) -
	       t2 * (t1 * (t1 * (double) ya - (double) (2 * yb)) + (double)
		     yc) + (double) yd) / 8 - fy0;
	fx2 = (t2 * (t2 * (double) xb - (double) (2 * xc)) -
	       t1 * (t2 * (t2 * (double) xa - (double) (2 * xb)) + (double)
		     xc) + (double) xd) / 8 - fx0;
	fy2 = (t2 * (t2 * (double) yb - (double) (2 * yc)) -
	       t1 * (t2 * (t2 * (double) ya - (double) (2 * yb)) + (double)
		     yc) + (double) yd) / 8 - fy0;
	fx0 -= fx3 = (t2 * (t2 * ((double) (3 * xb) - t2 * (double) xa) -
			    (double) (3 * xc)) + (double) xd) / 8;
	fy0 -= fy3 = (t2 * (t2 * ((double) (3 * yb) - t2 * (double) ya) -
			    (double) (3 * yc)) + (double) yd) / 8;
	x3 = ifloor(fx3 + 0.5);
	y3 = ifloor(fy3 + 0.5);	/* scale bounds to int */
	if (fx0 != 0.0) {
	    fx1 *= fx0 = (x0 - x3) / fx0;
	    fx2 *= fx0;
	}
	if (fy0 != 0.0) {
	    fy1 *= fy0 = (y0 - y3) / fy0;
	    fy2 *= fy0;
	}
	if (x0 != x3 || y0 != y3)	/* segment t1 - t2 */
	    plotCubicBezierSeg(x0, y0,
			       x0 + fx1, y0 + fy1,
			       x0 + fx2, y0 + fy2,
			       x3, y3);
	x0 = x3;
	y0 = y3;
	fx0 = fx3;
	fy0 = fy3;
	t1 = t2;
    }
}

#if 0
static void
plotQuadSpline(int n, int x[], int y[], int skip_segments)
{				/* plot quadratic spline, destroys input arrays x,y */
#define M_MAX 12
    double mi = 1, m[M_MAX];	/* diagonal constants of matrix */
    int i, x0, y0, x1, y1, x2, y2;
#ifdef DEBUG_SPLINE_SEGMENTS
    int color = 0;
#endif

    assert(n > 1);		/* need at least 3 points P[0]..P[n] */

#ifdef DEBUG_SPLINE_POINTS
    {
	int save_pattern;

	i = 0;
	global_context->temporary_write_controls.foreground = 11;
	save_pattern = global_context->temporary_write_controls.pattern;
	global_context->temporary_write_controls.pattern = 0xff;
	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
			   3600, NULL, NULL);
	i++;
	global_context->temporary_write_controls.foreground = 15;
	for (; i < n; i++) {
	    draw_patterned_arc(global_context,
			       x[i], y[i],
			       x[i] + 2, y[i],
			       0, 3600, NULL, NULL);
	}
	global_context->temporary_write_controls.foreground = 10;
	draw_patterned_arc(global_context, x[i], y[n], x[i] + 2, y[i], 0,
			   3600, NULL, NULL);
	global_context->temporary_write_controls.pattern = save_pattern;
    }
#endif

    x2 = x[n];
    y2 = y[n];

    x[1] = x0 = 8 * x[1] - 2 * x[0];	/* first row of matrix */
    y[1] = y0 = 8 * y[1] - 2 * y[0];

    for (i = 2; i < n; i++) {	/* forward sweep */
	if (i - 2 < M_MAX)
	    m[i - 2] = mi = 1.0 / (6.0 - mi);
	x[i] = x0 = ifloor(8 * x[i] - x0 * mi + 0.5);	/* store yi */
	y[i] = y0 = ifloor(8 * y[i] - y0 * mi + 0.5);
    }
    x1 = ifloor((x0 - 2 * x2) / (5.0 - mi) + 0.5);	/* correction last row */
    y1 = ifloor((y0 - 2 * y2) / (5.0 - mi) + 0.5);

    for (i = n - 2; i > 0; i--) {	/* back substitution */
	if (i <= M_MAX)
	    mi = m[i - 1];
	x0 = ifloor((x[i] - x1) * mi + 0.5);	/* next corner */
	y0 = ifloor((y[i] - y1) * mi + 0.5);
#ifdef DEBUG_SPLINE_SEGMENTS
	color++;
	global_context->temporary_write_controls.foreground = color;
#endif
	if ((n - 2) - i < skip_segments)
	    plotQuadBezier((x0 + x1) / 2, (y0 + y1) / 2, x1, y1, x2, y2);
	x2 = (x0 + x1) / 2;
	x1 = x0;
	y2 = (y0 + y1) / 2;
	y1 = y0;
    }
#ifdef DEBUG_SPLINE_SEGMENTS
    color++;
    global_context->temporary_write_controls.foreground = color;
#endif
    if (skip_segments > 0)
	plotQuadBezier(x[0], y[0], x1, y1, x2, y2);
}
#endif

static void
plotCubicSpline(int n, int x[], int y[], int skip_first_last)
{
#define M_MAX 12
    double mi = 0.25, m[M_MAX];	/* diagonal constants of matrix */
    int x3, y3, x4, y4;
    int i, x0, y0, x1, y1, x2, y2;
#ifdef DEBUG_SPLINE_SEGMENTS
    RegisterNum color = 0;
#endif

    assert(n > 2);		/* need at least 4 points P[0]..P[n] */

#ifdef DEBUG_SPLINE_POINTS
    {
	unsigned save_pattern;

	i = 0;
	global_context->temporary_write_controls.foreground = 11;
	save_pattern = global_context->temporary_write_controls.pattern;
	global_context->temporary_write_controls.pattern = 0xff;
	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
			   3600, NULL, NULL);
	i++;
	global_context->temporary_write_controls.foreground = 15;
	for (; i < n; i++) {
	    draw_patterned_arc(global_context,
			       x[i], y[i],
			       x[i] + 2, y[i],
			       0, 3600, NULL, NULL);
	}
	global_context->temporary_write_controls.foreground = 10;
	draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0,
			   3600, NULL, NULL);
	global_context->temporary_write_controls.pattern = save_pattern;
    }
#endif

    x3 = x[n - 1];
    y3 = y[n - 1];
    x4 = x[n];
    y4 = y[n];

    x[1] = x0 = 12 * x[1] - 3 * x[0];	/* first row of matrix */
    y[1] = y0 = 12 * y[1] - 3 * y[0];

    for (i = 2; i < n; i++) {	/* forward sweep */
	if (i - 2 < M_MAX)
	    m[i - 2] = mi = 0.25 / (2.0 - mi);
	x[i] = x0 = ifloor(12 * x[i] - 2 * x0 * mi + 0.5);
	y[i] = y0 = ifloor(12 * y[i] - 2 * y0 * mi + 0.5);
    }
    x2 = ifloor((x0 - 3 * x4) / (7 - 4 * mi) + 0.5);	/* correct last row */
    /* printf("y0=%d, y4=%d mi=%g\n", y0, y4, mi); */
    y2 = ifloor((y0 - 3 * y4) / (7 - 4 * mi) + 0.5);
    /* printf("y2=%d, y3=%d, y4=%d\n", y2, y3, y4); */
#ifdef DEBUG_SPLINE_SEGMENTS
    color++;
    global_context->temporary_write_controls.foreground = color;
#endif
    if (!skip_first_last)
	plotCubicBezier(x3, y3, (x2 + x4) / 2, (y2 + y4) / 2, x4, y4, x4, y4);

    if (n - 3 < M_MAX)
	mi = m[n - 3];
    x1 = ifloor((x[n - 2] - 2 * x2) * mi + 0.5);
    y1 = ifloor((y[n - 2] - 2 * y2) * mi + 0.5);
    for (i = n - 3; i > 0; i--) {	/* back substitution */
	if (i <= M_MAX)
	    mi = m[i - 1];
	x0 = ifloor((x[i] - 2 * x1) * mi + 0.5);
	y0 = ifloor((y[i] - 2 * y1) * mi + 0.5);
	x4 = ifloor((x0 + 4 * x1 + x2 + 3) / 6.0);	/* reconstruct P[i] */
	y4 = ifloor((y0 + 4 * y1 + y2 + 3) / 6.0);
#ifdef DEBUG_SPLINE_SEGMENTS
	color++;
	global_context->temporary_write_controls.foreground = color;
#endif
#define CB_PARM(num) ifloor((num) / 3.0 + 0.5)
	plotCubicBezier(x4, y4,
			CB_PARM(2 * x1 + x2),
			CB_PARM(2 * y1 + y2),
			CB_PARM(x1 + 2 * x2),
			CB_PARM(y1 + 2 * y2),
			x3, y3);
	x3 = x4;
	y3 = y4;
	x2 = x1;
	y2 = y1;
	x1 = x0;
	y1 = y0;
    }
    x0 = x[0];
    x4 = ifloor((3 * x0 + 7 * x1 + 2 * x2 + 6) / 12.0);		/* reconstruct P[1] */
    y0 = y[0];
    y4 = ifloor((3 * y0 + 7 * y1 + 2 * y2 + 6) / 12.0);
#ifdef DEBUG_SPLINE_SEGMENTS
    global_context->temporary_write_controls.foreground = 4;
#endif
    plotCubicBezier(x4, y4,
		    CB_PARM(2 * x1 + x2),
		    CB_PARM(2 * y1 + y2),
		    CB_PARM(x1 + 2 * x2),
		    CB_PARM(y1 + 2 * y2),
		    x3, y3);
#ifdef DEBUG_SPLINE_SEGMENTS
    color++;
    global_context->temporary_write_controls.foreground = color;
#endif
    if (!skip_first_last)
	plotCubicBezier(x0, y0, x0, y0, (x0 + x1) / 2, (y0 + y1) / 2, x4, y4);
}

static unsigned
find_free_alphabet_index(RegisGraphicsContext *context, unsigned alphabet,
			 unsigned pixw, unsigned pixh)
{
    unsigned ii, jj;

    /* try an exact match */
    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
	if (context->alphabets[ii].alphabet_num == alphabet &&
	    context->alphabets[ii].pixw == pixw &&
	    context->alphabets[ii].pixh == pixh) {
	    return ii;
	}
    }

    /* otherwise use any empty slot */
    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
	if (context->alphabets[ii].alphabet_num == INVALID_ALPHABET_NUM) {
	    context->alphabets[ii].alphabet_num = alphabet;
	    context->alphabets[ii].pixw = pixw;
	    context->alphabets[ii].pixh = pixh;
	    return ii;
	}
    }

    /* otherwise recycle a slot with a different font size */
    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
	if (context->alphabets[ii].alphabet_num == alphabet) {
	    context->alphabets[ii].pixw = pixw;
	    context->alphabets[ii].pixh = pixh;
	    context->alphabets[ii].name[0] = '\0';
	    context->alphabets[ii].fontname[0] = '\0';
	    context->alphabets[ii].use_font = 0;
	    if (context->alphabets[ii].bytes != NULL) {
		free(context->alphabets[ii].bytes);
		context->alphabets[ii].bytes = NULL;
	    }
	    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
		context->alphabets[ii].loaded[jj] = 0;
	    }
	    return ii;
	}
    }

    /* finally just recycle this arbitrary slot */
    context->alphabets[0U].alphabet_num = alphabet;
    context->alphabets[0U].pixw = pixw;
    context->alphabets[0U].pixh = pixh;
    context->alphabets[0U].name[0] = '\0';
    context->alphabets[0U].fontname[0] = '\0';
    context->alphabets[0U].use_font = 0;
    if (context->alphabets[0U].bytes != NULL) {
	free(context->alphabets[0U].bytes);
	context->alphabets[0U].bytes = NULL;
    }
    for (jj = 0U; jj < MAX_GLYPHS; jj++) {
	context->alphabets[0U].loaded[jj] = 0;
    }

    return 0U;
}

#ifdef DEBUG_SPECIFIC_CHAR_METRICS
static void
dump_bitmap_pixels(unsigned char const *pixels, unsigned w, unsigned h)
{
    unsigned yy, xx;

    for (yy = 0U; yy < h; yy++) {
	printf(" ");
	for (xx = 0U; xx < w; xx++) {
	    if (pixels[yy * w + xx]) {
		printf("#");
	    } else {
		printf("_");
	    }
	}
	printf("\n");
    }
}
#endif

#if OPT_RENDERFONT && defined(HAVE_TYPE_FCCHAR32)
static int
copy_bitmap_from_xft_font(Display *display, XftFont *font, FcChar32 ch,
			  unsigned char *pixels, unsigned w, unsigned h,
			  unsigned xmin, unsigned ymin)
{
    /*
     * FIXME: cache:
     * - the bitmap for the last M characters and target dimensions
     * - resuse the pixmap object where possible
     */
    XftColor bg, fg;
    Pixmap bitmap;
    XftDraw *draw;
    XImage *image;
    unsigned bmw, bmh;
    unsigned xx, yy;

    bg.pixel = 0UL;
    bg.color.red = 0;
    bg.color.green = 0;
    bg.color.blue = 0;
    bg.color.alpha = 0x0;

    fg.pixel = 1UL;
    fg.color.red = 0xffff;
    fg.color.green = 0xffff;
    fg.color.blue = 0xffff;
    fg.color.alpha = 0xffff;

    bmw = w + xmin;
    bmh = h;
    bitmap = XCreatePixmap(display,
			   DefaultRootWindow(display),
			   bmw, bmh,
			   1);
    if (bitmap == None) {
	TRACE(("Unable to create Pixmap\n"));
	return 0;
    }
    draw = XftDrawCreateBitmap(display, bitmap);
    if (!draw) {
	TRACE(("Unable to create XftDraw\n"));
	XFreePixmap(display, bitmap);
	return 0;
    }

    XftDrawRect(draw, &bg, 0, 0, bmw, bmh);
    XftDrawString32(draw, &fg, font, 0, font->ascent - (int) ymin,
		    &ch, 1);

    image = XGetImage(display, bitmap, (int) xmin, 0, w, h, 1, XYPixmap);
    if (!image) {
	TRACE(("Unable to create XImage\n"));
	XftDrawDestroy(draw);
	XFreePixmap(display, bitmap);
	return 0;
    }

    for (yy = 0U; yy < h; yy++) {
	for (xx = 0U; xx < w; xx++) {
	    pixels[yy * w + xx] = (unsigned char) XGetPixel(image,
							    (int) xx,
							    (int) yy);
	}
    }

    XDestroyImage(image);
    XftDrawDestroy(draw);
    XFreePixmap(display, bitmap);
    return 1;
}

static void
get_xft_glyph_dimensions(Display *display, XftFont *font, unsigned *w,
			 unsigned *h, unsigned *xmin, unsigned *ymin)
{
    unsigned workw, workh;
    FcChar32 ch;
    unsigned char *pixels;
    unsigned yy, xx;
    unsigned char_count, pixel_count;
    unsigned real_minx, real_maxx, real_miny, real_maxy;
    unsigned char_minx, char_maxx, char_miny, char_maxy;

    /*
     * For each ASCII or ISO-8859-1 printable code, find out what its
     * dimensions are.
     *
     * We actually render the glyphs and determine the extents ourselves
     * because the font library can lie by several pixels, and since we are
     * doing manual character placement in fixed areas the glyph boundary needs
     * to be accurate.
     *
     * Ignore control characters and spaces - their extent information is
     * misleading.
     */

    /* Our "work area" is just a buffer which should be big enough to hold the
     * largest glyph even if its size is under-reported by a couple of pixels
     * in each dimension.
     */
    workw = (unsigned) font->max_advance_width + 2U;
    if (font->ascent + font->descent > font->height) {
	workh = (unsigned) (font->ascent + font->descent) + 2U;
    } else {
	workh = (unsigned) font->height + 2U;
    }

    if (!(pixels = malloc(workw * workh))) {
	*w = 0U;
	*h = 0U;
	return;
    }

    /* FIXME: ch is in UCS32 -- try to support non-ASCII characters */
    char_count = 0U;
    real_minx = workw - 1U;
    real_maxx = 0U;
    real_miny = workh - 1U;
    real_maxy = 0U;
    for (ch = 33; ch < 256; ++ch) {
	if (ch >= 127 && ch <= 160)
	    continue;
	if (!FcCharSetHasChar(font->charset, ch))
	    continue;

	copy_bitmap_from_xft_font(display, font, ch, pixels,
				  workw, workh, 0U, 0U);

	pixel_count = 0U;
	char_minx = workh - 1U;
	char_maxx = 0U;
	char_miny = workh - 1U;
	char_maxy = 0U;
	for (yy = 0U; yy < workh; yy++) {
	    for (xx = 0U; xx < workw; xx++) {
		if (pixels[yy * workw + xx]) {
		    if (xx < char_minx)
			char_minx = xx;
		    if (xx > char_maxx)
			char_maxx = xx;
		    if (yy < char_miny)
			char_miny = yy;
		    if (yy > char_maxy)
			char_maxy = yy;
		    pixel_count++;
		}
	    }
	}
	if (pixel_count < 1U)
	    continue;

#ifdef DEBUG_SPECIFIC_CHAR_METRICS
	if (IS_DEBUG_CHAR(ch)) {
	    printf("char: '%c' (%d)\n", (char) ch, ch);
	    printf(" minx: %u\n", char_minx);
	    printf(" maxx: %u\n", char_maxx);
	    printf(" miny: %u\n", char_miny);
	    printf(" maxy: %u\n", char_maxy);
	    dump_bitmap_pixels(pixels, workw, workh);
	    printf("\n");
	}
#endif

	if (char_minx < real_minx)
	    real_minx = char_minx;
	if (char_maxx > real_maxx)
	    real_maxx = char_maxx;
	if (char_miny < real_miny)
	    real_miny = char_miny;
	if (char_maxy > real_maxy)
	    real_maxy = char_maxy;
	char_count++;
    }

    free(pixels);

    if (char_count < 1U) {
	*w = 0U;
	*h = 0U;
	return;
    }

    *w = (unsigned) (1 + real_maxx - real_minx);
    *h = (unsigned) (1 + real_maxy - real_miny);
    *xmin = real_minx;
    *ymin = real_miny;

#ifdef DEBUG_COMPUTED_FONT_METRICS
    printf("reported metrics:\n");
    printf(" %ux%u ascent=%u descent=%u\n", font->max_advance_width,
	   font->height, font->ascent, font->descent);
    printf("computed metrics:\n");
    printf(" real_minx=%u real_maxx=%u real_miny=%u real_maxy=%u\n",
	   real_minx, real_maxx, real_miny, real_maxy);
    printf(" final: %ux%u xmin=%u ymin=%u\n", *w, *h, *xmin, *ymin);
#endif
}

#define FONT_SIZE_CACHE_SIZE 32U

/* Find the font pixel size which returns the font which is closest to the given
 * maxw and maxh without overstepping either dimension.
 */
static XftFont *
find_best_xft_font_size(Display *display, Screen *screen, char const *fontname,
			unsigned maxw, unsigned maxh, unsigned max_pixels,
			unsigned *w, unsigned *h,
			unsigned *xmin, unsigned *ymin)
{
    XftFont *font;
    unsigned targeth;
    unsigned ii, cacheindex;
    static struct {
	char fontname[REGIS_FONTNAME_LEN];
	unsigned maxw, maxh, max_pixels;
	unsigned targeth;
	unsigned w, h;
	unsigned xmin;
	unsigned ymin;
    } cache[FONT_SIZE_CACHE_SIZE];

    assert(display);
    assert(screen);
    assert(fontname);
    assert(w);
    assert(h);
    assert(xmin);
    assert(ymin);

    cacheindex = FONT_SIZE_CACHE_SIZE;
    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
	if (cache[ii].maxw == maxw && cache[ii].maxh == maxh &&
	    cache[ii].max_pixels == max_pixels &&
	    strcmp(cache[ii].fontname, fontname) == 0) {
	    cacheindex = ii;
	    break;
	}
    }

    if (cacheindex < FONT_SIZE_CACHE_SIZE) {
	targeth = cache[cacheindex].targeth;
    } else {
	targeth = maxh * 10U + 5U;
    }
    for (;;) {
	if (targeth <= 5U) {
	    TRACE(("Giving up finding suitable Xft font size for %ux%u.\n",
		   maxw, maxh));
	    return NULL;
	}

	/*
	 * Xft does a bad job at:
	 *  - two-color low-resolution anti-aliased fonts
	 *  - non-anti-aliased fonts at low resolution unless a font size is
	 *    given (pixel size does not help, and the value of the font size
	 *    doesn't appear to matter).
	 *
	 * In those two cases it literally drops pixels, sometimes whole
	 * columns, making the glyphs unreadable and ugly even when readable.
	 */
	/*
	 * FIXME:
	 * Also, we need to scale the width and height separately.  The
	 * CHAR_WIDTH and CHAR_HEIGHT attributes would seem to be ideal, but
	 * don't appear to have any effect if set.  Instead we will manually
	 * scale the bitmap later, which may be very ugly because we won't try
	 * to identify different parts of glyphs or preserve density.
	 */
	{
	    XftPattern *pat;
	    XftPattern *match;
	    XftResult status;

	    font = NULL;
	    if ((pat = XftNameParse(fontname))) {
		XftPatternBuild(pat,
		/* arbitrary value */
				XFT_SIZE, XftTypeDouble, 12.0,
				XFT_PIXEL_SIZE, XftTypeDouble, (double)
				targeth / 10.0,
#if 0
				XFT_CHAR_WIDTH, XftTypeInteger, (int) maxw,
				XFT_CHAR_HEIGHT, XftTypeInteger, (int)
				(targeth / 10U),
#endif
				XFT_SPACING, XftTypeInteger, XFT_MONO,
				XFT_SLANT, XftTypeInteger, 0,
				XFT_ANTIALIAS, XftTypeBool, False,
				NULL);
		if ((match = XftFontMatch(display,
					  XScreenNumberOfScreen(screen),
					  pat, &status))) {
		    font = XftFontOpenPattern(display, match);
		}
	    }
	}
	if (!font) {
	    TRACE(("Unable to open a monospaced Xft font.\n"));
	    return NULL;
	}
#ifdef DEBUG_FONT_SIZE_SEARCH
	{
	    char buffer[1024];

	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
		printf("Testing font named \"%s\"\n", buffer);
	    } else {
		printf("Testing unknown font\n");
	    }
	}
#endif

	if (cacheindex < FONT_SIZE_CACHE_SIZE &&
	    targeth == cache[cacheindex].targeth) {
	    *w = cache[cacheindex].w;
	    *h = cache[cacheindex].h;
	    *xmin = cache[cacheindex].xmin;
	    *ymin = cache[cacheindex].ymin;
	} else {
	    get_xft_glyph_dimensions(display, font, w, h, xmin, ymin);
	}
#ifdef DEBUG_FONT_SIZE_SEARCH
	printf("checking max=%ux%u targeth=%u.%u\n", maxw, maxh, targeth /
	       10U, targeth % 10U);
#endif

	if (*h > maxh) {
	    XftFontClose(display, font);
#ifdef DEBUG_FONT_SIZE_SEARCH
	    printf("got %ux%u glyph; too tall; reducing target size\n", *w, *h);
#endif
	    if (*h > 2U * maxh) {
		targeth /= (*h / maxh);
	    } else if (targeth > 10U && *h > maxh + 1U) {
		targeth -= 10U;
	    } else {
		targeth--;
	    }
	    continue;
	}
	if (*w > maxw) {
	    XftFontClose(display, font);
#ifdef DEBUG_FONT_SIZE_SEARCH
	    printf("got %ux%u glyph; too wide; reducing target size\n", *w, *h);
#endif
	    if (*w > 2U * maxw) {
		targeth /= (*w / maxw);
	    } else if (targeth > 10U && *w > maxw + 1U) {
		targeth -= 10U;
	    } else {
		targeth--;
	    }
	    continue;
	}
	if (*w * *h > max_pixels) {
	    XftFontClose(display, font);
#ifdef DEBUG_FONT_SIZE_SEARCH
	    printf("got %ux%u glyph; too many pixels; reducing target size\n",
		   *w, *h);
#endif
	    if (*w * *h > 2U * max_pixels) {
		unsigned min = *w < *h ? *w : *h;
		unsigned divisor = (*w * *h) / (max_pixels * min);
		if (divisor > 1U) {
		    targeth /= divisor;
		} else if (targeth > 10U) {
		    targeth -= 10U;
		} else {
		    targeth--;
		}
	    } else {
		targeth--;
	    }
	    continue;
	}
#ifdef DEBUG_FONT_NAME
	{
	    char buffer[1024];

	    if (XftNameUnparse(font->pattern, buffer, (int) sizeof(buffer))) {
		printf("Final font for \"%s\" max %dx%d is \"%s\"\n",
		       fontname, maxw, maxh, buffer);
	    } else {
		printf("Final font for \"%s\" max %dx%d is unknown\n",
		       fontname, maxw, maxh);
	    }
	}
#endif

	if (cacheindex == FONT_SIZE_CACHE_SIZE) {
	    for (ii = 0U; ii < FONT_SIZE_CACHE_SIZE; ii++) {
		if (cache[ii].maxw == 0U || cache[ii].maxh == 0U ||
		    cache[ii].max_pixels == 0U) {
		    CopyFontname(cache[ii].fontname, fontname);
		    cache[ii].maxw = maxw;
		    cache[ii].maxh = maxh;
		    cache[ii].max_pixels = max_pixels;
		    cache[ii].targeth = targeth;
		    cache[ii].w = *w;
		    cache[ii].h = *h;
		    cache[ii].xmin = *xmin;
		    cache[ii].ymin = *ymin;
		    break;
		}
	    }
	    if (ii == FONT_SIZE_CACHE_SIZE) {
		ii = targeth % FONT_SIZE_CACHE_SIZE;
		CopyFontname(cache[ii].fontname, fontname);
		cache[ii].maxw = maxw;
		cache[ii].maxh = maxh;
		cache[ii].max_pixels = max_pixels;
		cache[ii].targeth = targeth;
		cache[ii].w = *w;
		cache[ii].h = *h;
		cache[ii].xmin = *xmin;
		cache[ii].ymin = *ymin;
	    }
	}
	return font;
    }
}
#endif

static int
get_xft_bitmap_of_character(RegisGraphicsContext const *context,
			    char const *fontname, int ch,
			    unsigned maxw, unsigned maxh, unsigned char *pixels,
			    unsigned max_pixels, unsigned *w, unsigned *h)
{
    /*
     * See Xft / RENDERFONT stuff in fontutils.c and used in utils.c
     * Add a separate configuration for ReGIS.
     */
    /*
     * FIXME: cache:
     * - resuse the font where possible
     */
#ifdef XRENDERFONT
    Display *display = XtDisplay(context->destination_graphic->xw);
    Screen *screen = XtScreen(context->destination_graphic->xw);
    XftFont *font;
    unsigned xmin = 0U, ymin = 0U;

    if (!(font = find_best_xft_font_size(display, screen, fontname, maxw, maxh,
					 max_pixels, w, h, &xmin, &ymin))) {
	TRACE(("Unable to find suitable Xft font\n"));
	return 0;
    }

    if (!copy_bitmap_from_xft_font(display, font, CharOf(ch), pixels, *w, *h,
				   xmin, ymin)) {
	TRACE(("Unable to create bitmap for '%c'\n", ch));
	XftFontClose(display, font);
	return 0;
    }
    XftFontClose(display, font);
    return 1;
#else
    (void) context;
    (void) context;
    (void) ch;
    (void) maxw;
    (void) maxh;
    (void) pixels;
    (void) max_pixels;
    (void) w;
    (void) h;

    return 0;
#endif
}

static unsigned
find_best_alphabet_index(RegisGraphicsContext const *context,
			 unsigned minw, unsigned minh,
			 unsigned maxw, unsigned maxh,
			 unsigned max_pixels)
{
    unsigned ii;
    unsigned bestmatch;
    unsigned bestw, besth;

    assert(context);
    assert(maxw);
    assert(maxh);

    bestmatch = MAX_REGIS_ALPHABETS;
    bestw = 0U;
    besth = 0U;
    for (ii = 0U; ii < MAX_REGIS_ALPHABETS; ii++) {
	if (context->alphabets[ii].alphabet_num ==
	    context->current_text_controls->alphabet_num &&
	    context->alphabets[ii].pixw >= minw &&
	    context->alphabets[ii].pixh >= minh &&
	    context->alphabets[ii].pixw <= maxw &&
	    context->alphabets[ii].pixh <= maxh &&
	    context->alphabets[ii].pixw > bestw &&
	    context->alphabets[ii].pixh > besth &&
	    context->alphabets[ii].pixw *
	    context->alphabets[ii].pixh <= max_pixels) {
	    bestmatch = ii;
	    bestw = context->alphabets[ii].pixw;
	    besth = context->alphabets[ii].pixh;
	}
    }

#ifdef DEBUG_ALPHABET_LOOKUP
    if (bestmatch < MAX_REGIS_ALPHABETS) {
	TRACE(("found alphabet %u at index %u size %ux%u font=%s\n",
	       context->current_text_controls->alphabet_num, bestmatch,
	       bestw, besth,
	       context->alphabets[bestmatch].use_font ?
	       context->alphabets[bestmatch].fontname : "(none)"));
    }
#endif

    return bestmatch;
}

#define GLYPH_WIDTH_BYTES(PIXW) ( ((PIXW) + 7U) >> 3U )

static int
get_user_bitmap_of_character(RegisGraphicsContext const *context,
			     int ch,
			     unsigned alphabet_index,
			     unsigned char *pixels)
{
    const unsigned char *glyph;
    unsigned w, h;
    unsigned xx, yy;
    unsigned byte, bit;

    assert(context);
    assert(pixels);

    if (!context->alphabets[alphabet_index].loaded[(unsigned char) ch]) {
	TRACE(("in alphabet %u with alphabet index %u user glyph for '%c' not loaded\n",
	       context->current_text_controls->alphabet_num, alphabet_index,
	       ch));
	return 0;
    }

    assert(context->alphabets[alphabet_index].bytes);

    w = context->alphabets[alphabet_index].pixw;
    h = context->alphabets[alphabet_index].pixh;
    glyph = &context->alphabets[alphabet_index]
	.bytes[(unsigned char) ch * GLYPH_WIDTH_BYTES(w) * h];

    for (yy = 0U; yy < h; yy++) {
	for (xx = 0U; xx < w; xx++) {
	    byte = yy * GLYPH_WIDTH_BYTES(w) + (xx >> 3U);
	    bit = xx & 7U;
	    pixels[yy * w + xx] = ((unsigned) glyph[byte] >> (7U - bit)) & 1U;
	}
    }

    return 1;
}

/*
 * alphabets
 *  0    built-in
 *  1-N  custom (max is 3 on VT3X0 -- up to MAX_REGIS_ALPHABETS with xterm)
 *
 * built-in 7-bit charsets
 *  (B    ASCII
 *  (0    DEC special graphics
 *  (>    DEC technical
 *  (A    NCR British
 *  (4    NCR Dutch
 *  (5    NCR Finnish
 *  (R    NCR French
 *  (9    NCR French Canadian
 *  (K    NCR German
 *  (Y    NCR Italian
 *  ('    NCR Norwegian/Danish
 *  (!6   NCR Portuguese
 *  (Z    NCR Spanish
 *  (7    NCR Swedish
 *  (-    NCR Swiss
 *
 * -@   ???
 *
 * built-in 8-bit charsets
 *  )%5   DEC supplemental graphics
 *  -A    ISO Latin-1 supplemental
 *  )<    user-preferred supplemental (94 chars)
 *
 * defaults
 *  terminal  char cell size   charsets      angle
 *  VT3x0     S1               0:ASCII(94)   0 (positive)
 *
 */
static void
get_bitmap_of_character(RegisGraphicsContext const *context, int ch,
			unsigned maxw, unsigned maxh, unsigned char *pixels,
			unsigned *w, unsigned *h, unsigned max_pixels)
{
    unsigned bestmatch;
    char const *fontname = NULL;

    if (context->current_text_controls->alphabet_num == 0) {
	fontname = context->builtin_font;
    }

    *w = 0U;
    *h = 0U;

    bestmatch = find_best_alphabet_index(context, 1U, 1U, maxw, maxh,
					 max_pixels);
    if (bestmatch < MAX_REGIS_ALPHABETS) {
	RegisAlphabet const *alpha = &context->alphabets[bestmatch];

	if (!alpha->use_font &&
	    get_user_bitmap_of_character(context, ch, bestmatch, pixels)) {
#ifdef DEBUG_USER_GLYPH
	    TRACE(("found user glyph for alphabet number %d (index %u)\n\n",
		   context->current_text_controls->alphabet_num, bestmatch));
#endif
	    *w = alpha->pixw;
	    *h = alpha->pixh;
	    return;
	}

	if (alpha->use_font)
	    fontname = alpha->fontname;
    }

    if (fontname) {
	if (get_xft_bitmap_of_character(context, fontname, ch,
					maxw, maxh, pixels,
					max_pixels, w, h)) {
	    if (*w > maxw) {
		TRACE(("BUG: Xft glyph is too wide: %ux%u but max is %ux%u\n",
		       *w, *h, maxw, maxh));
	    } else if (*h > maxh) {
		TRACE(("BUG: Xft glyph is too tall: %ux%u but max is %ux%u\n",
		       *w, *h, maxw, maxh));
	    } else if (*w * *h > max_pixels) {
		TRACE(("BUG: Xft glyph has too many pixels: %u but max is %u\n",
		       *w * *h, max_pixels));
	    } else {
		TRACE(("got glyph from \"%s\" for alphabet number %d\n",
		       fontname, context->current_text_controls->alphabet_num));
#ifdef DEBUG_SPECIFIC_CHAR_METRICS
		if (IS_DEBUG_CHAR(ch)) {
		    printf("got %ux%u Xft bitmap for '%c' target size %ux%u:\n",
			   *w, *h,
			   ch, maxw, maxh);
		    dump_bitmap_pixels(pixels, *w, *h);
		    printf("\n");
		}
#endif
		return;
	    }
	}
    }

    TRACE(("unable to load any bitmap for character '%c' in alphabet number %u at %ux%u\n",
	   ch, context->current_text_controls->alphabet_num, maxw, maxh));

    /* FIXME: this should probably produce an "unknown character" symbol */
    {
	unsigned xx, yy;

	*w = MIN2(8U, maxh);
	*h = MIN2(10U, maxw);
	for (yy = 0U; yy < *h; yy++)
	    for (xx = 0U; xx < *w; xx++)
		pixels[yy * *w + xx] = '\0';
    }
}

#define ROT_SHEAR_SCALE 8192
#define SIGNED_UNSIGNED_MOD(VAL, BASE) ( (((VAL) % (int) (BASE)) + (int) (BASE)) % (int) (BASE) )

static unsigned
get_shade_character_pixel(unsigned char const *pixels, unsigned w, unsigned h,
			  unsigned smaxf, unsigned scale, int slant_dx,
			  int px, int py)
{
    unsigned wx, wy;
    unsigned fx, fy;

    wx = (unsigned) SIGNED_UNSIGNED_MOD(px -
					(slant_dx * SIGNED_UNSIGNED_MOD(py, smaxf))
					/ ROT_SHEAR_SCALE, smaxf);
    wy = (unsigned) SIGNED_UNSIGNED_MOD(py, smaxf);

    fx = (wx * scale) >> SCALE_FIXED_POINT;
    fy = (wy * scale) >> SCALE_FIXED_POINT;
    if (fx < w && fy < h) {
	return (unsigned) pixels[fy * w + fx];
    }
    return 0U;
}

static void
draw_character(RegisGraphicsContext *context, int ch,
	       int slant_dx, int rot_shear_x,
	       int rot_shear_y, int x_sign_x, int x_sign_y,
	       int y_sign_x, int y_sign_y)
{
    const unsigned xmaxd = context->current_text_controls->character_display_w;
    const unsigned ymaxd = context->current_text_controls->character_display_h;
    const unsigned xmaxf = context->current_text_controls->character_unit_cell_w;
    const unsigned ymaxf = context->current_text_controls->character_unit_cell_h;
    unsigned w, h;
    unsigned xscale, yscale;
    unsigned fx, fy;
    unsigned px, py;
    int sx;
    int rx, ry;
    int ox, oy;
    unsigned pad_left, pad_right;
    unsigned pad_top, pad_bottom;
    unsigned char pixels[MAX_GLYPH_PIXELS];
    unsigned value;

    get_bitmap_of_character(context, ch, xmaxf, ymaxf, pixels, &w, &h,
			    MAX_GLYPH_PIXELS);
    if (w < 1 || h < 1) {
	return;
    }

    if (xmaxd > xmaxf) {
	pad_left = (xmaxd - xmaxf) / 2U;
	pad_right = (xmaxd - xmaxf) - pad_left;
    } else {
	pad_left = 0U;
	pad_right = 0U;
    }
    if (ymaxd > ymaxf) {
	pad_top = (ymaxd - ymaxf) / 2U;
	pad_bottom = (ymaxd - ymaxf) - pad_top;
    } else {
	pad_top = 0U;
	pad_bottom = 0U;
    }

    xscale = (w << SCALE_FIXED_POINT) / xmaxf;
    yscale = (h << SCALE_FIXED_POINT) / ymaxf;

    for (py = 0U; py < ymaxd; py++) {
	for (px = 0U; px < xmaxd; px++) {
	    if (py < pad_top || px < pad_left ||
		py >= ymaxd - pad_bottom || px >= xmaxd - pad_right) {
		value = 0U;
	    } else {
		fx = ((px - pad_left) * xscale) >> SCALE_FIXED_POINT;
		fy = ((py - pad_top) * yscale) >> SCALE_FIXED_POINT;
		if (fx < w && fy < h) {
		    value = (unsigned) pixels[fy * w + fx];
		} else {
		    value = 0U;
		}
	    }

	    sx = (int) px + (slant_dx * (int) py) / ROT_SHEAR_SCALE;
	    rx = x_sign_x * sx + x_sign_y * (int) py;
	    ry = y_sign_x * sx + y_sign_y * (int) py;
	    ox = rx + (rot_shear_x * ry) / ROT_SHEAR_SCALE;
	    oy = ry + (rot_shear_y * ox) / ROT_SHEAR_SCALE;
	    ox += (rot_shear_x * oy) / ROT_SHEAR_SCALE;

	    draw_regis_pixel(context,
			     (int) context->graphics_output_cursor_x + ox,
			     (int) context->graphics_output_cursor_y + oy,
			     value);
	}
    }
}

static void
draw_text(RegisGraphicsContext *context, char const *str)
{
    double total_rotation;
    size_t ii;
    int str_invert;
    int str_shear_x, str_shear_y;
    int slant_dx;
    int chr_x_sign_x, chr_x_sign_y;
    int chr_y_sign_x, chr_y_sign_y;
    int chr_shear_x, chr_shear_y;
    int begin_x, begin_y;
    int rx, ry;
    int ox, oy;

#ifdef DEBUG_ALPHABETS
    {
	unsigned n;

	for (n = 0U; n < MAX_REGIS_ALPHABETS; n++) {
	    printf("alphabet index %u\n", n);
	    if (context->alphabets[n].alphabet_num != INVALID_ALPHABET_NUM) {
		printf(" alphabet_num=%u\n", context->alphabets[n].alphabet_num);
		printf(" pixw=%d\n", context->alphabets[n].pixw);
		printf(" pixh=%d\n", context->alphabets[n].pixh);
		printf(" name=\"%s\"\n", context->alphabets[n].name);
		printf(" use_font=%d\n", context->alphabets[n].use_font);
		printf(" fontname=\"%s\"\n", context->alphabets[n].fontname);
		printf(" bytes=%p\n", context->alphabets[n].bytes);
	    }
	}
    }
#endif

    if (context->current_text_controls->slant <= -75 ||
	context->current_text_controls->slant >= +75) {
	TRACE(("ERROR: unsupported character slant angle %d\n",
	       context->current_text_controls->slant));
	return;
    }

    /* FIXME: grab when first entering command */
    begin_x = context->graphics_output_cursor_x;
    begin_y = context->graphics_output_cursor_y;

    total_rotation = 2.0 * M_PI *
	context->current_text_controls->string_rotation / 360.0;
    while (total_rotation > 1.5 * M_PI) {
	total_rotation -= 2.0 * M_PI;
    }
    if (total_rotation > 0.5 * M_PI) {
	total_rotation -= M_PI;
	str_invert = -1;
    } else {
	str_invert = 1;
    }
    str_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
    str_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));

    total_rotation = 2.0 * M_PI *
	context->current_text_controls->character_rotation / 360.0;
    while (total_rotation > 1.5 * M_PI) {
	total_rotation -= 2.0 * M_PI;
    }
    if (total_rotation > 0.5 * M_PI) {
	total_rotation -= M_PI;
	chr_x_sign_x = -1;
	chr_x_sign_y = 0;
	chr_y_sign_x = 0;
	chr_y_sign_y = -1;
    } else {
	chr_x_sign_x = 1;
	chr_x_sign_y = 0;
	chr_y_sign_x = 0;
	chr_y_sign_y = 1;
    }
    chr_shear_x = (int) (ROT_SHEAR_SCALE * -tan(0.5 * -total_rotation));
    chr_shear_y = (int) (ROT_SHEAR_SCALE * sin(-total_rotation));
    /*
     * FIXME: it isn't clear from the docs how slant affects the x positioning.
     * For now the code assumes the upper left is fixed.
     */
    TRACE(("float version: %.5f\n",
	   tan(2.0 * M_PI * abs(context->current_text_controls->slant) /
	       360.0)));
    if (context->current_text_controls->slant < 0) {
	slant_dx = (int) +(
			      tan(2.0 * M_PI * abs(context->current_text_controls->slant)
				  / 360.0) * ROT_SHEAR_SCALE);
    } else if (context->current_text_controls->slant > 0) {
	slant_dx = (int) -(
			      tan(2.0 * M_PI * abs(context->current_text_controls->slant)
				  / 360.0) * ROT_SHEAR_SCALE);
    } else {
	slant_dx = 0;
    }
    TRACE(("string rotation: %d\n",
	   context->current_text_controls->string_rotation));
    TRACE(("character rotation: %d\n",
	   context->current_text_controls->character_rotation));
    TRACE(("character slant: %d (%.5f pixels per line)\n",
	   context->current_text_controls->slant,
	   slant_dx / (double) ROT_SHEAR_SCALE));
    TRACE(("str_shear: %.5f, %.5f (sign=%d)\n",
	   str_shear_x / (double) ROT_SHEAR_SCALE,
	   str_shear_y / (double) ROT_SHEAR_SCALE,
	   str_invert));
    TRACE(("chr_shear: %.5f, %.5f (xsign=%d,%d, ysign=%d,%d)\n",
	   chr_shear_x / (double) ROT_SHEAR_SCALE,
	   chr_shear_y / (double) ROT_SHEAR_SCALE,
	   chr_x_sign_x, chr_x_sign_y,
	   chr_y_sign_x, chr_y_sign_y));

    rx = 0;
    ry = 0;
    for (ii = 0U; ii < strlen(str); ii++) {
	switch (str[ii]) {
	case '\r':
	    rx = 0;
	    break;
	case '\n':
	    /* FIXME: verify */
	    ry += (int) context->current_text_controls->character_display_h;
	    break;
	case '\b':
	    rx -= context->current_text_controls->character_inc_x;
	    ry -= context->current_text_controls->character_inc_y;
	    break;
	case '\t':
	    rx += context->current_text_controls->character_inc_x;
	    ry += context->current_text_controls->character_inc_y;
	    break;
	default:
	    ox = str_invert * rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
	    oy = str_invert * ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
	    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
	    context->graphics_output_cursor_x = begin_x + ox;
	    context->graphics_output_cursor_y = begin_y + oy;
	    draw_character(context, str[ii], slant_dx,
			   chr_shear_x, chr_shear_y,
			   chr_x_sign_x, chr_x_sign_y,
			   chr_y_sign_x, chr_y_sign_y);
	    rx += context->current_text_controls->character_inc_x;
	    ry += context->current_text_controls->character_inc_y;
	}
    }

    ox = rx + (str_shear_x * ry) / ROT_SHEAR_SCALE;
    oy = ry + (str_shear_y * ox) / ROT_SHEAR_SCALE;
    ox += (str_shear_x * oy) / ROT_SHEAR_SCALE;
    context->graphics_output_cursor_x = begin_x + ox;
    context->graphics_output_cursor_y = begin_y + oy;

    context->destination_graphic->dirty = 1;
    return;
}

/*
 * standard character cell sizes
 *   number  disp cell   unit cell       offset
 *   S0      [  9, 10]   [  8, disp_h]   [disp_w, 0]
 *   S1      [  9, 20]   [  8, disp_h]   [disp_w, 0]
 *   S2      [ 18, 30]   [ 16, disp_h]   [disp_w, 0]
 *   S3      [ 27, 45]   [ 24, disp_h]   [disp_w, 0]
 *   S4      [ 36, 60]   [ 32, disp_h]   [disp_w, 0]
 *   S5      [ 45, 75]   [ 40, disp_h]   [disp_w, 0]
 *   S6      [ 54, 90]   [ 48, disp_h]   [disp_w, 0]
 *   S7      [ 63,105]   [ 56, disp_h]   [disp_w, 0]
 *   S8      [ 72,120]   [ 64, disp_h]   [disp_w, 0]
 *   S9      [ 81,135]   [ 72, disp_h]   [disp_w, 0]
 *   S10     [ 90,150]   [ 80, disp_h]   [disp_w, 0]
 *   S11     [ 99,165]   [ 88, disp_h]   [disp_w, 0]
 *   S12     [108,180]   [ 96, disp_h]   [disp_w, 0]
 *   S13     [117,195]   [104, disp_h]   [disp_w, 0]
 *   S14     [126,210]   [112, disp_h]   [disp_w, 0]
 *   S15     [135,225]   [120, disp_h]   [disp_w, 0]
 *   S16     [144,240]   [128, disp_h]   [disp_w, 0]
 */
static int
get_standard_character_size(int standard, unsigned *disp_w, unsigned
			    *disp_h, unsigned *unit_w, unsigned *unit_h,
			    int *off_x, int *off_y)
{
    switch (standard) {
    case 0:
	*disp_w = 9U;
	*disp_h = 10U;
	*unit_w = 8U;
	break;
    case 1:
	*disp_w = 9U;
	*disp_h = 20U;
	*unit_w = 8U;
	break;
    case 2:
	*disp_w = 18U;
	*disp_h = 30U;
	*unit_w = 16U;
	break;
    case 3:
	*disp_w = 27U;
	*disp_h = 45U;
	*unit_w = 24U;
	break;
    case 4:
	*disp_w = 36U;
	*disp_h = 60U;
	*unit_w = 32U;
	break;
    case 5:
	*disp_w = 45U;
	*disp_h = 75U;
	*unit_w = 40U;
	break;
    case 6:
	*disp_w = 54U;
	*disp_h = 90U;
	*unit_w = 48U;
	break;
    case 7:
	*disp_w = 63U;
	*disp_h = 105U;
	*unit_w = 56U;
	break;
    case 8:
	*disp_w = 72U;
	*disp_h = 120U;
	*unit_w = 64U;
	break;
    case 9:
	*disp_w = 81U;
	*disp_h = 135U;
	*unit_w = 72U;
	break;
    case 10:
	*disp_w = 90U;
	*disp_h = 150U;
	*unit_w = 80U;
	break;
    case 11:
	*disp_w = 99U;
	*disp_h = 165U;
	*unit_w = 88U;
	break;
    case 12:
	*disp_w = 108U;
	*disp_h = 180U;
	*unit_w = 96U;
	break;
    case 13:
	*disp_w = 117U;
	*disp_h = 195U;
	*unit_w = 104U;
	break;
    case 14:
	*disp_w = 126U;
	*disp_h = 210U;
	*unit_w = 112U;
	break;
    case 15:
	*disp_w = 135U;
	*disp_h = 225U;
	*unit_w = 120U;
	break;
    case 16:
	*disp_w = 144U;
	*disp_h = 240U;
	*unit_w = 128U;
	break;
    default:
	return 1;
    }
    *unit_h = *disp_h;
    *off_x = (int) *disp_w;
    *off_y = 0;

    return 0;
}

static void
init_fragment(RegisDataFragment *fragment, char const *str)
{
    assert(fragment);
    assert(str);

    fragment->start = str;
    fragment->len = (unsigned) strlen(str);
    fragment->pos = 0U;
}

static void
copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src)
{
    assert(dst);
    assert(src);

    dst->start = src->start;
    dst->len = src->len;
    dst->pos = src->pos;
}

static char
peek_fragment(RegisDataFragment const *fragment)
{
    assert(fragment);

    if (fragment->pos < fragment->len) {
	return fragment->start[fragment->pos];
    }
    return '\0';
}

static char
pop_fragment(RegisDataFragment *fragment)
{
    assert(fragment);

    if (fragment->pos < fragment->len) {
	return fragment->start[fragment->pos++];
    }
    return '\0';
}

static char
get_fragment(RegisDataFragment const *fragment, unsigned pos)
{
    assert(fragment);

    if (fragment->pos + pos < fragment->len) {
	return fragment->start[fragment->pos + pos];
    }
    return '\0';
}

static size_t
fragment_len(RegisDataFragment const *fragment)
{
    assert(fragment);

    return fragment->len - fragment->pos;
}

static void
fragment_to_string(RegisDataFragment const *fragment, char *out,
		   unsigned outlen)
{
    unsigned remaininglen;
    unsigned endpos;

    assert(fragment);
    assert(out);

    if (!outlen)
	return;
    remaininglen = fragment->len - fragment->pos;
    if (remaininglen < outlen - 1U) {
	endpos = remaininglen;
    } else {
	endpos = outlen - 1U;
    }
    strncpy(out, &fragment->start[fragment->pos], endpos);
    out[endpos] = '\0';
}

#define MAX_FRAG 1024
static char const *
fragment_to_tempstr(RegisDataFragment const *fragment)
{
    static char tempstr[MAX_FRAG];

    assert(fragment);

    fragment_to_string(fragment, tempstr, MAX_FRAG);
    return tempstr;
}

static int
skip_regis_whitespace(RegisDataFragment *input)
{
    int skipped = 0;

    assert(input);

    for (; input->pos < input->len; input->pos++) {
	char ch = input->start[input->pos];
	if (ch != ',' && !IsSpace(ch)) {
	    break;
	}
	if (ch == '\n') {
	    TRACE(("end of input line\n\n"));
	}
	skipped = 1;
    }

    if (skipped)
	return 1;
    return 0;
}

static int
extract_regis_extent(RegisDataFragment *input, RegisDataFragment *output)
{
    char ch;

    assert(input);
    assert(output);

    output->start = &input->start[input->pos];
    output->len = 0U;
    output->pos = 0U;

    if (input->pos >= input->len)
	return 0;

    ch = input->start[input->pos];
    if (ch != '[')
	return 0;
    input->pos++;
    output->start++;

    /* FIXME: truncate to 16 bit signed integers */
    for (; input->pos < input->len; input->pos++, output->len++) {
	ch = input->start[input->pos];
	if (ch == ';') {
	    TRACE(("DATA_ERROR: end of input before closing bracket\n"));
	    break;
	}
	if (ch == ']')
	    break;
    }
    if (ch == ']')
	input->pos++;

    return 1;
}

static int
extract_regis_num(RegisDataFragment *input, RegisDataFragment *output)
{
    char ch = 0;
    int has_digits = 0;

    assert(input);
    assert(output);

    output->start = &input->start[input->pos];
    output->len = 0U;
    output->pos = 0U;

    if (input->start[input->pos] == '-' ||
	input->start[input->pos] == '+') {
	input->pos++;
	output->len++;
    }

    for (; input->pos < input->len; input->pos++, output->len++) {
	ch = input->start[input->pos];
	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
	    ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
	    ch != '8' && ch != '9') {
	    break;
	}
	has_digits = 1;
    }

    /* FIXME: what degenerate forms should be accepted ("E10" "1E" "1e" "1." "1ee10")? */
    /* FIXME: the terminal is said to support "floating point values", truncating to int... what do these look like? */
    if (has_digits && ch == 'E') {
	input->pos++;
	output->len++;
	for (; input->pos < input->len; input->pos++, output->len++) {
	    ch = input->start[input->pos];
	    if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
		ch != '4' && ch != '5' && ch != '6' && ch != '7' &&
		ch != '8' && ch != '9') {
		break;
	    }
	}
    }

    return has_digits;
}

static int
extract_regis_pixelvector(RegisDataFragment *input, RegisDataFragment *output)
{
    char ch;
    int has_digits;

    assert(input);
    assert(output);

    output->start = &input->start[input->pos];
    output->len = 0U;
    output->pos = 0U;

    if (input->pos < input->len) {
	ch = input->start[input->pos];
	if (ch == '+' || ch == '-') {
	    input->pos++;
	    output->len++;
	}
    }

    has_digits = 0;
    for (; input->pos < input->len; input->pos++, output->len++) {
	ch = input->start[input->pos];
	if (ch != '0' && ch != '1' && ch != '2' && ch != '3' &&
	    ch != '4' && ch != '5' && ch != '6' && ch != '7') {
	    break;
	}
	has_digits = 1;
    }

    return has_digits;
}

static int
extract_regis_command(RegisDataFragment *input, char *command)
{
    char ch;

    assert(input);
    assert(command);

    if (input->pos >= input->len)
	return 0;

    ch = input->start[input->pos];
    if (ch == '\0' || ch == ';') {
	return 0;
    }
    if (!islower(CharOf(ch)) && !isupper(CharOf(ch)) && ch != '@') {
	return 0;
    }
    *command = ch;
    input->pos++;

    return 1;
}

static int
extract_regis_string(RegisDataFragment *input, char *out, unsigned maxlen)
{
    char first_ch;
    char ch;
    unsigned outlen = 0U;

    assert(input);
    assert(out);

    if (input->pos >= input->len)
	return 0;

    ch = input->start[input->pos];
    if (ch != '\'' && ch != '"')
	return 0;
    first_ch = ch;
    input->pos++;

    ch = '\0';
    for (; input->pos < input->len; input->pos++) {
	char prev_ch = ch;
	ch = input->start[input->pos];
	/* ';' (resync) is not recognized in strings */
	if (prev_ch == first_ch) {
	    if (ch == first_ch) {
		if (outlen < maxlen) {
		    out[outlen] = ch;
		}
		outlen++;
		ch = '\0';
		continue;
	    }
	    if (outlen < maxlen)
		out[outlen] = '\0';
	    else
		out[maxlen] = '\0';
	    return 1;
	}
	if (ch == '\0')
	    break;
	if (ch != first_ch) {
	    if (outlen < maxlen) {
		out[outlen] = ch;
	    }
	    outlen++;
	}
    }
    if (ch == first_ch) {
	if (outlen < maxlen)
	    out[outlen] = '\0';
	else
	    out[maxlen] = '\0';
	return 1;
    }
    /* FIXME: handle multiple strings concatenated with commas */

    TRACE(("DATA_ERROR: end of input before closing quote\n"));
    return 0;
}

static int
extract_regis_parenthesized_data(RegisDataFragment *input,
				 RegisDataFragment *output)
{
    char ch;
    char first_ch;
    int nesting;

    assert(input);
    assert(output);

    output->start = &input->start[input->pos];
    output->len = 0U;
    output->pos = 0U;

    if (input->pos >= input->len)
	return 0;

    ch = input->start[input->pos];
    if (ch != '(')
	return 0;
    input->pos++;
    output->start++;
    nesting = 1;
    first_ch = '\0';

    ch = '\0';
    for (; input->pos < input->len; input->pos++, output->len++) {
	char prev_ch = ch;
	ch = input->start[input->pos];
	if (ch == '\'' || ch == '"') {
	    if (first_ch == '\0') {
		first_ch = ch;
	    } else {
		if (ch == prev_ch && prev_ch == first_ch) {
		    ch = '\0';
		} else if (ch == first_ch) {
		    first_ch = '\0';
		}
	    }
	    continue;
	}
	if (first_ch != '\0')
	    continue;

	if (ch == ';') {
	    TRACE(("leaving parenthesized data nested %d levels deep due to command termination character\n",
		   nesting));
	    break;
	}
	if (ch == '(')
	    nesting++;
	if (ch == ')') {
	    nesting--;
	    if (nesting == 0) {
		input->pos++;
		return 1;
	    }
	}
    }

    TRACE(("DATA_ERROR: end of input before closing paren (%d levels deep)\n",
	   nesting));
    return 0;
}

static int
extract_regis_option(RegisDataFragment *input,
		     char *option,
		     RegisDataFragment *output)
{
    char ch;
    int paren_level, bracket_level;
    char first_ch;

    assert(input);
    assert(option);
    assert(output);

    /* LETTER suboptions* value? */
    /*
     * FIXME: what are the rules for using separate parens vs. sharing between
     * options?
     */

    output->start = &input->start[input->pos];
    output->len = 0U;
    output->pos = 0U;

    if (input->pos >= input->len) {
	return 0;
    }

    ch = input->start[input->pos];
    /* FIXME: are options always letters or are some special characters ok? */
    if (ch == ';' || ch == ',' ||
	ch == '(' || ch == ')' ||
	ch == '[' || ch == ']' ||
	ch == '"' || ch == '\'' ||
	isdigit(CharOf(ch))) {
	return 0;
    }
    *option = ch;
    input->pos++;
    output->start++;
    paren_level = 0;
    bracket_level = 0;

    first_ch = '\0';
    for (; input->pos < input->len; input->pos++, output->len++) {
	ch = input->start[input->pos];
	TRACE(("looking at char '%c' in option '%c'\n", ch, *option));
	/* FIXME: any special rules for commas? */
	/* FIXME: handle escaped quotes */
	if (ch == '\'' || ch == '"') {
	    if (first_ch == ch) {
		first_ch = '\0';
	    } else {
		first_ch = ch;
	    }
	    continue;
	}
	if (first_ch != '\0')
	    continue;
	if (ch == '(') {
	    paren_level++;
	}
	if (ch == ')') {
	    paren_level--;
	    if (paren_level < 0) {
		TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n",
		       *option));
		return 0;
	    }
	}
	if (ch == '[') {
	    bracket_level++;
	}
	if (ch == ']') {
	    bracket_level--;
	    if (bracket_level < 0) {
		TRACE(("DATA_ERROR: found ReGIS option has value with too many close brackets \"%c\"\n",
		       *option));
		return 0;
	    }
	}
	if (paren_level == 0 && bracket_level == 0) {
	    /*
	     * Top-level commas indicate the end of this option and the start of
	     * another.
	     */
	    if (ch == ',')
		break;
	    /*
	     * Top-level command/option/suboption names also indicate the end of
	     * this option.  "E" is valid as the exponent indicator in a numeric
	     * parameter.
	     */
	    if (ch != 'E' && ch != 'e' &&
		((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')))
		break;
	}
	if (ch == ';')
	    break;
    }
    if (paren_level != 0) {
	TRACE(("DATA_ERROR: mismatched parens in argument to ReGIS option \"%c\"\n",
	       *option));
	return 0;
    }
    if (bracket_level != 0) {
	TRACE(("DATA_ERROR: mismatched brackets in argument to ReGIS option \"%c\"\n",
	       *option));
	return 0;
    }

    TRACE(("found ReGIS option and value \"%c\" \"%s\"\n",
	   *option,
	   fragment_to_tempstr(output)));
    return 1;
}

static int
regis_num_to_int(RegisDataFragment const *input, int *out)
{
    char ch;

    /* FIXME: handle exponential notation and rounding */
    /* FIXME: check for junk after the number */
    ch = peek_fragment(input);
    if (ch != '0' &&
	ch != '1' &&
	ch != '2' &&
	ch != '3' &&
	ch != '4' &&
	ch != '5' &&
	ch != '6' &&
	ch != '7' &&
	ch != '8' &&
	ch != '9' &&
	ch != '+' &&
	ch != '-') {
	return 0;
    }

    TRACE(("converting \"%s\" to an int\n", fragment_to_tempstr(input)));
    *out = atoi(fragment_to_tempstr(input));
    return 1;
}

static int
load_regis_colorspec(RegisGraphicsContext const *context,
		     RegisDataFragment const *input,
		     short *r_out, short *g_out, short *b_out)
{
    RegisDataFragment colorspec;
    short r = -1, g = -1, b = -1;
    short l = -1;
    int simple;

    copy_fragment(&colorspec, input);
    TRACE(("colorspec option: \"%s\"\n", fragment_to_tempstr(&colorspec)));

    skip_regis_whitespace(&colorspec);
    simple = 0;
    if (fragment_len(&colorspec) == 1) {
	simple = 1;
    } else if (fragment_len(&colorspec) > 1) {
	char after = get_fragment(&colorspec, 1);
	if (IsSpace(after))
	    simple = 1;
    }
    if (simple) {
	char ch = pop_fragment(&colorspec);

	TRACE(("got ReGIS RGB colorspec pattern '%c' with arguments: \"%s\"\n",
	       ch, fragment_to_tempstr(&colorspec)));
	switch (ch) {
	case 'D':
	case 'd':
	    r = 0;
	    g = 0;
	    b = 0;
	    l = 0;
	    break;
	case 'R':
	case 'r':
	    r = 100;
	    g = 0;
	    b = 0;
	    l = 46;
	    break;
	case 'G':
	case 'g':
	    r = 0;
	    g = 100;
	    b = 0;
	    l = 50;
	    break;
	case 'B':
	case 'b':
	    r = 0;
	    g = 0;
	    b = 100;
	    l = 50;
	    break;
	case 'C':
	case 'c':
	    r = 0;
	    g = 100;
	    b = 100;
	    l = 50;
	    break;
	case 'Y':
	case 'y':
	    r = 100;
	    g = 100;
	    b = 0;
	    l = 50;
	    break;
	case 'M':
	case 'm':
	    r = 100;
	    g = 0;
	    b = 100;
	    l = 50;
	    break;
	case 'W':
	case 'w':
	    r = 100;
	    g = 100;
	    b = 100;
	    l = 100;
	    break;
	default:
	    TRACE(("unknown RGB color name: \"%c\"\n", ch));
	    return 0;
	}
    } else {
	RegisDataFragment num;
	int max, val;
	char comp;
	short h = -1;
	short s = -1;

	while (colorspec.pos < colorspec.len) {
	    if (skip_regis_whitespace(&colorspec))
		continue;

	    comp = pop_fragment(&colorspec);
	    switch (comp) {
	    case ',':
		/* not sure if this is valid, but it is easy to handle */
		continue;
	    case 'H':
	    case 'h':
		max = 360;
		comp = 'H';
		break;
	    case 'L':
	    case 'l':
		max = 100;
		comp = 'L';
		break;
	    case 'S':
	    case 's':
		max = 100;
		comp = 'S';
		break;
#ifdef ENABLE_RGB_COLORSPECS
	    case 'R':		/* RLogin extension */
	    case 'r':
		max = 100;
		comp = 'R';
		break;
	    case 'G':		/* RLogin extension */
	    case 'g':
		max = 100;
		comp = 'G';
		break;
	    case 'B':		/* RLogin extension */
	    case 'b':
		max = 100;
		comp = 'B';
		break;
#endif
	    default:
		TRACE(("unrecognized character in colorspec: \"%c\"\n", comp));
		return 0;
	    }

	    skip_regis_whitespace(&colorspec);
	    if (!extract_regis_num(&colorspec, &num)) {
		TRACE(("unrecognized character in colorspec: \"%c\"\n", comp));
		return 0;
	    }
	    if (!regis_num_to_int(&num, &val)) {
		TRACE(("DATA_ERROR: component value %s is not a number\n",
		       fragment_to_tempstr(&num)));
		return 0;
	    }
	    /* FIXME: error, truncate, wrap, ...? */
	    if (val < 0 || val > max) {
		TRACE(("DATA_ERROR: component value %d out of range\n", val));
		return 0;
	    }

	    switch (comp) {
	    case 'H':
		h = (short) val;
		break;
	    case 'L':
		l = (short) val;
		break;
	    case 'S':
		s = (short) val;
		break;
	    case 'R':
		r = (short) val;
		break;
	    case 'G':
		g = (short) val;
		break;
	    case 'B':
		b = (short) val;
		break;
	    }
	}

	if (h >= 0 && l >= 0 && s >= 0 && r < 0 && g < 0 && b < 0) {
	    TRACE(("found HLS colorspec to be converted: %hd,%hd,%hd\n",
		   h, l, s));
	    hls2rgb(h, l, s, &r, &g, &b);
	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
	} else if (h < 0 && l < 0 && s < 0 && r >= 0 && g >= 0 && b >= 0) {
	    TRACE(("found RGB colorspec: %hd,%hd,%hd\n", r, g, b));
	    l = (short) ((MIN3(r, g, b) + MAX3(r, g, b)) / 2);
	    TRACE(("calculated L: %d\n", l));
	} else if (h < 0 && l >= 0 && s < 0 && r < 0 && g < 0 && b < 0) {
	    TRACE(("found L colorspec to be converted: %hd,%hd,%hd\n",
		   h, l, s));
	    hls2rgb(0, l, 0, &r, &g, &b);
	    TRACE(("converted to RGB: %hd,%hd,%hd\n", r, g, b));
	} else {
	    TRACE(("unrecognized colorspec format\n"));
	    return 0;
	}
    }

    /*
     * The VT240 and VT330 models convert to the closest grayscale value.
     */
    if (context->terminal_id == 240 || context->terminal_id == 330) {
	hls2rgb(0, l, 0, &r, &g, &b);
	TRACE(("converted to grayscale: %hd,%hd,%hd\n", r, g, b));
    }

    *r_out = r;
    *g_out = g;
    *b_out = b;

    skip_regis_whitespace(&colorspec);
    if (colorspec.pos < colorspec.len) {
	char skip;

	skip = pop_fragment(&colorspec);
	(void) skip;		/* variable needed only if tracing */
	TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS colorspec \"%c\"\n",
	       skip));
    }

    return 1;
}

static int
load_regis_regnum_or_colorspec(RegisGraphicsContext const *context,
			       RegisDataFragment const *input,
			       RegisterNum *out)
{
    int val;
    RegisDataFragment colorspec;
    RegisDataFragment num;
    RegisDataFragment coloroption;

    copy_fragment(&colorspec, input);
    TRACE(("looking at colorspec pattern: \"%s\"\n",
	   fragment_to_tempstr(&colorspec)));

    skip_regis_whitespace(&colorspec);

    if (extract_regis_num(&colorspec, &num)) {
	if (!regis_num_to_int(&num, &val)) {
	    TRACE(("DATA_ERROR: colorspec value %s is not a valid register\n",
		   fragment_to_tempstr(&num)));
	    return 0;
	}
	if (val < 0) {
	    /* FIXME: error, truncate, wrap, ...? */
	    TRACE(("DATA_ERROR: ignoring negative colorspec value: %d\n", val));
	    return 0;
	}
	if (val >= (int) context->destination_graphic->valid_registers) {
	    /* FIXME: error, truncate, wrap, ...? */
	    TRACE(("DATA_ERROR: colorspec value %d is too big; wrapping\n",
		   val));
	    val %= (int) context->destination_graphic->valid_registers;
	}

	TRACE(("colorspec contains index for register %u\n", val));
	*out = (RegisterNum) val;

	skip_regis_whitespace(&colorspec);
	if (colorspec.pos < colorspec.len) {
	    char skip;

	    skip = pop_fragment(&colorspec);
	    (void) skip;	/* variable needed only if tracing */
	    TRACE(("DATA_ERROR: unexpected character after register \"%c\"\n",
		   skip));
	    return 0;
	}

	return 1;
    }

    if (extract_regis_parenthesized_data(&colorspec, &coloroption)) {
	short r, g, b;

	if (!load_regis_colorspec(context, &coloroption, &r, &g, &b)) {
	    TRACE(("unable to parse colorspec\n"));
	    return 0;
	}

	*out = find_color_register(context->destination_graphic->color_registers,
				   r, g, b);
	TRACE(("colorspec maps to closest register %u\n", *out));

	return 1;
    }

    TRACE(("expected register number or colorspec, but found: \"%s\"\n",
	   fragment_to_tempstr(&colorspec)));
    return 0;
}

static int
to_scaled_int(char const *num, int scale, int *value)
{
    unsigned long whole, frac;
    char *end;

    /* FIXME: handle whitespace? how about trailing junk? */
    whole = strtoul(num, &end, 10);
    if (end[0] == '.') {
	char temp[5] = "0000";

	if (end[1] != '\0') {
	    temp[0] = end[1];
	    if (end[2] != '\0') {
		temp[1] = end[2];
		if (end[3] != '\0') {
		    temp[2] = end[3];
		    if (end[4] != '\0') {
			temp[3] = end[4];
		    }
		}
	    }
	}
	frac = strtoul(temp, NULL, 10);
    } else if (end[0] == '\0' || end[0] == ',' || IsSpace(end[0])) {
	frac = 0;
    } else {
	TRACE(("unexpected character %c in number %s\n", end[0], num));
	return 0;
    }

    *value = (int) (whole * (unsigned) scale +
		    (frac * (unsigned) scale) / 10000);

    return 1;
}

static int
load_regis_raw_extent(char const *extent, int *relx, int *rely,
		      int *xloc, int *yloc, int scale)
{
    int xsign, ysign;
    char const *xpart;
    char const *ypart;

    xpart = extent;
    if ((ypart = strchr(extent, ','))) {
	ypart++;
    } else {
	ypart = "";
    }

    while (IsSpace(xpart[0]))
	xpart++;
    while (IsSpace(ypart[0]))
	ypart++;

    if (xpart[0] == '-') {
	xsign = -1;
	xpart++;
    } else if (xpart[0] == '+') {
	xsign = +1;
	xpart++;
    } else {
	xsign = 0;
    }
    if (ypart[0] == '-') {
	ysign = -1;
	ypart++;
    } else if (ypart[0] == '+') {
	ysign = +1;
	ypart++;
    } else {
	ysign = 0;
    }

    if (xpart[0] == '\0' || xpart[0] == ',') {
	*relx = 1;
	*xloc = 0;
    } else if (xsign == 0) {
	int val;

	if (!to_scaled_int(xpart, scale, &val))
	    return 0;
	*relx = 0;
	*xloc = val;
    } else {
	int val;

	if (!to_scaled_int(xpart, scale, &val))
	    return 0;
	*relx = 1;
	*xloc = xsign * val;
    }
    if (ypart[0] == '\0') {
	*rely = 1;
	*yloc = 0;
    } else if (ysign == 0) {
	int val;

	if (!to_scaled_int(ypart, scale, &val))
	    return 0;
	*rely = 0;
	*yloc = val;
    } else {
	int val;

	if (!to_scaled_int(ypart, scale, &val))
	    return 0;
	*rely = 1;
	*yloc = ysign * val;
    }

    return 1;
}

static int
load_regis_pixel_extent(char const *extent, int origx, int origy,
			int *xloc, int *yloc)
{
    int relx, rely;
    int px, py;

    if (!load_regis_raw_extent(extent, &relx, &rely, &px, &py, 1)) {
	TRACE(("invalid coordinates in extent %s\n", extent));
	return 0;
    }

    *xloc = px;
    *yloc = py;

    if (relx)
	*xloc += origx;
    if (rely)
	*yloc += origy;

    return 1;
}

#define COORD_SCALE 1000

static int
load_regis_coord_extent(RegisGraphicsContext const *context, char const *extent,
			int origx, int origy, int *xloc, int *yloc)
{
    int relx, rely;
    int ux, uy;

    if (!load_regis_raw_extent(extent, &relx, &rely, &ux, &uy, COORD_SCALE)) {
	TRACE(("invalid coordinates in extent %s\n", extent));
	return 0;
    }

    if (relx) {
	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
	TRACE(("converted relative user X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
	       ux / (double) COORD_SCALE, px, context->width,
	       context->x_off, context->x_div));
	*xloc = origx + px;
    } else {
	const int px = TRANSLATE_XCOORD(context, ux, COORD_SCALE);
	TRACE(("converted absolute user X coord %.03f to absolute pixel X coord %d\n",
	       ux / (double) COORD_SCALE, px));
	*xloc = px;
    }
    if (rely) {
	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);
	TRACE(("converted relative user Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
	       uy / (double) COORD_SCALE, py, context->height,
	       context->y_off, context->y_div));
	*yloc = origy + py;
    } else {
	const int py = TRANSLATE_YCOORD(context, uy, COORD_SCALE);
	TRACE(("converted absolute user Y coord %.03f to absolute pixel Y coord %d\n",
	       uy / (double) COORD_SCALE, py));
	*yloc = py;
    }

    return 1;
}

static int
load_regis_raw_pixelvector_digit(char const *pixelvector,
				 unsigned *offset,
				 int *dx, int *dy, int mul)
{
    switch (pixelvector[*offset]) {
    case '0':
	*dx += mul;
	break;
    case '1':
	*dx += mul;
	*dy -= mul;
	break;
    case '2':
	*dy -= mul;
	break;
    case '3':
	*dx -= mul;
	*dy -= mul;
	break;
    case '4':
	*dx -= mul;
	break;
    case '5':
	*dx -= mul;
	*dy += mul;
	break;
    case '6':
	*dy += mul;
	break;
    case '7':
	*dx += mul;
	*dy += mul;
	break;
    default:
	return 0;
    }

    (*offset)++;
    return 1;
}

static int
load_regis_pixel_pixelvector(char const *pixelvector,
			     int mul,
			     int origx, int origy,
			     int *xloc, int *yloc)
{
    int found = 0;
    int px = 0, py = 0;
    unsigned offset = 0U;
    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
					    &px, &py,
					    mul))
	found = 1;
    if (pixelvector[offset] != '\0') {
	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
	       &pixelvector[offset]));
    }

    *xloc = origx + px;
    *yloc = origy + py;

    return found;
}

static int
load_regis_coord_pixelvector(RegisGraphicsContext const *context,
			     char const *pixelvector,
			     int origx, int origy,
			     int *xloc, int *yloc)
{
    const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE);
    int found = 0;
    int ux = 0, uy = 0;
    unsigned offset = 0U;
    while (load_regis_raw_pixelvector_digit(pixelvector, &offset,
					    &ux, &uy,
					    mul))
	found = 1;
    if (pixelvector[offset] != '\0') {
	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
	       &pixelvector[offset]));
    } {
	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);

	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
	       ux / (double) COORD_SCALE, px, context->width,
	       context->x_off, context->x_div));
	*xloc = origx + px;

	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
	       uy / (double) COORD_SCALE, py, context->height,
	       context->y_off, context->y_div));
	*yloc = origy + py;
    }

    return found;
}

static int
load_regis_coord_pixelvector_step(RegisGraphicsContext const *context,
				  char const *pixelvector,
				  unsigned *offset,
				  int origx, int origy,
				  int *xloc, int *yloc)
{
    const int mul = (int) (context->temporary_write_controls.pv_multiplier * COORD_SCALE);
    int found = 0;
    int ux = 0, uy = 0;
    if (load_regis_raw_pixelvector_digit(pixelvector, offset, &ux, &uy, mul))
	found = 1;
    if (!found && pixelvector[*offset] != '\0') {
	TRACE(("DATA_ERROR: ignoring unknown pixel vector digits: \"%s\"\n",
	       &pixelvector[*offset]));
    } {
	const int px = SCALE_XCOORD(context, ux, COORD_SCALE);
	const int py = SCALE_YCOORD(context, uy, COORD_SCALE);

	TRACE(("converted relative X coord %.03f to relative pixel X coord %d (width=%d xoff=%d xdiv=%d)\n",
	       ux / (double) COORD_SCALE, px, context->width,
	       context->x_off, context->x_div));
	*xloc = origx + px;

	TRACE(("converted relative Y coord %.03f to relative pixel Y coord %d (height=%d yoff=%d ydiv=%d)\n",
	       uy / (double) COORD_SCALE, py, context->height,
	       context->y_off, context->y_div));
	*yloc = origy + py;
    }

    return found;
}

static int
load_regis_write_control(RegisParseState *state,
			 RegisGraphicsContext const *context,
			 int cur_x, int cur_y,
			 int option,
			 RegisDataFragment *arg,
			 RegisWriteControls *out)
{
    TRACE(("checking write control option \"%c\" with arg \"%s\"\n",
	   option, fragment_to_tempstr(arg)));
    switch (option) {
    case 'C':
    case 'c':
	TRACE(("write control compliment writing mode \"%s\"\n",
	       fragment_to_tempstr(arg)));
	out->write_style = WRITE_STYLE_COMPLEMENT;
	break;
    case 'E':
    case 'e':
	TRACE(("write control erase writing mode \"%s\"\n",
	       fragment_to_tempstr(arg)));
	out->write_style = WRITE_STYLE_ERASE;
	break;
    case 'F':
    case 'f':
	TRACE(("write control plane write mask \"%s\"\n",
	       fragment_to_tempstr(arg)));
	{
	    int val;
	    if (!regis_num_to_int(arg, &val) ||
		val < 0 || val >= (int) context->destination_graphic->valid_registers) {
		TRACE(("interpreting out of range value as 0 FIXME\n"));
		out->plane_mask = 0U;
	    } else {
		out->plane_mask = (unsigned) val;
	    }
	}
	break;
    case 'I':
    case 'i':
	TRACE(("write control foreground color \"%s\"\n",
	       fragment_to_tempstr(arg)));
	if (!load_regis_regnum_or_colorspec(context, arg, &out->foreground)) {
	    TRACE(("DATA_ERROR: write control foreground color specifier not recognized: \"%s\"\n",
		   fragment_to_tempstr(arg)));
	    return 0;
	}
	break;
    case 'L':
    case 'l':
	TRACE(("write control line width \"%s\" (FIXME: currently ignored)\n",
	       fragment_to_tempstr(arg)));
	{
	    int val;
	    if (!regis_num_to_int(arg, &val) ||
		val < 0 || val >= (int) 9) {
		TRACE(("interpreting out of range value as 1 FIXME\n"));
		out->line_width = 1U;
	    } else {
		out->line_width = (unsigned) val;
	    }
	}
	break;
    case 'M':
    case 'm':
	TRACE(("write control found pixel multiplication factor \"%s\"\n",
	       fragment_to_tempstr(arg)));
	{
	    int val;
	    if (!regis_num_to_int(arg, &val) || val <= 0) {
		TRACE(("interpreting out of range value %d as 1 FIXME\n", val));
		out->pv_multiplier = 1U;
	    } else {
		out->pv_multiplier = (unsigned) val;
	    }
	}
	break;
    case 'N':
    case 'n':
	TRACE(("write control negative pattern control \"%s\"\n",
	       fragment_to_tempstr(arg)));
	{
	    int val;
	    if (!regis_num_to_int(arg, &val)) {
		val = -1;
	    }
	    switch (val) {
	    default:
		TRACE(("interpreting out of range value %d as 0 FIXME\n", val));
		out->invert_pattern = 0U;
		break;
	    case 0:
		out->invert_pattern = 0U;
		break;
	    case 1:
		out->invert_pattern = 1U;
		break;
	    }
	}
	break;
    case 'P':
    case 'p':
	TRACE(("write control found pattern control \"%s\"\n",
	       fragment_to_tempstr(arg)));
	{
	    RegisDataFragment suboptionset;
	    RegisDataFragment suboptionarg;
	    RegisDataFragment item;
	    char suboption;

	    while (arg->pos < arg->len) {
		if (skip_regis_whitespace(arg))
		    continue;

		TRACE(("looking for option in \"%s\"\n",
		       fragment_to_tempstr(arg)));
		if (extract_regis_parenthesized_data(arg, &suboptionset)) {
		    TRACE(("got write pattern suboptionset: \"%s\"\n",
			   fragment_to_tempstr(&suboptionset)));
		    while (suboptionset.pos < suboptionset.len) {
			skip_regis_whitespace(&suboptionset);
			if (extract_regis_option(&suboptionset, &suboption,
						 &suboptionarg)) {
			    skip_regis_whitespace(&suboptionarg);
			    TRACE(("inspecting write pattern suboption \"%c\" with value \"%s\"\n",
				   suboption,
				   fragment_to_tempstr(&suboptionarg)));
			    switch (suboption) {
			    case 'M':
			    case 'm':
				TRACE(("found pattern multiplier \"%s\"\n",
				       fragment_to_tempstr(&suboptionarg)));
				{
				    RegisDataFragment num;
				    int val;

				    if (extract_regis_num(&suboptionarg,
							  &num)) {
					if (!regis_num_to_int(&num, &val)
					    || val < 1) {
					    TRACE(("interpreting out of range pattern multiplier \"%s\" as 2 FIXME\n",
						   fragment_to_tempstr(&num)));
					    out->pattern_multiplier = 2U;
					} else {
					    out->pattern_multiplier =
						(unsigned) val;
					}
					skip_regis_whitespace(&suboptionarg);
				    }

				    if (fragment_len(&suboptionarg)) {
					TRACE(("DATA_ERROR: unknown content after pattern multiplier \"%s\"\n",
					       fragment_to_tempstr(&suboptionarg)));
					return 0;
				    }
				}
				break;
			    default:
				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
				       suboption,
				       fragment_to_tempstr(&suboptionarg)));
				return 0;
			    }
			    continue;
			}

			TRACE(("DATA_ERROR: skipping unknown token in pattern control suboptionset (expecting option): \"%s\"\n",
			       fragment_to_tempstr(&suboptionset)));
			pop_fragment(&suboptionset);
		    }
		    continue;
		}

		TRACE(("looking for int in \"%s\"\n",
		       fragment_to_tempstr(arg)));
		if (extract_regis_num(arg, &item)) {
		    if (peek_fragment(&item) == '0' ||
			peek_fragment(&item) == '1') {
			unsigned pattern = 0U;
			unsigned bitcount;

			TRACE(("converting pattern bits \"%s\"\n",
			       fragment_to_tempstr(&item)));
			for (bitcount = 0;; bitcount++) {
			    char ch = pop_fragment(&item);
			    if (ch == '\0')
				break;
			    switch (ch) {
			    case '0':
				if (bitcount < MAX_PATTERN_BITS) {
				    pattern <<= 1U;
				}
				break;
			    case '1':
				if (bitcount < MAX_PATTERN_BITS) {
				    pattern <<= 1U;
				    pattern |= 1U;
				}
				break;
			    default:
				TRACE(("DATA_ERROR: unknown ReGIS write pattern bit value \"%c\"\n",
				       ch));
				return 0;
			    }
			}

			if (bitcount > 0U) {
			    unsigned extrabits;

			    for (extrabits = 0;
				 bitcount + extrabits < MAX_PATTERN_BITS;
				 extrabits++) {
				if (pattern & (1U << (bitcount - 1U))) {
				    pattern <<= 1U;
				    pattern |= 1U;
				} else {
				    pattern <<= 1U;
				}
			    }
			}

			out->pattern = pattern;
		    } else {
			int val;

			TRACE(("converting pattern id \"%s\"\n",
			       fragment_to_tempstr(&item)));
			if (!regis_num_to_int(&item, &val))
			    val = -1;
			switch (val) {	/* FIXME: exponential allowed? */
			case 0:
			    out->pattern = 0x00;	/* solid bg */
			    break;
			case 1:
			    out->pattern = 0xff;	/* solid fg */
			    break;
			case 2:
			    out->pattern = 0xf0;	/* dash */
			    break;
			case 3:
			    out->pattern = 0xe4;	/* dash dot */
			    break;
			case 4:
			    out->pattern = 0xaa;	/* dot */
			    break;
			case 5:
			    out->pattern = 0xea;	/* dash dot dot */
			    break;
			case 6:
			    out->pattern = 0x88;	/* sparse dot */
			    break;
			case 7:
			    out->pattern = 0x84;	/* asymmetric sparse dot */
			    break;
			case 8:
			    out->pattern = 0xc8;	/* sparse dash dot */
			    break;
			case 9:
			    out->pattern = 0x86;	/* sparse dot dash */
			    break;
			default:
			    TRACE(("DATA_ERROR: unknown ReGIS standard write pattern \"%d\"\n",
				   val));
			    return 0;
			}
		    }

		    TRACE(("final pattern is %02x\n", out->pattern));
		    continue;
		}
		skip_regis_whitespace(arg);

		TRACE(("DATA_ERROR: skipping unknown token in pattern suboption: \"%s\"\n",
		       fragment_to_tempstr(arg)));
		pop_fragment(arg);
	    }
	}
	break;
    case 'R':
    case 'r':
	TRACE(("write control switch to replacement writing mode \"%s\"\n",
	       fragment_to_tempstr(arg)));
	out->write_style = WRITE_STYLE_REPLACE;
	break;
    case 'S':
    case 's':
	TRACE(("write control shading control \"%s\"\n",
	       fragment_to_tempstr(arg)));
	{
	    RegisDataFragment suboptionset;
	    RegisDataFragment suboptionarg;
	    RegisDataFragment item;
	    char suboption;
	    char shading_character = '\0';
	    unsigned reference_dim = WRITE_SHADING_REF_Y;
	    /* FIXME: are relative offsets additive? */
	    int ref_x = cur_x, ref_y = cur_y;
	    int shading_enabled = 0;

	    while (arg->pos < arg->len) {
		if (skip_regis_whitespace(arg))
		    continue;

		if (extract_regis_string(arg, state->temp, state->templen)) {
		    TRACE(("found fill char \"%s\"\n", state->temp));
		    /* FIXME: allow longer strings, ignore extra chars, or treat as error? */
		    if (strlen(state->temp) != 1) {
			TRACE(("DATA_ERROR: expected exactly one char in fill string FIXME\n"));
			return 0;
		    }
		    shading_character = state->temp[0];
		    shading_enabled = 1;
		    TRACE(("shading character is: '%c' (%d)\n",
			   shading_character, (int) shading_character));
		    continue;
		}

		if (extract_regis_parenthesized_data(arg, &suboptionset)) {
		    skip_regis_whitespace(&suboptionset);
		    TRACE(("got shading control suboptionset: \"%s\"\n",
			   fragment_to_tempstr(&suboptionset)));
		    while (suboptionset.pos < suboptionset.len) {
			if (skip_regis_whitespace(&suboptionset))
			    continue;
			if (extract_regis_option(&suboptionset, &suboption,
						 &suboptionarg)) {
			    TRACE(("inspecting write shading suboption \"%c\" with value \"%s\"\n",
				   suboption,
				   fragment_to_tempstr(&suboptionarg)));
			    switch (suboption) {
			    case 'X':
			    case 'x':
				TRACE(("found horizontal shading suboption \"%s\"\n",
				       fragment_to_tempstr(&suboptionarg)));
				if (fragment_len(&suboptionarg)) {
				    TRACE(("DATA_ERROR: unexpected value to horizontal shading suboption FIXME\n"));
				    return 0;
				}
				reference_dim = WRITE_SHADING_REF_X;
				shading_enabled = 1;
				break;
			    default:
				TRACE(("DATA_ERROR: unknown ReGIS write pattern suboption '%c' arg \"%s\"\n",
				       suboption,
				       fragment_to_tempstr(&suboptionarg)));
				return 0;
			    }
			    continue;
			}

			TRACE(("DATA_ERROR: skipping unknown token in shading control suboptionset (expecting option): \"%s\"\n",
			       fragment_to_tempstr(&suboptionset)));
			pop_fragment(&suboptionset);
		    }
		    continue;
		}

		if (extract_regis_extent(arg, &item)) {
		    TRACE(("found extent in shading option curr=%d,%d ref=%d,%d\n",
			   cur_x, cur_y, ref_x, ref_y));
		    if (!load_regis_coord_extent(context,
						 fragment_to_tempstr(&item),
						 ref_x, ref_y,
						 &ref_x, &ref_y)) {
			TRACE(("DATA_ERROR: unable to parse extent in write shading option '%c': \"%s\"\n",
			       option, fragment_to_tempstr(&item)));
			return 0;
		    }
		    TRACE(("shading reference = %d,%d (%s)\n", ref_x, ref_y,
			   ((reference_dim == WRITE_SHADING_REF_X)
			    ? "X"
			    : "Y")));
		    continue;
		}

		if (extract_regis_num(arg, &item)) {
		    if (!regis_num_to_int(&item, &shading_enabled)) {
			TRACE(("DATA_ERROR: unable to parse int in write shading option '%c': \"%s\"\n",
			       option, fragment_to_tempstr(&item)));
			return 0;
		    }
		    if (shading_enabled < 0 || shading_enabled > 1) {
			TRACE(("interpreting out of range value %d as 0 FIXME\n",
			       shading_enabled));
			shading_enabled = 0;
		    }
		    TRACE(("shading enabled = %d\n", shading_enabled));
		    continue;
		}

		if (skip_regis_whitespace(arg)) {
		    continue;
		}

		TRACE(("DATA_ERROR: skipping unknown token in shade suboption: \"%s\"\n",
		       fragment_to_tempstr(arg)));
		pop_fragment(arg);
	    }

	    if (shading_enabled) {
		out->shading_enabled = 1U;
		out->shading_reference_dim = reference_dim;
		out->shading_reference = ((reference_dim == WRITE_SHADING_REF_X)
					  ? ref_x
					  : ref_y);
		out->shading_character = shading_character;
		TRACE(("final shading state: enabled, dim=%d ref=%d, char=%c\n",
		       out->shading_reference_dim, out->shading_reference,
		       out->shading_character));
	    } else {
		/* FIXME: confirm there is no effect if shading isn't enabled
		 * in the same command
		 */
		out->shading_enabled = 0U;
		TRACE(("final shading state: shading disabled\n"));
	    }
	}
	break;
    case 'V':
    case 'v':
	TRACE(("write control switch to overlay writing mode \"%s\"\n",
	       fragment_to_tempstr(arg)));
	out->write_style = WRITE_STYLE_OVERLAY;
	break;
    default:
	TRACE(("DATA_ERROR: ignoring unknown ReGIS write option \"%c\" arg \"%s\"\n",
	       option, fragment_to_tempstr(arg)));
	return 0;
    }

    return 1;
}

static int
load_regis_write_control_set(RegisParseState *state,
			     RegisGraphicsContext const *context,
			     int cur_x, int cur_y,
			     RegisDataFragment *controls,
			     RegisWriteControls *out)
{
    RegisDataFragment optionset;
    RegisDataFragment arg;
    char option;

    while (controls->pos < controls->len) {
	if (skip_regis_whitespace(controls))
	    continue;

	if (extract_regis_parenthesized_data(controls, &optionset)) {
	    TRACE(("got write control optionset: \"%s\"\n",
		   fragment_to_tempstr(&optionset)));
	    while (optionset.pos < optionset.len) {
		skip_regis_whitespace(&optionset);
		if (extract_regis_option(&optionset, &option, &arg)) {
		    skip_regis_whitespace(&arg);
		    TRACE(("got write control option and value: \"%c\" \"%s\"\n",
			   option, fragment_to_tempstr(&arg)));
		    if (!load_regis_write_control(state, context,
						  cur_x, cur_y,
						  option, &arg, out)) {
			return 0;
		    }
		    continue;
		}

		TRACE(("DATA_ERROR: skipping unknown token in write control optionset (expecting option): \"%s\"\n",
		       fragment_to_tempstr(&optionset)));
		pop_fragment(&optionset);
	    }
	    continue;
	}

	TRACE(("DATA_ERROR: skipping unknown token in write controls (expecting optionset): \"%s\"\n",
	       fragment_to_tempstr(controls)));
	pop_fragment(controls);
    }

    return 1;
}

static void
init_regis_write_controls(int terminal_id, unsigned all_planes,
			  RegisWriteControls *controls)
{
    controls->pv_multiplier = 1U;
    controls->pattern = 0xff;	/* solid */
    controls->pattern_multiplier = 2U;
    controls->invert_pattern = 0U;
    controls->plane_mask = all_planes;
    controls->write_style = WRITE_STYLE_OVERLAY;
    switch (terminal_id) {
    case 125:			/* FIXME: verify */
    case 240:			/* FIXME: verify */
    case 241:			/* FIXME: verify */
    case 330:
	controls->foreground = 3U;
	break;
    case 340:
    default:
	controls->foreground = 7U;
	break;
    case 382:
	controls->foreground = 1U;	/* FIXME: verify */
	break;
    }
    controls->shading_enabled = 0U;
    controls->shading_character = '\0';
    controls->shading_reference = 0;	/* no meaning if shading is disabled */
    controls->shading_reference_dim = WRITE_SHADING_REF_NONE;
    controls->line_width = 1U;
    /* FIXME: add the rest */
}

static void
map_regis_graphics_pages(XtermWidget xw, RegisGraphicsContext *context)
{
    const int charrow = 0;
    const int charcol = 0;
    unsigned old_display_id = ~0U;

    if (context->destination_graphic)
	context->destination_graphic->hidden = 1;
    if (context->display_graphic) {
	context->display_graphic->hidden = 1;
	old_display_id = context->display_graphic->id;
    }

    context->destination_graphic = get_new_or_matching_graphic(xw,
							       charrow, charcol,
							       context->width,
							       context->height,
							       context->destination_page);
    if (context->destination_graphic) {
	context->destination_graphic->hidden = 1;
	context->destination_graphic->valid = 1;
    }

    context->display_graphic = get_new_or_matching_graphic(xw,
							   charrow, charcol,
							   context->width,
							   context->height,
							   context->display_page);
    if (context->display_graphic) {
	context->display_graphic->hidden = 0;
	if (old_display_id != context->display_graphic->id) {
	    if (!context->display_graphic->valid) {
		draw_solid_rectangle(context->display_graphic, 0, 0,
				     context->width, context->height,
				     context->background);
	    }
	    context->display_graphic->dirty = 1;
	    context->force_refresh = 1;
	    /* FIXME: This isn't really enough.  If there are holes in the new
	     * graphic they should be cleared and set to the text from the same
	     * page.  But we don't have pages for text in xterm (the alt buffer
	     * is similar though).
	     */
	}
	context->display_graphic->valid = 1;
    }

    TRACE(("using graphics destination=[%d -> %u] display=[%d -> %u]\n",
	   context->destination_page,
	   (context->destination_graphic
	    ? context->destination_graphic->id
	    : 0U),
	   context->display_page,
	   (context->display_graphic
	    ? context->display_graphic->id
	    : 0U)));
}

static void
copy_regis_write_controls(RegisWriteControls const *src,
			  RegisWriteControls *dst)
{
    dst->pv_multiplier = src->pv_multiplier;
    dst->pattern = src->pattern;
    dst->pattern_multiplier = src->pattern_multiplier;
    dst->invert_pattern = src->invert_pattern;
    dst->foreground = src->foreground;
    dst->plane_mask = src->plane_mask;
    dst->write_style = src->write_style;
    dst->shading_enabled = src->shading_enabled;
    dst->shading_character = src->shading_character;
    dst->shading_reference = src->shading_reference;
    dst->shading_reference_dim = src->shading_reference_dim;
    dst->line_width = src->line_width;
}

static void
init_regis_text_controls(RegisTextControls *controls)
{
    controls->alphabet_num = 0U;	/* built-in */
    controls->character_set_l = 0U;	/* ASCII */
    controls->character_set_r = 0U;	/* Latin-1 */
    get_standard_character_size(1, &controls->character_display_w,
				&controls->character_display_h,
				&controls->character_unit_cell_w,
				&controls->character_unit_cell_h,
				&controls->character_inc_x,
				&controls->character_inc_y);
    controls->string_rotation = 0;
    controls->character_rotation = 0;
    controls->slant = 0;
}

static void
copy_regis_text_controls(RegisTextControls const *src, RegisTextControls *dst)
{
    dst->alphabet_num = src->alphabet_num;
    dst->character_set_l = src->character_set_l;
    dst->character_set_r = src->character_set_r;
    dst->character_display_w = src->character_display_w;
    dst->character_display_h = src->character_display_h;
    dst->character_unit_cell_w = src->character_unit_cell_w;
    dst->character_unit_cell_h = src->character_unit_cell_h;
    dst->character_inc_x = src->character_inc_x;
    dst->character_inc_y = src->character_inc_y;
    dst->string_rotation = src->string_rotation;
    dst->character_rotation = src->character_rotation;
    dst->slant = src->slant;
}

static void
init_regis_alphabets(RegisGraphicsContext *context)
{
    unsigned alphabet_index;

    for (alphabet_index = 0U; alphabet_index < MAX_REGIS_ALPHABETS;
	 alphabet_index++) {
	context->alphabets[alphabet_index].alphabet_num = INVALID_ALPHABET_NUM;
	context->alphabets[alphabet_index].pixw = 0U;
	context->alphabets[alphabet_index].pixh = 0U;
	context->alphabets[alphabet_index].name[0] = '\0';
	context->alphabets[alphabet_index].fontname[0] = '\0';
	context->alphabets[alphabet_index].use_font = 0;
	context->alphabets[alphabet_index].bytes = NULL;
    }
}

static void
init_regis_graphics_context(int terminal_id, int width, int height,
			    unsigned max_colors, const char *builtin_font,
			    RegisGraphicsContext *context)
{
    context->destination_graphic = NULL;
    context->display_graphic = NULL;
    context->display_page = 0U;
    context->destination_page = 0U;
    context->terminal_id = terminal_id;

    /* reset addressing / clear user coordinates */
    context->width = width;
    context->height = height;
    context->x_off = 0;
    context->y_off = 0;
    context->x_div = width - 1;
    context->y_div = height - 1;

    /*
     * Generate a mask covering all valid color register address bits
     * (but don't bother past 2**16).
     */
    context->all_planes = max_colors;
    context->all_planes--;
    context->all_planes |= 1U;
    context->all_planes |= context->all_planes >> 1U;
    context->all_planes |= context->all_planes >> 2U;
    context->all_planes |= context->all_planes >> 4U;
    context->all_planes |= context->all_planes >> 8U;

    context->builtin_font = builtin_font;

    init_regis_write_controls(terminal_id, context->all_planes,
			      &context->persistent_write_controls);
    copy_regis_write_controls(&context->persistent_write_controls,
			      &context->temporary_write_controls);

    init_regis_text_controls(&context->persistent_text_controls);
    context->current_text_controls = &context->persistent_text_controls;
    init_regis_alphabets(context);

    context->multi_input_mode = 0;
    /* FIXME: coordinates */
    /* FIXME: scrolling */
    context->background = 0U;
    /* FIXME: input cursor location */
    /* FIXME: input cursor style */
    context->graphics_output_cursor_x = 0;
    context->graphics_output_cursor_y = 0;
    /* FIXME: output cursor style */

    context->force_refresh = 0;
}

static int
parse_regis_command(RegisParseState *state)
{
    char ch = peek_fragment(&state->input);
    if (ch == '\0')
	return 0;

    if (!extract_regis_command(&state->input, &ch))
	return 0;

    switch (ch) {
    case 'C':
    case 'c':
	/* Curve

	 * C
	 * (A)  # set the arc length in degrees (+ or nothing for
	 *      # counter-clockwise, - for clockwise, rounded to the
	 *      # closest integer degree)
	 * (B)  # begin closed curve sequence (must have at least two
	 *      # values; this option can not be nested)
	 * (C)  # position is the center, current location is the
	 *      # circumference (stays in effect until next command)
	 * (E)  # end curve sequence (drawing is performed here)
	 * (S)  # begin open curve sequence
	 * (W)  # temporary write options (see write command)
	 * [<center, circumference position>]  # center if (C), otherwise point on circumference
	 * [<point in curve sequence>]...  # if between (B) and (E)
	 * <pv>...  # if between (B) and (E)
	 */
	TRACE(("found ReGIS command \"%c\" (curve)\n", ch));
	state->command = 'c';
	state->curve_mode = CURVE_POSITION_ARC_EDGE;
	state->arclen = 360;
	state->num_points = 0U;
	break;
    case 'F':
    case 'f':
	/* Fill

	 * F
	 * (V)  # polygon (see vector command)
	 * (C)  # curve (see curve command)
	 * (W)  # temporary write options (see write command)
	 */
	TRACE(("found ReGIS command \"%c\" (filled polygon)\n", ch));
	state->command = 'f';
	break;
    case 'L':
    case 'l':
	/* Load

	 * L
	 * (A)  # set character set number or name
	 * (F)"fontname"  # load from font (xterm extension)
	 * (S)[w,h]  # set glyph size (xterm extension)
	 * "ascii"xx,xx,xx,xx,xx,xx,xx,xx  # pixel values
	 */
	TRACE(("found ReGIS command \"%c\" (load charset)\n", ch));
	state->command = 'l';
	state->load_index = MAX_REGIS_ALPHABETS;
	state->load_w = 8U;
	state->load_h = 10U;
	state->load_alphabet = 1U;	/* FIXME: is this the correct default */
	state->load_name[0] = '\0';
	state->load_glyph = (unsigned) (unsigned char) '\0';
	state->load_row = 0U;
	break;
    case 'P':
    case 'p':
	/* Position

	 * P
	 * (B)  # begin bounded position stack (last point returns to first)
	 * (E)  # end position stack
	 * (P)  # select graphics page for the input and output cursors
	 * (S)  # begin unbounded position stack
	 * (W)  # temporary write options (see write command)
	 * <pv>  # move: 0 == right, 1 == upper right, ..., 7 == lower right
	 * [<position>]  # move to position (X, Y, or both)
	 *
	 * Note the stack does not need to be ended before the next command
	 * Note: maximum depth is 16 levels
	 */
	TRACE(("found ReGIS command \"%c\" (position)\n", ch));
	state->command = 'p';
	break;
    case 'R':
    case 'r':
	/* Report

	 * R
	 * (E)  # parse error
	 * (I<val>)  # set input mode (0 == one-shot, 1 == multiple) (always returns CR)
	 * (L)  # character set
	 * (M(<name>)  # macrograph contents
	 * (M(=)  # macrograph storage
	 * (P)  # output cursor position
	 * (P(I))  # input cursor position (when in one-shot or multiple mode)
	 */
	TRACE(("found ReGIS command \"%c\" (report status)\n", ch));
	state->command = 'r';
	break;
    case 'S':
    case 's':
	/* Screen

	 * S
	 * (A[<upper left>][<lower right>])
	 * (C<setting>  # 0 (cursor output off), 1 (cursor output on)
	 * (E)  # erase to background color, resets shades, curves, and stacks
	 * (F)  # print the graphic and erase the screen (DECprint extension)
	 * (H(P<printer offset>)[<print area cornet>][<print area corner>)
	 * (I<color register>)  # set the background to a specific register
	 * (I(<rgbcode>))  # set the background to the register closest to an "RGB" color
	 * (I(R<r>G<g>B<b>))  # set the background to the register closest to an RGB triplet (RLogin extension)
	 * (I(H<h>L<l>S<s>))  # set the background to the register closest to an HLS triplet
	 * (I(L<l>))  # set the background to the register closest to a grayscale value
	 * (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)
	 * (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)
	 * (M<color index to set>(R<red>G<green>B<blue>)...)  # 0..100, 0..100, 0..100 (sets color and grayscale registers) (RLogin extension)
	 * (M<color index to set>(AR<red>G<green>B<blue>)...)  # 0..100, 0..100, 0..100 (sets color registers only) (RLogin extension)
	 * (M<color index to set>(H<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color and grayscale registers)
	 * (M<color index to set>(AH<hue>L<lightness>S<saturation>)...)  # 0..360, 0..100, 0..100 (sets color registers only)
	 * (M<color index to set>(L<mono level>)...)  # level is 0 ... 100 (sets grayscale registers only)
	 * (P<graphics page number>)  # 0 (default) or 1
	 * (T(<time delay ticks>)  # 60 ticks per second, up to 32767 ticks
	 * (W(M<factor>)  # PV multiplier
	 * <PV scroll offset>  # scroll data so given coordinate is at the upper-left
	 * [scroll offset]  # scroll data so given coordinate is at the upper-left
	 */
	TRACE(("found ReGIS command \"%c\" (screen)\n", ch));
	state->command = 's';
	break;
    case 'T':
    case 't':
	/* Text

	 * T
	 * (A)  # specify which alphabet to select character sets from (0==builtin)
	 * (A0L"<designator>"))  # specify a built-in set for GL via two-char designator
	 * (A0R"<designator>"))  # specify a built-in set for GR via two-char or three-char designator
	 * (A<num>R"<designator>"))  # specify a user-loaded (1-3) set for GR via two-char or three-char designator
	 * (B)  # begin temporary text control
	 * (D<angle>)  # specify a string or character tilt
	 * (E)  # end temporary text control
	 * (H<factor>)  # select a height multiplier (1-256)
	 * (I<angle>)  # italic/oblique: no slant (0), lean back (-1 though -45), lean forward (+1 through +45)
	 * (M[width factor,height factor])  # select size multipliers (width 1-16) (height 1-256)
	 * (S<size id>)  # select one of the 17 standard character sizes
	 * (S[dimensions])  # set a custom display cell size (char with border)
	 * (U[dimensions])  # set a custom unit cell size (char size)
	 * (W<write command>)  # temporary write options (see write command)
	 * [<char offset>]  # optional offset between characters
	 * <PV spacing>  # for subscripts and superscripts
	 * '<text>'  # optional
	 * "<text>"  # optional
	 */
	TRACE(("found ReGIS command \"%c\" (text)\n", ch));
	state->command = 't';
	state->string_rot_set = 0;
	break;
    case 'V':
    case 'v':
	/* Vector

	 * V
	 * (B)  # begin bounded position stack (last point returns to first)
	 * (E)  # end position stack
	 * (S)  # begin unbounded position stack
	 * (W)  # temporary write options (see write command)
	 * <pv>  # draw a line to the pixel vector
	 * []  # draw a dot at the current location
	 * [<position>]  # draw a line to position
	 */
	TRACE(("found ReGIS command \"%c\" (vector)\n", ch));
	state->command = 'v';
	break;
    case 'W':
    case 'w':
	/* Write

	 * W
	 * (C)  # complement writing mode
	 * (E)  # erase writing mode
	 * (F<plane>)  # set the foreground intensity to a specific register
	 * (I<color register>)  # set the foreground to a specific register
	 * (I(<rgbcode>))  # set the foreground to the register closest to an "RGB" color
	 * (I(R<r>G<g>B<b>))  # set the foreground to the register closest to an RGB triplet (RLogin extension)
	 * (I(H<h>L<l>S<s>))  # set the foreground to the register closest to an HLS triplet
	 * (I(L<l>))  # set the foreground to the register closest to a grayscale value
	 * (L<width>)  # set the line width (RLogin extension)
	 * (M<pixel vector multiplier>)  # set the multiplication factor
	 * (N<setting>)  # 0 == negative patterns disabled, 1 == negative patterns enabled
	 * (P<pattern number>)  # 0..9: 0 == none, 1 == solid, 2 == 50% dash, 3 == dash-dot
	 * (P<pattern bits>)  # 2 to 8 bits represented as a 0/1 sequence
	 * (P<(M<pattern multiplier>))
	 * (R)  # replacement writing mode
	 * (S'<character>')  # set shading character
	 * (S<setting>)  # 0 == disable shding, 1 == enable shading
	 * (S[reference point])  # set a horizontal reference line including this point
	 * (S(X)[reference point])  # set a vertical reference line including this point
	 * (V)  # overlay writing mode
	 */
	TRACE(("found ReGIS command \"%c\" (write parameters)\n", ch));
	state->command = 'w';
	break;
    case '@':
	/* Macrograph */
	TRACE(("found ReGIS macrograph command\n"));
	ch = pop_fragment(&state->input);
	TRACE(("inspecting macrograph character \"%c\"\n", ch));
	switch (ch) {
	case '.':
	    TRACE(("clearing all macrographs FIXME\n"));
	    /* FIXME: handle */
	    break;
	case ':':
	    TRACE(("defining macrograph\n"));
	    /* FIXME: what about whitespace before the name? */
	    if (fragment_len(&state->input) < 1) {
		TRACE(("DATA_ERROR: macrograph definition without name, ignoring\n"));
		return 0;
	    } {
		char name;

		name = pop_fragment(&state->input);
		TRACE(("defining macrgraph for \"%c\"\n", name));
		(void) name;	/* This will be used in the future. */
		for (;;) {
		    char next = peek_fragment(&state->input);
		    if (next == ';') {
			/* FIXME: parse, handle  :<name><definition>; */
			pop_fragment(&state->input);
			break;
		    } else if (next == '\0') {
			TRACE(("DATA_ERROR: macrograph definition ends before semicolon\n"));
			break;
		    }
		    pop_fragment(&state->input);
		}
	    }
	    break;
	case ';':
	    TRACE(("DATA_ERROR: found extraneous terminator for macrograph definition\n"));
	    break;
	default:
	    if ((ch > 'A' && ch < 'Z') || (ch > 'a' && ch < 'z')) {
		TRACE(("expanding macrograph \"%c\" FIXME\n", ch));
		/* FIXME: handle */
	    } else {
		TRACE(("DATA_ERROR: unknown macrograph subcommand \"%c\"\n",
		       ch));
	    }
	    /* FIXME: parse, handle */
	    break;
	}
	state->command = '@';
	break;
    default:
	TRACE(("DATA_ERROR: unknown ReGIS command %04x (%c), setting to '_'\n",
	       (int) ch, ch));
	state->command = '_';
	state->option = '_';
	return 0;
    }

    state->option = '_';

    return 1;
}

static int
parse_regis_option(RegisParseState *state, RegisGraphicsContext *context)
{
    RegisDataFragment optionarg;

    if (!extract_regis_option(&state->input, &state->option, &optionarg))
	return 0;
    skip_regis_whitespace(&optionarg);

    TRACE(("found ReGIS option \"%c\": \"%s\"\n",
	   state->option, fragment_to_tempstr(&optionarg)));

    switch (state->command) {
    case 'c':
	TRACE(("inspecting curve option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'A':
	case 'a':
	    TRACE(("found arc length \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment arclen;

		if (!extract_regis_num(&optionarg, &arclen)) {
		    TRACE(("DATA_ERROR: expected int in curve arclen option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("arc length string %s\n", fragment_to_tempstr(&arclen)));
		if (!regis_num_to_int(&arclen, &state->arclen)) {
		    TRACE(("DATA_ERROR: unable to parse int in curve arclen option: \"%s\"\n",
			   fragment_to_tempstr(&arclen)));
		    break;
		}
		TRACE(("value of arc length is %d\n", state->arclen));
		while (state->arclen < -360)
		    state->arclen += 360;
		while (state->arclen > 360)
		    state->arclen -= 360;
		TRACE(("using final arc length %d\n", state->arclen));

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in arc length option \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
	    }
	    break;
	case 'B':
	case 'b':
	    TRACE(("begin closed curve \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: invalid closed curve option \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    state->curve_mode = CURVE_POSITION_CLOSED_CURVE;
	    state->num_points = 0U;
	    state->x_points[state->num_points] =
		context->graphics_output_cursor_x;
	    state->y_points[state->num_points] =
		context->graphics_output_cursor_y;
	    state->num_points++;
	    break;
	case 'C':
	case 'c':
	    TRACE(("found center position mode \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: invalid center position option \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    state->curve_mode = CURVE_POSITION_ARC_CENTER;
	    break;
	case 'E':
	case 'e':
	    TRACE(("found end curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to curve option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }

	    switch (state->curve_mode) {
	    case CURVE_POSITION_CLOSED_CURVE:
		{
		    unsigned i;

#ifdef DEBUG_SPLINE_POINTS
		    printf("points: \n");
		    for (i = 0; i < state->num_points; i++)
			printf("  %d,%d\n",
			       state->x_points[i], state->y_points[i]);
#endif

#ifdef DEBUG_SPLINE_WITH_ROTATION
		    {
			static unsigned shift = 0;
			int temp_x[MAX_CURVE_POINTS], temp_y[MAX_CURVE_POINTS];
			shift++;
			shift = shift % state->num_points;
			for (i = 0; i < state->num_points; i++) {
			    temp_x[i] = state->x_points[i];
			    temp_y[i] = state->y_points[i];
			}
			for (i = 0; i < state->num_points; i++) {
			    state->x_points[i] =
				temp_x[(i + shift) % state->num_points];
			    state->y_points[i] =
				temp_y[(i + shift) % state->num_points];
			}

#ifdef DEBUG_SPLINE_POINTS
			printf("after shift %d: \n", shift);
			for (i = 0; i < state->num_points; i++)
			    printf("  %d,%d\n",
				   state->x_points[i], state->y_points[i]);
#endif
		    }
#endif

		    for (i = state->num_points; i > 0; i--) {
			state->x_points[i] = state->x_points[i - 1];
			state->y_points[i] = state->y_points[i - 1];
		    }
		    state->x_points[0] = state->x_points[state->num_points];
		    state->y_points[0] = state->y_points[state->num_points];
		    state->num_points++;
		    for (i = state->num_points; i != 0; i--) {
			state->x_points[i] = state->x_points[i - 1];
			state->y_points[i] = state->y_points[i - 1];
		    }
		    state->x_points[0] = state->x_points[state->num_points - 1];
		    state->y_points[0] = state->y_points[state->num_points - 1];
		    state->num_points++;
		    state->x_points[state->num_points] = state->x_points[2];
		    state->y_points[state->num_points] = state->y_points[2];
		    state->num_points++;
#ifdef DEBUG_SPLINE_WITH_OVERDRAW
		    state->x_points[state->num_points] = state->x_points[3];
		    state->y_points[state->num_points] = state->y_points[3];
		    state->num_points++;
		    state->x_points[state->num_points] = state->x_points[4];
		    state->y_points[state->num_points] = state->y_points[4];
		    state->num_points++;
#endif
#ifdef DEBUG_SPLINE_POINTS
		    printf("after points added: \n");
		    for (i = 0; i < state->num_points; i++)
			printf("  %d,%d\n",
			       state->x_points[i], state->y_points[i]);
#endif
		}

		TRACE(("drawing closed spline\n"));
		TRACE(("output location was: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		global_context = context;	/* FIXME: remove after updating spline code */
		plotCubicSpline((int) state->num_points - 1,
				state->x_points, state->y_points,
				1);
		TRACE(("output location now: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		TRACE(("output location finally: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		state->num_points = 0U;
		break;
	    case CURVE_POSITION_OPEN_CURVE:
#ifdef DEBUG_SPLINE_POINTS
		{
		    unsigned i;

		    printf("points: \n");
		    for (i = 0U; i < state->num_points; i++)
			printf("  %d,%d\n",
			       state->x_points[i], state->y_points[i]);
		}
#endif
		TRACE(("drawing open spline\n"));
		TRACE(("output location was: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		global_context = context;	/* FIXME: remove after updating spline code */
		plotCubicSpline((int) state->num_points - 1,
				state->x_points, state->y_points,
				1);
		TRACE(("output location now: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));

		context->graphics_output_cursor_x =
		    state->x_points[state->num_points - 1];
		context->graphics_output_cursor_y =
		    state->y_points[state->num_points - 1];
		TRACE(("output location finally: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		state->num_points = 0U;
		break;
	    default:
		TRACE(("DATA_ERROR: end curve option unexpected \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    break;
	case 'S':
	case 's':
	    TRACE(("begin open curve \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: invalid open curve option \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    state->curve_mode = CURVE_POSITION_OPEN_CURVE;
	    state->num_points = 0U;
	    state->x_points[state->num_points] =
		context->graphics_output_cursor_x;
	    state->y_points[state->num_points] =
		context->graphics_output_cursor_y;
	    state->num_points++;
	    TRACE(("first point on curve with location %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));
	    break;
	case 'W':
	case 'w':
	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS curve command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 'f':
	TRACE(("ERROR: fill commands should not be handled here\n"));
	break;
    case 'l':
	TRACE(("inspecting load option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'A':
	case 'a':
	    TRACE(("found alphabet specifier option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    for (;;) {
		RegisDataFragment alphabetarg;

		if (extract_regis_num(&optionarg, &alphabetarg)) {
		    int alphabet;

		    TRACE(("alphabet number: %s\n",
			   fragment_to_tempstr(&alphabetarg)));
		    if (!regis_num_to_int(&alphabetarg, &alphabet)) {
			TRACE(("DATA_ERROR: unable to parse int in load alphabet option: \"%s\"\n",
			       fragment_to_tempstr(&alphabetarg)));
			break;
		    }
		    if (alphabet < 0 ||
			(unsigned) alphabet >= MAX_REGIS_ALPHABETS) {
			TRACE(("DATA_ERROR: invalid alphabet: \"%d\"\n",
			       alphabet));
			break;
		    }
#ifndef ENABLE_UPLOAD_ALPHABET_ZERO
		    if (alphabet == 0) {
			TRACE(("DATA_ERROR: alphabet 0 can not be modified\n"));
			break;
		    }
#endif

		    TRACE(("using alphabet: %d\n", alphabet));
		    state->load_alphabet = (unsigned) alphabet;
		} else if (extract_regis_string(&optionarg, state->temp,
						state->templen)) {
		    TRACE(("alphabet name: %s\n",
			   fragment_to_tempstr(&alphabetarg)));
		    if (strlen(state->temp) == 0U ||
			strlen(state->temp) >= REGIS_ALPHABET_NAME_LEN) {
			TRACE(("DATA_ERROR: alphabet names must be between 1 and %u characters long: \"%s\" FIXME\n",
			       REGIS_ALPHABET_NAME_LEN - 1U, state->temp));
			break;
		    }

		    strcpy(state->load_name, state->temp);
		    TRACE(("using name for alphabet %u: %s\n",
			   state->load_alphabet, state->load_name));
		} else if (skip_regis_whitespace(&optionarg)) {
		    ;
		} else if (!fragment_len(&optionarg)) {
		    break;
		} else {
		    TRACE(("DATA_ERROR: expected int or string in load alphabet option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
	    }
	    break;
#ifdef ENABLE_UPLOAD_ALPHABET_FROM_FONT
	case 'F':
	case 'f':
	    TRACE(("found font option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));

	    if (state->load_index == MAX_REGIS_ALPHABETS) {
		state->load_index = find_free_alphabet_index(context,
							     state->load_alphabet,
							     state->load_w,
							     state->load_h);
		TRACE(("current alphabet is %u and size is %ux%u; assigning alphabet index %u\n",
		       state->load_alphabet, state->load_w, state->load_h, state->load_index));
	    }

	    for (;;) {
		RegisDataFragment fontarg;

		if (skip_regis_whitespace(&optionarg))
		    continue;
		if (extract_regis_num(&optionarg, &fontarg)) {
		    int enabled;

		    TRACE(("fontname enabled: %s\n", fragment_to_tempstr(&fontarg)));
		    if (!regis_num_to_int(&fontarg, &enabled)) {
			TRACE(("DATA_ERROR: unable to parse int in load fontname option: \"%s\"\n",
			       fragment_to_tempstr(&fontarg)));
			break;
		    }
		    if (enabled != 0U && enabled != 1U) {
			TRACE(("DATA_ERROR: invalid fontname enable state: \"%d\"\n", enabled));
			break;
		    }

		    TRACE(("fontname enabled: %d\n", enabled));
		    context->alphabets[state->load_index].use_font = enabled;
		    continue;
		}
		if (extract_regis_string(&optionarg, state->temp,
					 state->templen)) {
		    if (strlen(state->temp) == 0U ||
			strlen(state->temp) >= REGIS_FONTNAME_LEN) {
			TRACE(("DATA_ERROR: font names must be between 1 and %u characters long: \"%s\"\n",
			       REGIS_FONTNAME_LEN - 1U, state->temp));
			break;
		    }

		    strcpy(context->alphabets[state->load_index].fontname,
			   state->temp);
		    context->alphabets[state->load_index].use_font = 1;
		    TRACE(("using backing font: %s\n",
			   context->alphabets[state->load_index].fontname));
		}

		if (!fragment_len(&optionarg)) {
		    break;
		} else {
		    TRACE(("DATA_ERROR: unexpected text in load fontname option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
	    }
	    break;
#endif
#ifdef ENABLE_USER_FONT_SIZE
	case 'S':
	case 's':
	    TRACE(("found glyph size option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    while (fragment_len(&optionarg) > 0) {
		RegisDataFragment sizearg;

		if (skip_regis_whitespace(&optionarg))
		    continue;

		if (extract_regis_extent(&optionarg, &sizearg)) {
		    int w, h;
		    unsigned size;

		    TRACE(("glyph size: %s\n", fragment_to_tempstr(&sizearg)));
		    /* FIXME: verify this is in pixels, not user coordinates */
		    if (!load_regis_pixel_extent(fragment_to_tempstr(&sizearg),
						 0, 0,
						 &w, &h)) {
			TRACE(("DATA_ERROR: unable to parse extent in glyph size option: \"%s\"\n",
			       fragment_to_tempstr(&sizearg)));
			break;
		    }
		    if (w < 1 || h < 1) {
			TRACE(("DATA_ERROR: glyph dimensions must not be negative or zero: %dx%d\n",
			       w, h));
			break;
		    }
		    size = GLYPH_WIDTH_BYTES((unsigned) w) * (unsigned) h;
		    if (size > MAX_REGIS_ALPHABET_BYTES) {
			TRACE(("DATA_ERROR: glyph is too large (%u bytes, limit is %u bytes)\n",
			       size, MAX_REGIS_ALPHABET_BYTES));
			break;
		    }

		    if (state->load_index != MAX_REGIS_ALPHABETS) {
			TRACE(("DATA_ERROR: glyph size can not be changed after any data is loaded\n"));
			break;
		    }

		    TRACE(("using glyph size: %dx%d\n", w, h));
		    state->load_w = (unsigned) w;
		    state->load_h = (unsigned) h;
		    continue;
		}

		TRACE(("DATA_ERROR: expected extent in glyph size option: \"%s\"\n",
		       fragment_to_tempstr(&sizearg)));
		break;
	    }
	    break;
#endif
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS load command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 'p':
	TRACE(("inspecting position option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'B':
	case 'b':
	    TRACE(("found begin bounded position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to position option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next >= POSITION_STACK_SIZE) {
		/* FIXME: ignore, error, update counter? */
		TRACE(("unable to push position to full stack\n"));
		break;
	    }
	    TRACE(("pushing location: %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));

	    state->stack_x[state->stack_next] =
		context->graphics_output_cursor_x;
	    state->stack_y[state->stack_next] =
		context->graphics_output_cursor_y;
	    state->stack_next++;
	    break;
	case 'E':
	case 'e':
	    TRACE(("found end position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to position option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next == 0U) {
		TRACE(("DATA_ERROR: unable to pop position from empty stack\n"));
		break;
	    }

	    state->stack_next--;
	    if (state->stack_x[state->stack_next] != DUMMY_STACK_X ||
		state->stack_y[state->stack_next] != DUMMY_STACK_Y) {
		context->graphics_output_cursor_x =
		    state->stack_x[state->stack_next];
		context->graphics_output_cursor_y =
		    state->stack_y[state->stack_next];
		TRACE(("popped location: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
	    } else {
		TRACE(("not popping location\n"));
	    }
	    break;
	case 'P':
	case 'p':
	    TRACE(("found graphics page destination option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment pagearg;
		int page;

		if (!extract_regis_num(&optionarg, &pagearg)) {
		    TRACE(("DATA_ERROR: expected int in page destination option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("page option arg: %s\n", fragment_to_tempstr(&pagearg)));
		if (!regis_num_to_int(&pagearg, &page)) {
		    TRACE(("DATA_ERROR: unable to parse int in page destination option: \"%s\"\n",
			   fragment_to_tempstr(&pagearg)));
		    break;
		}
		if (page < 0 || (unsigned) page >= MAX_REGIS_PAGES) {
		    TRACE(("DATA_ERROR: invalid page: \"%d\"\n", page));
		    break;
		}

		TRACE(("using destination page number: %d\n", page));
		context->destination_page = (unsigned) page;
		map_regis_graphics_pages(context->destination_graphic->xw,
					 context);
	    }
	    break;
	case 'S':
	case 's':
	    TRACE(("found begin unbounded position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to end position option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next >= POSITION_STACK_SIZE) {
		/* FIXME: ignore, error, update counter? */
		TRACE(("unable to push dummy position to full stack\n"));
		break;
	    }

	    TRACE(("pushing dummy positions instead of %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));
	    state->stack_x[state->stack_next] = DUMMY_STACK_X;
	    state->stack_y[state->stack_next] = DUMMY_STACK_Y;
	    state->stack_next++;
	    break;
	case 'W':
	case 'w':
	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS position command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 'r':
	TRACE(("inspecting report option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'E':
	case 'e':
	    TRACE(("found parse error report \"%s\" FIXME\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
		break;
	    } {
		char reply[64];

		TRACE(("got report last error condition\n"));
		/* FIXME: verify no CSI */
		/* FIXME: implement after adding error tracking */
		sprintf(reply, "\"%u, %u\"\r", 0U, 0U);
		unparseputs(context->display_graphic->xw, reply);
		unparse_end(context->display_graphic->xw);
	    }
	    break;
	case 'I':
	case 'i':
	    TRACE(("found set input mode \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment modearg;
		int mode;

		if (!extract_regis_num(&optionarg, &modearg)) {
		    TRACE(("DATA_ERROR: expected int in report input mode option: \"%s\"\n",
			   fragment_to_tempstr(&modearg)));
		    break;
		}

		TRACE(("input mode: %s\n", fragment_to_tempstr(&modearg)));
		if (!regis_num_to_int(&modearg, &mode)) {
		    TRACE(("DATA_ERROR: unable to parse int in report input mode option: \"%s\"\n",
			   fragment_to_tempstr(&modearg)));
		    break;
		}
		if (mode != 0 && mode != 1) {
		    TRACE(("DATA_ERROR: ignoring invalid input mode: \"%d\"\n",
			   mode));
		    break;
		}

		TRACE(("using input mode: %d (%s)\n", mode,
		       (mode == 0)
		       ? "one-shot"
		       : "multiple"));
		context->multi_input_mode = mode;
		if (context->multi_input_mode) {
		    TRACE(("ERROR: multi-mode input not implemented FIXME\n"));
		    /* FIXME: enable input cursor, send location on mouse clicks or non-arrowkey keypresses */
		} else {
		    /* FIXME: if in multi-mode, disable input cursor, stop tracking mouse clicks and keypresses */
		    /* FIXME: enable input cursor and disable drawing until location report request command is received */
		    /* FIXME: upon mouse click or keypress respond with location report */
		}
		/* FIXME: implement input cursor */
		/* FIXME: implement mouse tracking */
		/* FIXME: implement arrow key movement */
		/* FIXME: implement button/key collection */

		unparseputs(context->display_graphic->xw, "\r");
		unparse_end(context->display_graphic->xw);

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg) > 0U) {
		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
			   state->option, fragment_to_tempstr(&optionarg)));
		}
	    }
	    break;
	case 'L':
	case 'l':
	    TRACE(("found character set load report \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
		break;
	    }
	    if (state->load_index == MAX_REGIS_ALPHABETS) {
		TRACE(("DATA_ERROR: unable to report alphabet name because no alphabet is loading\n"));
		/* FIXME: how should errors be handled? */
		unparseputs(context->display_graphic->xw, "\033A''\r");
		unparse_end(context->display_graphic->xw);
		break;
	    }

	    /* FIXME: also send CSI here? */
	    unparseputs(context->display_graphic->xw, "\033A'");
	    unparseputs(context->display_graphic->xw,
			context->alphabets[state->load_index].name);
	    unparseputs(context->display_graphic->xw, "'\r");
	    unparse_end(context->display_graphic->xw);
	    break;
	case 'M':
	case 'm':
	    TRACE(("found macrograph report \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment suboptionarg;
		char name = '\0';

		if (extract_regis_parenthesized_data(&optionarg,
						     &suboptionarg)) {
		    skip_regis_whitespace(&suboptionarg);
		    TRACE(("got macrograph report character: \"%s\"\n",
			   fragment_to_tempstr(&suboptionarg)));
		    if (fragment_len(&suboptionarg) > 0U) {
			name = pop_fragment(&suboptionarg);

			skip_regis_whitespace(&suboptionarg);
			if (fragment_len(&optionarg) > 0U) {
			    TRACE(("DATA_ERROR: unexpected content in ReGIS macrograph report suboptions: \"%s\"\n",
				   fragment_to_tempstr(&suboptionarg)));
			    break;
			}
		    }
		}
		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg) > 0U) {
		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
			   state->option, fragment_to_tempstr(&optionarg)));
		    break;
		}

		if (name == '\0') {
		    TRACE(("DATA_ERROR: no macro name given to ReGIS macrograph report command\n"));
		    break;
		}

		if (name == '=') {
		    char reply[64];

		    TRACE(("got report macrograph storage\n"));
		    /* FIXME: verify no CSI */
		    /* FIXME: implement when macrographs are supported */
		    sprintf(reply, "\"%u, %u\"\r", 1000U, 1000U);
		    unparseputs(context->display_graphic->xw, reply);
		    unparse_end(context->display_graphic->xw);
		} else {
		    TRACE(("got report macrograph name '%c'\n", name));
		    /*
		     * FIXME: Implement when macrographs are supported (and
		     * allow it to be disabled for security reasons).
		     */
		    /* FIXME: also send CSI here? */
		    unparseputs(context->display_graphic->xw, "@;\r");
		    unparse_end(context->display_graphic->xw);
		}
	    }
	    break;
	case 'P':
	case 'p':
	    TRACE(("found cursor position report \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment suboptionarg;
		int output = 1;

		if (extract_regis_parenthesized_data(&optionarg,
						     &suboptionarg)) {
		    skip_regis_whitespace(&suboptionarg);
		    TRACE(("got cursor position report suboption: \"%s\"\n",
			   fragment_to_tempstr(&suboptionarg)));
		    if (fragment_len(&suboptionarg) > 0U) {
			char suboption;

			suboption = pop_fragment(&suboptionarg);
			if (suboption == 'i' || suboption == 'I') {
			    output = 0;		/* input location report */
			} else {
			    TRACE(("DATA_ERROR: unknown ReGIS postion report suboption '%c'\n",
				   suboption));
			    break;
			}

			skip_regis_whitespace(&suboptionarg);
			if (fragment_len(&optionarg) > 0U) {
			    TRACE(("DATA_ERROR: unexpected content in ReGIS postion report suboptions: \"%s\"\n",
				   fragment_to_tempstr(&suboptionarg)));
			    break;
			}
		    }
		}
		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg) > 0U) {
		    TRACE(("DATA_ERROR: unexpected arguments to ReGIS report command option '%c' arg \"%s\"\n",
			   state->option, fragment_to_tempstr(&optionarg)));
		    break;
		}

		TRACE(("got report cursor position (output=%d)\n", output));

		/* FIXME: look into supporting ANSI locator reports (DECLRP) */
		if (output == 1) {
		    char reply[64];

		    /* FIXME: verify no leading char or button sequence */
		    /* FIXME: should we ever send an eight-bit CSI? */
		    /* FIXME: verify in absolute, not user, coordinates */
		    sprintf(reply, "\033[[%d,%d]\r",
			    context->graphics_output_cursor_x,
			    context->graphics_output_cursor_y);
		    unparseputs(context->display_graphic->xw, reply);
		    unparse_end(context->display_graphic->xw);
		} else {
		    char reply[64];
		    int x, y;

		    if (context->multi_input_mode) {
			/* FIXME: track input coordinates */
			x = y = 0;	/* placeholders */

			/* send CSI240~[x,y]\r with current input cursor location */

			/* FIXME: verify no leading char or button sequence */
			/* FIXME: should we ever send an eight-bit CSI? */
			/* FIXME: verify in absolute, not user, coordinates */
			TRACE(("sending multi-mode input report at %d,%d\n",
			       x, y));
			sprintf(reply, "\033[[%d,%d]\r", x, y);
			unparseputs(context->display_graphic->xw, reply);
			unparse_end(context->display_graphic->xw);
			break;
		    } else {
			char ch;

			/* FIXME: wait for first non-arrow keypress or mouse click, and don't update graphics while waiting */
			ch = ' ';	/* placeholder */
			x = y = 0;	/* placeholders */

			/* send <key or button>[x,y]\r to report input cursor location */

			/* null button: CSI240~ */
			/* left button: CSI241~ */
			/* middle button: CSI243~ */
			/* right button: CSI245~ */
			/* extra button: CSI247~ */
			/* FIXME: support DECLBD to change button assignments */
			/* FIXME: verify no leading char or button sequence */
			/* FIXME: should we ever send an eight-bit CSI? */
			TRACE(("sending one-shot input report with %c at %d,%d\n",
			       ch, x, y));
			sprintf(reply, "\033[%c[%d,%d]\r", ch, x, y);
			unparseputs(context->display_graphic->xw, reply);
			unparse_end(context->display_graphic->xw);
			break;
		    }
		}
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS report command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 's':
	TRACE(("inspecting screen option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'A':
	case 'a':
	    TRACE(("found address definition \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment address_extent;
		int got_ul = 0;
		int got_lr = 0;
		int ulx = 0, uly = 0, lrx = 0, lry = 0;

		while (fragment_len(&optionarg)) {
		    if (skip_regis_whitespace(&optionarg))
			continue;

		    if (extract_regis_extent(&optionarg, &address_extent)) {
			int x, y;

			/* FIXME: are relative values supposed to be handled? */
			if (!load_regis_pixel_extent(fragment_to_tempstr(&address_extent),
						     0, 0, &x, &y)) {
			    TRACE(("DATA_ERROR: unable to parse extent in address definition: \"%s\"\n",
				   fragment_to_tempstr(&address_extent)));
			    break;
			}

			if (!got_ul) {
			    ulx = x;
			    uly = y;
			    got_ul = 1;
			} else if (!got_lr) {
			    lrx = x;
			    lry = y;
			    got_lr = 1;
			} else {
			    TRACE(("DATA_ERROR: ignoring extra extent argument in address definition: \"%s\"\n",
				   fragment_to_tempstr(&address_extent)));
			}
			continue;
		    }

		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: expected extent argument but found: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    return 1;
		}

		if (!got_ul || !got_lr) {
		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: one or both locations missing in definition\n"));
		    return 1;
		}
		if (ulx == lrx || uly == lry) {
		    TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition: one or both dimensions are zero: ul=%d,%d lr=%d,%d\n",
			   ulx, uly, lrx, lry));
		    return 1;
		} {
		    const int cw = abs(ulx - lrx) + 1;
		    const int ch = abs(uly - lry) + 1;
		    int width, height;

		    /*
		     * FIXME: Should we attempt to resize existing contents?
		     * We are actually changing the output size, but terminals
		     * just changed coordinates.
		     */
#if 1
		    int scale;
		    const int mw = context->destination_graphic->max_width;
		    const int mh = context->destination_graphic->max_height;

		    TRACE(("custom screen size pre scaling: %dx%d\n", cw, ch));

		    width = cw;
		    height = ch;

		    scale = 1;
		    while (width * scale < 200 ||
			   height * scale < 200) {
			scale++;
		    }
		    width *= scale;
		    height *= scale;

		    scale = 1;
		    while (width / scale > mw ||
			   height / scale > mh) {
			scale++;
		    }
		    width /= scale;
		    height /= scale;
#else
		    width = context->width;
		    height = context->height;
#endif

		    TRACE(("custom screen address: ul=%d,%d lr=%d,%d\n",
			   ulx, uly, lrx, lry));

		    context->x_off = ulx;
		    context->y_off = uly;
		    context->x_div = lrx - ulx;
		    context->y_div = lry - uly;
		    context->width = width;
		    context->height = height;
		    context->destination_graphic->actual_width = width;
		    context->destination_graphic->actual_height = height;
		    context->destination_graphic->dirty = 1;

		    TRACE(("conversion factors: off=%+d,%+d div=%+d,%+d width=%d, height=%d\n",
			   context->x_off, context->y_off,
			   context->x_div, context->y_div,
			   context->width, context->height));
		}
	    }
	    break;
	case 'C':
	case 'c':
	    TRACE(("found cursor control \"%s\" FIXME\n",
		   fragment_to_tempstr(&optionarg)));
	    /* FIXME: handle */
	    /* C0 == output cursor off, C1 == output cursor on */
	    /* C(H0) == output cursor diamond (default), C(H1) == output cursor diamond, C(H2) == output cursor crosshair */
	    /* 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 */
	    /* 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) */
	    if (!fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen cursor control option value \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    break;
	case 'E':
	case 'e':
	    TRACE(("found erase request \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: ignoring unexpected argument to ReGIS erase request \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    DRAW_ALL(context, context->background);
	    context->destination_graphic->dirty = 1;
	    context->force_refresh = 1;
	    break;
	case 'F':
	case 'f':
	    TRACE(("found page eject request \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: ignoring unexpected argument to ReGIS page eject request \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    /* We aren't going to print anything so no need to deduplicate. */
	    DRAW_ALL(context, context->background);
	    context->destination_graphic->dirty = 1;
	    context->force_refresh = 1;
	    break;
	case 'H':
	case 'h':
	    TRACE(("found hardcopy control \"%s\" FIXME\n",
		   fragment_to_tempstr(&optionarg)));
	    /* FIXME: handle */
	    /* screen S(H), area to (input?/output?) cursor S(H[X,Y]), or area S(H[X,Y][X,Y]) */
	    if (!fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen hardcopy control option value \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    break;
	case 'I':
	case 'i':
	    TRACE(("found screen background color index \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_regnum_or_colorspec(context, &optionarg,
						&context->background)) {
		TRACE(("DATA_ERROR: screen background color specifier not recognized: \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    break;
	case 'M':
	case 'm':
	    TRACE(("found screen color register mapping \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment regnum;
		RegisDataFragment colorspec;

		while (fragment_len(&optionarg)) {
		    if (skip_regis_whitespace(&optionarg))
			continue;

		    if (extract_regis_num(&optionarg, &regnum)) {
			int register_num;
			int color_only;
			short r, g, b;

			if (!regis_num_to_int(&regnum, &register_num)) {
			    TRACE(("DATA_ERROR: unable to parse int in screen color register mapping option: \"%s\"\n",
				   fragment_to_tempstr(&regnum)));
			    return 1;
			}
			if (register_num < 0 ||
			    register_num > (int) context->destination_graphic->valid_registers) {
			    TRACE(("interpreting out of range register number %d as 0 FIXME\n",
				   register_num));
			    register_num = 0;
			}
			skip_regis_whitespace(&optionarg);
			if (!extract_regis_parenthesized_data(&optionarg,
							      &colorspec)) {
			    TRACE(("DATA_ERROR: expected to find parentheses after register number: \"%s\"\n",
				   fragment_to_tempstr(&optionarg)));
			    return 1;
			}

			skip_regis_whitespace(&colorspec);
			switch (peek_fragment(&colorspec)) {
			case 'A':
			case 'a':
			    pop_fragment(&colorspec);
			    color_only = 1;
			    break;
			default:
			    color_only = 0;
			    break;
			}

			TRACE(("mapping register %d to color spec: \"%s\"\n",
			       register_num, fragment_to_tempstr(&colorspec)));
			if (!load_regis_colorspec(context, &colorspec,
						  &r, &g, &b)) {
			    TRACE(("DATA_ERROR: unable to use colorspec for mapping of register %d\n",
				   register_num));
			    return 1;
			}

			if (color_only &&
			    (context->terminal_id == 240 ||
			     context->terminal_id == 330)) {
			    TRACE(("NOT setting color register %d to %hd,%hd,%hd\n",
				   register_num, r, g, b));
			} else {
			    TRACE(("setting color register %d to %hd,%hd,%hd\n",
				   register_num, r, g, b));
			    update_color_register(context->destination_graphic,
						  (RegisterNum) register_num,
						  r, g, b);
			}
			continue;
		    } {
			char skip;

			skip_regis_whitespace(&optionarg);
			skip = pop_fragment(&optionarg);
			(void) skip;	/* variable needed only if tracing */
			TRACE(("DATA_ERROR: ignoring mapping request with unexpected character \"%c\"\n",
			       skip));
			return 1;
		    }
		}
	    }
	    break;
	case 'P':
	case 'p':
	    TRACE(("found graphics page display option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment pagearg;
		int page;

		if (!extract_regis_num(&optionarg, &pagearg)) {
		    TRACE(("DATA_ERROR: expected int in page display option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("page option arg: %s\n", fragment_to_tempstr(&pagearg)));
		if (!regis_num_to_int(&pagearg, &page)) {
		    TRACE(("DATA_ERROR: unable to parse int in page display option: \"%s\"\n",
			   fragment_to_tempstr(&pagearg)));
		    break;
		}
		if (page < 0 || (unsigned) page >= MAX_REGIS_PAGES) {
		    TRACE(("DATA_ERROR: invalid page: \"%d\"\n", page));
		    break;
		}

		TRACE(("using display page number: %d\n", page));
		context->display_page = (unsigned) page;
		map_regis_graphics_pages(context->display_graphic->xw, context);
	    }
	    break;
	case 'T':
	case 't':
	    TRACE(("found time delay \"%s\" FIXME\n",
		   fragment_to_tempstr(&optionarg)));
	    /* FIXME: handle (maybe -- might not be worth it, plus it is an easy DoS vector) */
	    if (!fragment_len(&optionarg)) {
		TRACE(("DATA_ERROR: ignoring malformed ReGIS screen time delay option value \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		return 1;
	    }
	    break;
	case 'W':
	case 'w':
	    /* Only M (pixel vector multiplier) is useful -- for the scrolling multiplier. */
	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS screen command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 't':
	TRACE(("inspecting text option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	if (!fragment_len(&optionarg)) {
	    TRACE(("DATA_ERROR: ignoring malformed ReGIS text command option value \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    return 1;
	}
	switch (state->option) {
	case 'A':
	case 'a':
	    TRACE(("found alphabet specifier option \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment alphabetarg;
		int alphabet;

		if (!extract_regis_num(&optionarg, &alphabetarg)) {
		    TRACE(("DATA_ERROR: expected int in text alphabet option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("alphabet: %s\n", fragment_to_tempstr(&alphabetarg)));
		if (!regis_num_to_int(&alphabetarg, &alphabet)) {
		    TRACE(("DATA_ERROR: unable to parse int in text alphabet option: \"%s\"\n",
			   fragment_to_tempstr(&alphabetarg)));
		    break;
		}
		if (alphabet < 0 ||
		    (unsigned) alphabet >= MAX_REGIS_ALPHABETS) {
		    TRACE(("DATA_ERROR: invalid alphabet: \"%d\"\n", alphabet));
		    break;
		}

		TRACE(("using alphabet number: %d\n", alphabet));
		context->current_text_controls->alphabet_num = (unsigned) alphabet;

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text alphabet option \"%s\"\n",
			   fragment_to_tempstr(&alphabetarg)));
		    break;
		}
	    }
	    break;
	case 'B':
	case 'b':
	    TRACE(("found beginning of temporary text control \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    copy_regis_text_controls(&context->persistent_text_controls,
				     &context->temporary_text_controls);
	    context->current_text_controls = &context->temporary_text_controls;
	    break;
	case 'D':
	case 'd':
	    TRACE(("found text tilt control \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment rotationarg;
		int rotation;

		if (!extract_regis_num(&optionarg, &rotationarg)) {
		    TRACE(("DATA_ERROR: expected int in text tilt option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("tilt: %s\n", fragment_to_tempstr(&rotationarg)));
		if (!regis_num_to_int(&rotationarg, &rotation)) {
		    TRACE(("DATA_ERROR: unable to parse int in text tilt option: \"%s\"\n",
			   fragment_to_tempstr(&rotationarg)));
		    break;
		}
		while (rotation < 0) {
		    rotation += 360;
		}
		while (rotation >= 360) {
		    rotation -= 360;
		}
		/* FIXME: we don't have to be this restrictive, though the
		 * VT3x0 apparently was. What might depend on this?
		 */
#ifndef ENABLE_FREE_ROTATION
		/* Use closest value which is a multiple of 45 degrees. */
		rotation = 45 * ((rotation + 22) / 45);
#endif

		/* For some reason ReGIS reused the "D" option for the text
		 * command to represent two different attributes.  Character
		 * tilt can only be modified if a string tilt option has
		 * already been given.
		 */
		/* FIXME: handle character size prameter */
		if (state->string_rot_set) {
		    TRACE(("using character rotation (tilt): %d\n", rotation));
		    context->current_text_controls->character_rotation =
			rotation;
		} else {
		    TRACE(("using string rotation (tilt): %d\n", rotation));
		    context->current_text_controls->string_rotation =
			rotation;
		    context->current_text_controls->character_rotation =
			rotation;
		    state->string_rot_set = 1;
		}

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text tilt option \"%s\"\n",
			   fragment_to_tempstr(&rotationarg)));
		    break;
		}
	    }
	    break;
	case 'E':
	case 'e':
	    TRACE(("found end of temporary text control \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    context->current_text_controls = &context->persistent_text_controls;
	    break;
	case 'H':
	case 'h':
	    TRACE(("found height multiplier \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment multiarg;
		int multiplier;
		unsigned height;

		if (!extract_regis_num(&optionarg, &multiarg)) {
		    TRACE(("DATA_ERROR: expected int in text height multiplier option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("multiplier: %s\n", fragment_to_tempstr(&multiarg)));
		if (!regis_num_to_int(&multiarg, &multiplier)) {
		    TRACE(("DATA_ERROR: unable to parse int in text height multiplier option: \"%s\"\n",
			   fragment_to_tempstr(&multiarg)));
		    break;
		}
		if (multiplier < 0) {
		    TRACE(("DATA_ERROR: out of range height multiplier: \"%d\", using 0 FIXME\n",
			   multiplier));
		    multiplier = 0;	/* FIXME: verify zero is accepted */
		}
		if (multiplier > 256) {
		    TRACE(("DATA_ERROR: out of range height multiplier: \"%d\", using 256 FIXME\n",
			   multiplier));
		    multiplier = 256;
		}
		TRACE(("using height multiplier: %d\n", multiplier));
		height = (unsigned) multiplier *10U;	/* base character height */
		context->current_text_controls->character_display_h = height;
		context->current_text_controls->character_unit_cell_h = height;

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text multiplier option \"%s\"\n",
			   fragment_to_tempstr(&multiarg)));
		    break;
		}
	    }
	    break;
	case 'I':
	case 'i':
	    TRACE(("found italic control \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment italicarg;
		int italic;

		if (!extract_regis_num(&optionarg, &italicarg)) {
		    TRACE(("DATA_ERROR: expected int in text italic option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("italic angle: %s\n", fragment_to_tempstr(&italicarg)));
		if (!regis_num_to_int(&italicarg, &italic)) {
		    TRACE(("DATA_ERROR: unable to parse int in text italic option: \"%s\"\n",
			   fragment_to_tempstr(&italicarg)));
		    break;
		}

		/*
		 * This is overly-restrictive but matches what the docs say
		 * should happen.  Add an option to allow exact angles?
		 */
#ifndef ENABLE_VARIABLE_ITALICS
		if (italic <= -31) {
		    italic = -45;
		} else if (italic < 0) {
		    italic = -27;	/* docs say 22, but that gives .404 x:y ratio */
		} else if (italic >= 31) {
		    italic = 45;
		} else if (italic > 0) {
		    italic = 27;	/* docs say 22, but that gives .404 x:y ratio */
		}
#else
		if (italic <= -72) {
		    italic = -72;
		} else if (italic >= 72) {
		    italic = 72;
		}
#endif

		TRACE(("using italic angle: %d\n", italic));
		context->current_text_controls->slant = italic;

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text italic option \"%s\"\n",
			   fragment_to_tempstr(&italicarg)));
		    break;
		}
	    }
	    break;
	case 'M':
	case 'm':
	    TRACE(("found text command size multiplier \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment sizemultiplierarg;
		int sizemultiplier;
		int ww, hh;

		if (!extract_regis_extent(&optionarg, &sizemultiplierarg)) {
		    TRACE(("DATA_ERROR: expected extent in size multiplier option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("size multiplier: %s\n",
		       fragment_to_tempstr(&sizemultiplierarg)));
		/* FIXME: verify this is in pixels, not user coordinates */
		if (!load_regis_pixel_extent(fragment_to_tempstr(&sizemultiplierarg),
					     0, 0, &ww, &hh)) {
		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
			   state->option, fragment_to_tempstr(&sizemultiplierarg)));
		    break;
		}
		if (!regis_num_to_int(&sizemultiplierarg, &sizemultiplier)) {
		    TRACE(("DATA_ERROR: unable to parse extent in size multiplier option: \"%s\"\n",
			   fragment_to_tempstr(&sizemultiplierarg)));
		    break;
		}
		if (ww < 1 || hh < 1) {
		    TRACE(("DATA_ERROR: invalid size multiplier: %d,%d FIXME\n",
			   ww, hh));
		    break;
		}
		if (ww > 16) {
		    ww = 16;
		}
		if (hh > 16) {
		    hh = 16;
		}

		TRACE(("using size multiplier: %d,%d\n", ww, hh));

		/* times the S1 character unit cell dimensions */
		context->current_text_controls->character_unit_cell_w =
		    (unsigned) ww *8U;
		context->current_text_controls->character_unit_cell_h =
		    (unsigned) hh *20U;

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text unit cell size option \"%s\"\n",
			   fragment_to_tempstr(&sizemultiplierarg)));
		    break;
		}
	    }
	    break;
	case 'S':
	case 's':
	    TRACE(("found display size or standard character cell size \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    for (;;) {
		RegisDataFragment displaysizearg;

		skip_regis_whitespace(&optionarg);
		if (extract_regis_extent(&optionarg, &displaysizearg)) {
		    int disp_w, disp_h;

		    TRACE(("custom display size: %s\n",
			   fragment_to_tempstr(&displaysizearg)));
		    /* FIXME: verify this is in pixels, not user coordinates */
		    if (!load_regis_pixel_extent(fragment_to_tempstr(&displaysizearg),
						 0, 0, &disp_w, &disp_h)) {
			TRACE(("DATA_ERROR: unable to parse extent in text display size option: \"%s\"\n",
			       fragment_to_tempstr(&displaysizearg)));
			break;
		    }
		    if (disp_w < 1 || disp_h < 1) {
			TRACE(("DATA_ERROR: invalid text display size: %dx%d FIXME\n",
			       disp_w, disp_h));
			break;
		    }

		    TRACE(("using display cell size: %d,%d\n", disp_w, disp_h));
		    context->current_text_controls->character_display_w =
			(unsigned) disp_w;
		    context->current_text_controls->character_display_h =
			(unsigned) disp_h;
		    TRACE(("using offset: %d,%d\n", disp_w, 0));
		    context->current_text_controls->character_inc_x = disp_w;
		    context->current_text_controls->character_inc_y = 0;

		    continue;
		}

		if (extract_regis_num(&optionarg, &displaysizearg)) {
		    int standard;
		    unsigned disp_w, disp_h, unit_w, unit_h;
		    int off_x, off_y;

		    TRACE(("standard display cell size: %s\n",
			   fragment_to_tempstr(&displaysizearg)));
		    if (!regis_num_to_int(&displaysizearg, &standard)) {
			TRACE(("DATA_ERROR: unable to parse int in text standard cell size option: \"%s\"\n",
			       fragment_to_tempstr(&displaysizearg)));
			break;
		    }
		    if (get_standard_character_size(standard, &disp_w, &disp_h,
						    &unit_w, &unit_h,
						    &off_x, &off_y)) {
			TRACE(("DATA_ERROR: unrecognized standard cell size: \"%d\"\n",
			       standard));
			break;
		    }

		    TRACE(("using display cell size: %u,%u\n", disp_w, disp_h));
		    context->current_text_controls->character_display_w = disp_w;
		    context->current_text_controls->character_display_h = disp_h;
		    TRACE(("using offset: %d,%d\n", off_x, off_y));
		    context->current_text_controls->character_inc_x = off_x;
		    context->current_text_controls->character_inc_y = off_y;

		    /*
		     * Some ReGIS documentation implies that the "S" option only
		     * affects character spacing after a rotation option ("ReGIS
		     * uses the spacing value associated with the cell size to
		     * space the characters in the tilted string").  The 7-13
		     * example in the VT330/VT340 Programmer Reference Manual vol 2
		     * appears to say otherwise.  FIXME: verify
		     */
		    if (1 || !state->string_rot_set) {	/* forced for now */
			TRACE(("using unit cell size: %u,%u\n", unit_w, unit_h));
			context->current_text_controls->character_unit_cell_w =
			    unit_w;
			context->current_text_controls->character_unit_cell_h =
			    unit_h;
		    }

		    continue;
		}

		if (skip_regis_whitespace(&optionarg)) {
		    continue;
		}

		if (!fragment_len(&optionarg)) {
		    break;
		}

		TRACE(("DATA_ERROR: expected int or extent in text display size option: \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    break;
	case 'U':
	case 'u':
	    TRACE(("found text command custom unit cell size \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    {
		RegisDataFragment unitsizearg;
		int unitsize;
		int unit_w, unit_h;

		if (!extract_regis_extent(&optionarg, &unitsizearg)) {
		    TRACE(("DATA_ERROR: expected extent in text unit cell size option: \"%s\"\n",
			   fragment_to_tempstr(&optionarg)));
		    break;
		}
		TRACE(("unitsize cell size: %s\n",
		       fragment_to_tempstr(&unitsizearg)));
		/* FIXME: verify this is in pixels, not user coordinates */
		if (!load_regis_pixel_extent(fragment_to_tempstr(&unitsizearg),
					     0, 0,
					     &unit_w, &unit_h)) {
		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
			   state->option, fragment_to_tempstr(&unitsizearg)));
		    break;
		}
		if (!regis_num_to_int(&unitsizearg, &unitsize)) {
		    TRACE(("DATA_ERROR: unable to parse extent in text unit cell size option: \"%s\"\n",
			   fragment_to_tempstr(&unitsizearg)));
		    break;
		}
		if (unit_w < 1 || unit_h < 1) {
		    TRACE(("DATA_ERROR: invalid text unit cell size: %dx%d FIXME\n",
			   unit_w, unit_h));
		    break;
		}

		TRACE(("using unit cell size: %d,%d\n", unit_w, unit_h));

		context->current_text_controls->character_unit_cell_w =
		    (unsigned) unit_w;
		context->current_text_controls->character_unit_cell_h =
		    (unsigned) unit_h;

		skip_regis_whitespace(&optionarg);
		if (fragment_len(&optionarg)) {
		    TRACE(("DATA_ERROR: ignoring trailing junk in text unit cell size option \"%s\"\n",
			   fragment_to_tempstr(&unitsizearg)));
		    break;
		}
	    }
	    break;
	case 'W':
	case 'w':
	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
		break;
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS text command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 'v':
	TRACE(("inspecting vector option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	switch (state->option) {
	case 'B':
	case 'b':
	    TRACE(("found begin bounded position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next >= POSITION_STACK_SIZE) {
		/* FIXME: ignore, error, update counter? */
		TRACE(("unable to push position to full stack\n"));
		break;
	    }
	    TRACE(("pushing location: %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));

	    state->stack_x[state->stack_next] =
		context->graphics_output_cursor_x;
	    state->stack_y[state->stack_next] =
		context->graphics_output_cursor_y;
	    state->stack_next++;
	    break;
	case 'E':
	case 'e':
	    TRACE(("found end vector position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next == 0U) {
		TRACE(("DATA_ERROR: unable to pop position from empty stack\n"));
		break;
	    }

	    state->stack_next--;
	    if (state->stack_x[state->stack_next] != DUMMY_STACK_X ||
		state->stack_y[state->stack_next] != DUMMY_STACK_Y) {
		int orig_x, orig_y;

		orig_x = context->graphics_output_cursor_x;
		orig_y = context->graphics_output_cursor_y;
		context->graphics_output_cursor_x =
		    state->stack_x[state->stack_next];
		context->graphics_output_cursor_y =
		    state->stack_y[state->stack_next];
		TRACE(("popped location: %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));

		TRACE(("drawing line to popped location\n"));
		draw_patterned_line(context,
				    orig_x, orig_y,
				    context->graphics_output_cursor_x,
				    context->graphics_output_cursor_y);
	    } else {
		TRACE(("not popping location\n"));
	    }
	    break;
	case 'S':
	case 's':
	    TRACE(("found begin unbounded position stack \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    skip_regis_whitespace(&optionarg);
	    if (fragment_len(&optionarg) > 0U) {
		TRACE(("DATA_ERROR: ignoring unexpected arguments to vector option '%c' arg \"%s\"\n",
		       state->option, fragment_to_tempstr(&optionarg)));
	    }
	    if (state->stack_next >= POSITION_STACK_SIZE) {
		/* FIXME: ignore, error, update counter? */
		TRACE(("unable to push dummy position to full stack\n"));
		break;
	    }

	    TRACE(("pushing dummy vector positions instead of %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));
	    state->stack_x[state->stack_next] = DUMMY_STACK_X;
	    state->stack_y[state->stack_next] = DUMMY_STACK_Y;
	    state->stack_next++;
	    break;
	case 'W':
	case 'w':
	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: ignoring unknown ReGIS vector command option '%c' arg \"%s\"\n",
		   state->option, fragment_to_tempstr(&optionarg)));
	    break;
	}
	break;
    case 'w':
	skip_regis_whitespace(&optionarg);
	TRACE(("inspecting permanent write option \"%c\" with value \"%s\"\n",
	       state->option, fragment_to_tempstr(&optionarg)));
	if (!load_regis_write_control(state, context,
				      context->graphics_output_cursor_x,
				      context->graphics_output_cursor_y,
				      state->option, &optionarg,
				      &context->persistent_write_controls)) {
	    TRACE(("DATA_ERROR: invalid write options\n"));
	    return 1;
	}
	break;
    default:
	TRACE(("DATA_ERROR: unexpected option in \"%c\" command: \"%s\"\n",
	       state->command, fragment_to_tempstr(&optionarg)));
	return 1;
    }

    return 1;
}

static int
parse_regis_items(RegisParseState *state, RegisGraphicsContext *context)
{
    RegisDataFragment *const input = &state->input;
    RegisDataFragment item;

    if (input->pos >= input->len)
	return 0;

    if (extract_regis_extent(input, &item)) {
	TRACE(("found extent \"%s\"\n", fragment_to_tempstr(&item)));
	switch (state->command) {
	case 'c':
	    {
		int orig_x, orig_y;
		int new_x, new_y;

		if (state->num_points > 0) {
		    orig_x = state->x_points[state->num_points - 1];
		    orig_y = state->y_points[state->num_points - 1];
		} else {
		    orig_x = context->graphics_output_cursor_x;
		    orig_y = context->graphics_output_cursor_y;
		}
		if (!load_regis_coord_extent(context,
					     fragment_to_tempstr(&item),
					     orig_x, orig_y,
					     &new_x, &new_y)) {
		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
			   state->command, fragment_to_tempstr(&item)));
		    break;
		}

		switch (state->curve_mode) {
		case CURVE_POSITION_ARC_CENTER:
		case CURVE_POSITION_ARC_EDGE:
		    {
			double radians;
			int tenthdegs;
			int c_x, c_y;
			int e_x, e_y;
			int e_x_final = 0, e_y_final = 0;

			TRACE(("drawing arc: curve_mode=%d\n", state->curve_mode));
			TRACE(("drawing arc: new=%d,%d orig=%d,%d\n",
			       new_x, new_y, orig_x, orig_y));
			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
			    c_x = new_x;
			    c_y = new_y;
			    e_x = orig_x;
			    e_y = orig_y;
			} else {
			    c_x = orig_x;
			    c_y = orig_y;
			    e_x = new_x;
			    e_y = new_y;
			}

			radians = atan2((double) (c_y - e_y),
					(double) (e_x - c_x));
			tenthdegs = (int) (0.5 + 3600.0 * radians / (2.0 * M_PI));
			if (tenthdegs < 0)
			    tenthdegs += 3600;

			TRACE(("drawing arc centered at location %d,%d to location %d,%d from %g degrees (%g radians) for %d degrees\n",
			       c_x, c_y,
			       e_x, e_y,
			       tenthdegs / 10., radians, state->arclen));
			draw_patterned_arc(context,
					   c_x, c_y,
					   e_x, e_y,
					   tenthdegs, state->arclen * 10,
					   &e_x_final, &e_y_final);

#ifdef DEBUG_ARC_CENTER
			DRAW_PIXEL(context, c_x + 1, c_y, 3U);
			DRAW_PIXEL(context, c_x - 1, c_y, 3U);
			DRAW_PIXEL(context, c_x, c_y + 1, 3U);
			DRAW_PIXEL(context, c_x, c_y - 1, 3U);
			DRAW_PIXEL(context, c_x, c_y, 3U);
#endif

#ifdef DEBUG_ARC_START
			DRAW_PIXEL(context, e_x + 1, e_y, 2U);
			DRAW_PIXEL(context, e_x - 1, e_y, 2U);
			DRAW_PIXEL(context, e_x, e_y + 1, 2U);
			DRAW_PIXEL(context, e_x, e_y - 1, 2U);
			DRAW_PIXEL(context, e_x, e_y, 2U);
#endif

#ifdef DEBUG_ARC_END
			DRAW_PIXEL(context, e_x_final + 1, e_y_final + 1, 1U);
			DRAW_PIXEL(context, e_x_final + 1, e_y_final - 1, 1U);
			DRAW_PIXEL(context, e_x_final - 1, e_y_final + 1, 1U);
			DRAW_PIXEL(context, e_x_final - 1, e_y_final - 1, 1U);
			DRAW_PIXEL(context, e_x_final, e_y_final, 1U);
#endif

			if (state->curve_mode == CURVE_POSITION_ARC_CENTER) {
			    TRACE(("moving cursor to final point on arc %d,%d\n",
				   e_x_final, e_y_final));
			    if (state->num_points > 0) {
				state->x_points[state->num_points - 1] =
				    e_x_final;
				state->y_points[state->num_points - 1] =
				    e_y_final;
			    }
			    context->graphics_output_cursor_x = e_x_final;
			    context->graphics_output_cursor_y = e_y_final;
			}
		    }
		    break;
		case CURVE_POSITION_OPEN_CURVE:
		case CURVE_POSITION_CLOSED_CURVE:
		    if (state->num_points >= MAX_INPUT_CURVE_POINTS) {
			TRACE(("DATA_ERROR: got curve point, but already have max points (%d)\n",
			       state->num_points));
			break;
		    }
		    state->x_points[state->num_points] = new_x;
		    state->y_points[state->num_points] = new_y;
		    state->num_points++;
		    TRACE(("adding point to curve with location %d,%d\n",
			   new_x, new_y));
		    break;
		default:
		    TRACE(("ERROR: got position, but curve mode %d is unknown\n",
			   state->curve_mode));
		    break;
		}
	    }
	    break;
	case 'p':
	    if (!load_regis_coord_extent(context,
					 fragment_to_tempstr(&item),
					 context->graphics_output_cursor_x,
					 context->graphics_output_cursor_y,
					 &context->graphics_output_cursor_x,
					 &context->graphics_output_cursor_y)) {
		TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
		       state->command, fragment_to_tempstr(&item)));
		break;
	    }
	    TRACE(("moving pen to location %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));
	    break;
	case 's':
	    TRACE(("extent scroll argument to screen command: \"%s\"\n",
		   fragment_to_tempstr(&item)));
	    {
		int old_ul_x, old_ul_y;
		int new_ul_x, new_ul_y;
		int copy_w, copy_h;

		old_ul_x = 0;
		old_ul_y = 0;
		TRACE(("current upper-left coordinate in pixel coordinates: %d,%d\n",
		       old_ul_x, old_ul_y));
		/* FIXME: verify this is in user coordinates, not pixels */
		if (!load_regis_coord_extent(context,
					     fragment_to_tempstr(&item),
					     old_ul_x, old_ul_y,
					     &new_ul_x, &new_ul_y)) {
		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
			   state->command, fragment_to_tempstr(&item)));
		    break;
		}
		TRACE(("scrolling image to updated upper-left coordinate in pixel coordinates: %d,%d\n",
		       new_ul_x, new_ul_y));

		/* FIXME: does any write mode affect revealed background? */
		if (new_ul_x > 0)
		    copy_w = context->width - new_ul_x;
		else
		    copy_w = context->width;
		if (new_ul_y > 0)
		    copy_h = context->height - new_ul_y;
		else
		    copy_h = context->height;
		/* FIXME: verify this applies to write page, not display page */
		copy_overlapping_area(context->destination_graphic,
				      new_ul_x, new_ul_y,
				      0, 0,
				      (unsigned) copy_w, (unsigned) copy_h,
				      context->background);
		context->destination_graphic->dirty = 1;
		context->force_refresh = 1;
	    }
	    break;
	case 't':
	    /* FIXME: verify this is in pixels, not user coordinates */
	    if (!load_regis_pixel_extent(fragment_to_tempstr(&item),
					 0, 0,
					 &context->current_text_controls->character_inc_x,
					 &context->current_text_controls->character_inc_y)) {
		TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
		       state->command, fragment_to_tempstr(&item)));
		break;
	    }
	    TRACE(("setting character spacing to %d,%d\n",
		   context->current_text_controls->character_inc_x,
		   context->current_text_controls->character_inc_y));
	    break;
	case 'v':
	    {
		int orig_x, orig_y;

		orig_x = context->graphics_output_cursor_x;
		orig_y = context->graphics_output_cursor_y;
		if (!load_regis_coord_extent(context,
					     fragment_to_tempstr(&item),
					     orig_x, orig_y,
					     &context->graphics_output_cursor_x,
					     &context->graphics_output_cursor_y)) {
		    TRACE(("DATA_ERROR: unable to parse extent in '%c' command: \"%s\"\n",
			   state->command, fragment_to_tempstr(&item)));
		    break;
		}
		TRACE(("drawing line to location %d,%d\n",
		       context->graphics_output_cursor_x,
		       context->graphics_output_cursor_y));
		draw_patterned_line(context,
				    orig_x, orig_y,
				    context->graphics_output_cursor_x,
				    context->graphics_output_cursor_y);
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: unexpected extent in \"%c\" command: \"%s\"\n",
		   state->command, fragment_to_tempstr(&item)));
	    break;
	}
	return 1;
    }

    if (state->command != 'l' && extract_regis_pixelvector(input, &item)) {
	TRACE(("found pixel vector \"%s\"\n", fragment_to_tempstr(&item)));
	switch (state->command) {
	case 'c':
	    /* FIXME: parse, handle */
	    TRACE(("pixelvector in curve command FIXME\n"));
	    break;
	    /* FIXME: not sure if 'f' supports PVs */
	case 'p':
	    /* FIXME: error checking */
	    if (!load_regis_coord_pixelvector(context,
					      fragment_to_tempstr(&item),
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &context->graphics_output_cursor_x,
					      &context->graphics_output_cursor_y)) {
		TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
		       state->command, fragment_to_tempstr(&item)));
		break;
	    }
	    TRACE(("moving pen to location %d,%d\n",
		   context->graphics_output_cursor_x,
		   context->graphics_output_cursor_y));
	    break;
	case 's':
	    TRACE(("pixelvector scroll argument to screen command: \"%s\"\n",
		   fragment_to_tempstr(&item)));
	    {
		int old_ul_x, old_ul_y;
		int new_ul_x, new_ul_y;
		int copy_w, copy_h;

		old_ul_x = 0;
		old_ul_y = 0;
		TRACE(("current upper-left coordinate in pixel coordinates: %d,%d\n",
		       old_ul_x, old_ul_y));
		/* FIXME: verify this is in user coordinates, not pixels */
		/* FIXME: verify that multiple PV digits result in a single
		 * copy (this changes the contents of any exposed edge)
		 */
		if (!load_regis_coord_pixelvector(context,
						  fragment_to_tempstr(&item),
						  old_ul_x, old_ul_y,
						  &new_ul_x, &new_ul_y)) {
		    TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
			   state->command, fragment_to_tempstr(&item)));
		    break;
		}
		TRACE(("scrolling image to updated upper-left coordinate in pixel coordinates: %d,%d\n",
		       new_ul_x, new_ul_y));

		/* FIXME: does any write mode affect revealed background? */
		if (new_ul_x > 0)
		    copy_w = context->width - new_ul_x;
		else
		    copy_w = context->width;
		if (new_ul_y > 0)
		    copy_h = context->height - new_ul_y;
		else
		    copy_h = context->height;
		/* FIXME: verify this applies to write page, not display page */
		copy_overlapping_area(context->destination_graphic,
				      new_ul_x, new_ul_y,
				      0, 0,
				      (unsigned) copy_w, (unsigned) copy_h,
				      context->background);
		context->destination_graphic->dirty = 1;
		context->force_refresh = 1;
	    }
	    break;
	case 't':
	    {
		int dx, dy;

		/* FIXME: verify this does not use user coordinates */
		if (!load_regis_pixel_pixelvector(fragment_to_tempstr(&item),
						  1, 0, 0, &dx, &dy)) {
		    TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
			   state->command, fragment_to_tempstr(&item)));
		    break;
		}

		dx *= (int) (
				context->current_text_controls->character_display_w
				>> 1U);
		dy *= (int) (
				context->current_text_controls->character_display_h
				>> 1U);
		TRACE(("adding character offset %d,%d\n", dx, dy));
		context->graphics_output_cursor_x += dx;
		context->graphics_output_cursor_y += dy;
	    }
	    break;
	case 'v':
	    {
		char const *const pixelvector = fragment_to_tempstr(&item);
		unsigned offset;

		for (offset = 0U; pixelvector[offset] != '\0';) {
		    int orig_x = context->graphics_output_cursor_x;
		    int orig_y = context->graphics_output_cursor_y;
		    if (!load_regis_coord_pixelvector_step(context, pixelvector,
							   &offset,
							   orig_x, orig_y,
							   &context->graphics_output_cursor_x,
							   &context->graphics_output_cursor_y)) {
			TRACE(("DATA_ERROR: unable to parse pixel vector in '%c' command: \"%s\"\n",
			       state->command, fragment_to_tempstr(&item)));
			break;
		    }
		    TRACE(("drawing line to location %d,%d\n",
			   context->graphics_output_cursor_x,
			   context->graphics_output_cursor_y));
		    draw_patterned_line(context, orig_x, orig_y,
					context->graphics_output_cursor_x,
					context->graphics_output_cursor_y);
		}
	    }
	    break;
	default:
	    TRACE(("DATA_ERROR: unexpected pixel vector in \"%c\" command: \"%s\"\n",
		   state->command, fragment_to_tempstr(&item)));
	    break;
	}
	return 1;
    }

    if (extract_regis_string(input, state->temp, state->templen)) {
	switch (state->command) {
	case 'l':
	    /* FIXME: confirm that extra characters are ignored */
	    TRACE(("found character to load: \"%s\"\n", state->temp));
	    state->load_glyph = (unsigned) (unsigned char) state->temp[0];
	    state->load_row = 0U;
	    break;
	case 't':
	    TRACE(("found string to draw: \"%s\"\n", state->temp));
	    draw_text(context, state->temp);
	    break;
	default:
	    TRACE(("DATA_ERROR: unexpected string argument to \"%c\" command: \"%s\"\n",
		   state->command, state->temp));
	    break;
	}
	return 1;
    }

    /* hex values */
    if (state->command == 'l') {
	unsigned digit;

	for (digit = 0U; digit < (state->load_w + 3U) >> 2U; digit++) {
	    char ch = peek_fragment(input);

	    if (!IS_HEX_DIGIT(ch)) {
		if (ch != ',' && ch != ';' &&
		    ch != ' ' && ch != '\r' &&
		    ch != '\n') {
		    TRACE(("found end of hexadecimal string witch '%c' on digit %u\n",
			   ch, digit));
		    /* FIXME: need to unput the digits up to this point */
		    /*
		     * Report success since we ate some characters,
		     * and the new char needs to be compared with commands
		     * and other top-level things.
		     */
		    if (digit != 0U)
			return 1;
		    return 0;
		}
		pop_fragment(input);
		break;
	    }

	    state->temp[digit] = ch;
	    pop_fragment(input);
	}
	state->temp[digit] = '\0';

	if (strlen(state->temp) > 0) {
	    unsigned long val;
	    unsigned glyph_size;

	    val = strtoul(state->temp, NULL, 16);
	    TRACE(("found row %u for glyph %u: \"%s\" value %02lx (%lu)\n",
		   state->load_row, state->load_glyph, state->temp, val, val));

	    if (state->load_row >= state->load_h) {
		TRACE(("DATA_ERROR: ignoring extra row for glyph %u\n",
		       state->load_glyph));
		return 0;
	    }

	    if (state->load_index == MAX_REGIS_ALPHABETS) {
		state->load_index = find_free_alphabet_index(context,
							     state->load_alphabet,
							     state->load_w,
							     state->load_h);
		TRACE(("current alphabet is %u and size is %ux%u; assigning alphabet index %u\n",
		       state->load_alphabet, state->load_w, state->load_h, state->load_index));
	    }

	    glyph_size = GLYPH_WIDTH_BYTES(
					      context->alphabets[state->load_index].pixw) *
		context->alphabets[state->load_index].pixh;
	    if (context->alphabets[state->load_index].bytes == NULL) {
		if (!(context->alphabets[state->load_index].bytes =
		      calloc(MAX_GLYPHS * glyph_size, sizeof(unsigned char)))) {
		    TRACE(("ERROR: unable to allocate %u bytes for glyph storage\n",
			   MAX_GLYPHS * glyph_size));
		    return 0;
		}
	    } {
		unsigned char *glyph;
		unsigned bytew;
		unsigned byte;
		unsigned unused_bits;

		glyph = &context->alphabets[state->load_index]
		    .bytes[state->load_glyph * glyph_size];
		bytew = GLYPH_WIDTH_BYTES(context->alphabets[state->load_index]
					  .pixw);
		unused_bits = 8U - (context->alphabets[state->load_index].pixw
				    & 3U);
		if (unused_bits == 8U) {
		    unused_bits = 0U;
		}
		for (byte = 0U; byte < bytew; byte++) {
		    glyph[state->load_row * bytew + byte] =
			(unsigned char) (((val << unused_bits) >>
					  ((bytew - (byte + 1U)) << 3U)) & 255U);
#ifdef DEBUG_LOAD
		    TRACE(("bytew=%u val=%lx byte=%u output=%x\n", bytew, val,
			   byte,
			   (unsigned) glyph[state->load_row * bytew + byte]));
#endif
		}

		state->load_row++;
		context->alphabets[state->load_index]
		    .loaded[state->load_glyph] = 1;
#ifdef DEBUG_LOAD
		TRACE(("marking alphabet %u at index %u glyph %u as loaded\n",
		       state->load_alphabet, state->load_index,
		       state->load_glyph));
#endif
		return 1;
	    }
	}
    }

    return 0;
}

static int
parse_regis_toplevel(RegisParseState *state, RegisGraphicsContext *context)
{
    RegisDataFragment parenthesized;
    char ch;
#if 0
    TRACE(("reference line: shading=%d ref=%u loc=%d\n",
	   context->temporary_write_controls.shading_enabled,
	   context->temporary_write_controls.shading_reference_dim,
	   context->temporary_write_controls.shading_reference));
#endif

#ifdef DEBUG_PARSING
    TRACE(("parsing top level: char %d of %d (next char '%c')\n",
	   state->input.pos,
	   state->input.len,
	   peek_fragment(&state->input)));
#endif
    if (skip_regis_whitespace(&state->input))
	return 0;
    /* FIXME: the semicolon terminates the current command even if inside of an optionset or extent */
    if (peek_fragment(&state->input) == ';') {
	pop_fragment(&state->input);
	TRACE(("ending '%c' command\n", state->command));
	state->command = '_';
	state->option = '_';
	return 1;
    }
    /* Load statements contain hex values which may look like commands. */
    ch = peek_fragment(&state->input);
    if (state->command != 'l' || !IS_HEX_DIGIT(ch)) {
#ifdef DEBUG_PARSING
	TRACE(("checking for top level command...\n"));
#endif
	if (parse_regis_command(state)) {
	    /* FIXME: verify that these are the things reset on a new command */
	    TRACE(("resetting temporary write controls and pattern state\n"));
	    copy_regis_write_controls(&context->persistent_write_controls,
				      &context->temporary_write_controls);
	    context->pattern_count = 0U;
	    context->pattern_bit = 1U;

	    /* FIXME: what happens if temporary text controls aren't closed? */
	    /* FIXME: what if temporary text controls are nested? */
	    context->current_text_controls = &context->persistent_text_controls;
	    return 1;
	}
    }
#ifdef DEBUG_PARSING
    TRACE(("checking for top level parentheses...\n"));
#endif
    if (extract_regis_parenthesized_data(&state->input, &parenthesized)) {
	RegisDataFragment orig_input;

	if (state->command == 'f') {	/* Fill */
	    TRACE(("found commands in fill mode \"%s\"\n",
		   fragment_to_tempstr(&parenthesized)));
	    orig_input = state->input;
	    state->input = parenthesized;
	    state->command = '_';
	    state->option = '_';
	    context->fill_mode = 1;
	    context->fill_point_count = 0U;
	    while (state->input.pos < state->input.len)
		parse_regis_toplevel(state, context);
	    draw_filled_polygon(context);
	    context->fill_point_count = 0U;
	    context->fill_mode = 0;
	    state->command = 'f';
	    state->input = orig_input;
	    return 1;
	} else {
	    orig_input = state->input;
	    state->input = parenthesized;
	    state->option = '_';
	    TRACE(("parsing at optionset level: %d of %d\n",
		   state->input.pos,
		   state->input.len));
	    for (;;) {
		if (state->input.pos >= state->input.len)
		    break;
		TRACE(("looking at optionset character: \"%c\"\n",
		       peek_fragment(&state->input)));
		if (skip_regis_whitespace(&state->input))
		    continue;
		if (parse_regis_option(state, context))
		    continue;
		if (parse_regis_items(state, context))
		    continue;
		if (state->input.pos >= state->input.len)
		    break;
		{
		    char skip;

		    skip = pop_fragment(&state->input);
		    (void) skip;	/* variable needed only if tracing */
		    TRACE(("DATA_ERROR: skipping unexpected character in optionset: \"%c\"\n",
			   skip));
		}
		/* FIXME: suboptions */
	    }
	    state->option = '_';
	    state->input = orig_input;
	    return 1;
	}
    }
    if (state->command == 'f') {	/* Fill */
	RegisDataFragment optionarg;
	if (extract_regis_option(&state->input, &state->option, &optionarg)) {
	    skip_regis_whitespace(&optionarg);

	    TRACE(("found temporary write options \"%s\"\n",
		   fragment_to_tempstr(&optionarg)));
	    if (!load_regis_write_control_set(state, context,
					      context->graphics_output_cursor_x,
					      context->graphics_output_cursor_y,
					      &optionarg,
					      &context->temporary_write_controls)) {
		TRACE(("DATA_ERROR: invalid temporary write options \"%s\"\n",
		       fragment_to_tempstr(&optionarg)));
	    }
	    return 1;
	}
	TRACE(("checking for top level items (though none should be present)...\n"));
	if (parse_regis_items(state, context))
	    return 1;
    } else {
#ifdef DEBUG_PARSING
	TRACE(("checking for top level items...\n"));
#endif
	if (parse_regis_items(state, context))
	    return 1;
    }
    if (state->input.pos >= state->input.len)
	return 0;

    {
	char skip;

	skip = pop_fragment(&state->input);
	(void) skip;		/* variable needed only if tracing */
	TRACE(("DATA_ERROR: skipping unexpected character at top level: \"%c\"\n", ch));
    }
    return 0;
}

static void
init_regis_parse_state(RegisParseState *state)
{
    state->command = '_';
    state->option = '_';
    state->stack_next = 0U;
    state->load_index = MAX_REGIS_ALPHABETS;
}

void
parse_regis(XtermWidget xw, ANSI *params, char const *string)
{
    TScreen *screen = TScreenOf(xw);
    RegisGraphicsContext *const context = &persistent_context;
    RegisParseState *const state = &persistent_state;
    struct timeval prev_tv;
    struct timeval curr_tv;
    unsigned iterations;
    int Pmode;

    if (params->a_nparam > 0)
	Pmode = params->a_param[0];
    else
	Pmode = 0;

    TRACE(("ReGIS vector graphics mode, param_count=%d mode=%d\n",
	   params->a_nparam, Pmode));

    init_fragment(&state->input, string);
    state->templen = (unsigned) strlen(string) + 1U;
    if (!(state->temp = malloc((size_t) state->templen))) {
	TRACE(("Unable to allocate temporary buffer of size %u\n",
	       state->templen));
	return;
    }

    /* Update the screen scrolling and do a refresh.
     * The refresh may not cover the whole graphic.
     */
    if (screen->scroll_amt)
	FlushScroll(xw);

    /* Only reset on the first ReGIS image unless it is being requested. */
    if (context->width == 0 || context->height == 0 ||
	Pmode == 1 || Pmode == 3) {
	init_regis_parse_state(state);
	init_regis_graphics_context(screen->terminal_id,
				    screen->graphics_regis_def_wide,
				    screen->graphics_regis_def_high,
				    get_color_register_count(screen),
				    screen->graphics_regis_default_font,
				    context);
    }

    map_regis_graphics_pages(xw, context);

    X_GETTIMEOFDAY(&prev_tv);
    iterations = 0U;
    refresh_modified_displayed_graphics(xw);

    for (;;) {
	if (skip_regis_whitespace(&state->input))
	    continue;
	if (parse_regis_toplevel(state, context)) {
	    int need_refresh = 0;

	    /* FIXME: Move refresh logic out of the top level so that long
	     * sequences of filled drawing commands can be refreshed before the
	     * end of the fill command.
	     */
	    iterations++;
	    if (context->force_refresh) {
		X_GETTIMEOFDAY(&curr_tv);
		need_refresh = 1;
	    } else if (iterations > MIN_ITERATIONS_BEFORE_REFRESH) {
		X_GETTIMEOFDAY(&curr_tv);
		if ((Time) curr_tv.tv_sec > (Time) prev_tv.tv_sec + 1UL) {
		    need_refresh = 1;
		} else {
#define DiffTime(TV) (TV.tv_sec * 1000L + TV.tv_usec / 1000L)
		    long diff = (long) (DiffTime(curr_tv) - DiffTime(prev_tv));
		    if (diff > MIN_MS_BEFORE_REFRESH)
			need_refresh = 1;
		}
	    }

	    if (need_refresh) {
		TRACE(("refreshing after %u iterations and %ldms\n",
		       iterations,
		       DiffTime(curr_tv) - DiffTime(prev_tv)));
		context->force_refresh = 0;
		/* FIXME: pre-ANSI compilers need memcpy() */
		prev_tv = curr_tv;
		iterations = 0U;
		refresh_modified_displayed_graphics(xw);
#if OPT_DOUBLE_BUFFER
		{
		    XdbeSwapInfo swap;

		    swap.swap_window = VWindow(screen);
		    swap.swap_action = XdbeCopied;
		    XdbeSwapBuffers(XtDisplay(term), &swap, 1);
		    XFlush(XtDisplay(xw));
		}
#endif
	    }

	    continue;
	}

	if (state->input.pos >= state->input.len)
	    break;
    }

    free(state->temp);

    refresh_modified_displayed_graphics(xw);
    TRACE(("DONE! Successfully parsed ReGIS data.\n"));
}
