summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthieu Herrb <matthieu@cvs.openbsd.org>2014-05-09 19:57:30 +0000
committerMatthieu Herrb <matthieu@cvs.openbsd.org>2014-05-09 19:57:30 +0000
commitb440e7f7b5256ba872a43dc2e766e4216e40f2c5 (patch)
treef01a6cda22de0df4c4e784c18c339185be765fb7
parent0abff89ae011fd09403930fb14063b72ba40603a (diff)
Missing new files in update to xterm 304
-rw-r--r--app/xterm/graphics_regis.c3482
-rw-r--r--app/xterm/graphics_regis.h48
-rw-r--r--app/xterm/graphics_sixel.c640
-rw-r--r--app/xterm/graphics_sixel.h48
4 files changed, 4218 insertions, 0 deletions
diff --git a/app/xterm/graphics_regis.c b/app/xterm/graphics_regis.c
new file mode 100644
index 000000000..be4c10308
--- /dev/null
+++ b/app/xterm/graphics_regis.c
@@ -0,0 +1,3482 @@
+/* $XTermId: graphics_regis.c,v 1.23 2014/05/03 12:44:53 tom Exp $ */
+
+/*
+ * Copyright 2014 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>
+
+#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
+
+#undef DEBUG_BEZIER
+#undef DEBUG_SPLINE_SEGMENTS
+#undef DEBUG_SPLINE_POINTS
+#undef DEBUG_SPLINE_WITH_ROTATION
+#undef DEBUG_SPLINE_WITH_OVERDRAW
+
+#define ITERATIONS_BEFORE_REFRESH 100U
+/* *INDENT-OFF* */
+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;
+} RegisWriteControls;
+
+typedef struct RegisDataFragment {
+ char const *start;
+ unsigned pos;
+ unsigned len;
+} RegisDataFragment;
+
+typedef enum RegisParseLevel {
+ INPUT,
+ OPTIONSET
+} RegisParseLevel;
+/* *INDENT-ON* */
+
+#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)
+
+typedef struct RegisParseState {
+ RegisDataFragment input;
+ RegisDataFragment optionset;
+ char *temp;
+ unsigned templen;
+ RegisParseLevel level;
+ char command;
+ char option;
+ /* curve options */
+ int curve_mode;
+ int arclen;
+ int x_points[MAX_CURVE_POINTS];
+ int y_points[MAX_CURVE_POINTS];
+ unsigned num_points;
+} RegisParseState;
+
+typedef struct RegisGraphicsContext {
+ Graphic *graphic;
+ int terminal_id;
+ unsigned all_planes;
+ RegisterNum background;
+ RegisWriteControls persistent_write_controls;
+ RegisWriteControls temporary_write_controls;
+ int graphics_output_cursor_x;
+ int graphics_output_cursor_y;
+ unsigned pattern_count;
+ unsigned pattern_bit;
+} RegisGraphicsContext;
+
+#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 ROT_LEFT_N(V, N) ( (((V) << ((N) & 3U )) & 255U) | ((V) >> (8U - ((N) & 3U))) )
+#define ROT_LEFT(V) ( (((V) << 1U) & 255U) | ((V) >> 7U) )
+
+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 color = 0;
+
+ switch (context->temporary_write_controls.write_style) {
+ case WRITE_STYLE_OVERLAY:
+ /* update pixels with foreground when pattern is 1, unchanged when pattern is 0 */
+ if (!(context->temporary_write_controls.pattern & context->pattern_bit)) {
+ 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, 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 = ((context->temporary_write_controls.pattern &
+ context->pattern_bit)
+ ? fg
+ : bg);
+ }
+ break;
+
+ case WRITE_STYLE_COMPLEMENT:
+ /* update pixels with background when pattern is 1, unchanged when pattern is 0 */
+ color = read_pixel(context->graphic, x, y);
+ if (color == COLOR_HOLE)
+ color = context->background;
+ color = color ^ context->all_planes;
+ break;
+
+ case WRITE_STYLE_ERASE:
+ /* update pixels with foreground */
+ if (context->temporary_write_controls.invert_pattern) {
+ color = context->temporary_write_controls.foreground;
+ } else {
+ color = context->background;
+ }
+ break;
+ }
+
+ draw_solid_pixel(context->graphic, x, y, color);
+}
+
+static void
+fill_to_pixel(RegisGraphicsContext *context, int x, int y)
+{
+ unsigned dim = context->temporary_write_controls.shading_reference_dim;
+ int ref = context->temporary_write_controls.shading_reference;
+
+ 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) {
+ draw_regis_pixel(context, curr_x, y);
+ }
+ } else {
+ 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);
+ draw_regis_pixel(context, x, curr_y);
+ }
+ }
+}
+
+static void
+draw_patterned_pixel(RegisGraphicsContext *context, int x, int y)
+{
+ if (context->temporary_write_controls.shading_enabled) {
+ if (context->temporary_write_controls.shading_character != '\0') {
+ /* FIXME: handle character fills */
+ TRACE(("pixel shaded with character\n"));
+ } else {
+ fill_to_pixel(context, x, y);
+ }
+ } else {
+ 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);
+ }
+}
+
+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_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_patterned_pixel(context, x, y);
+ }
+ }
+}
+
+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)
+{
+ 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 points_start, points_stop;
+ int points;
+ int iterations;
+ int quad;
+ long rx, ry;
+ long dx, dy;
+ long e2;
+ long error;
+
+ if (a_length == 0)
+ return;
+ if (a_length > 0) {
+ quadmap = pos_quadmap;
+ } else {
+ quadmap = neg_quadmap;
+ if (a_start != 0)
+ a_start = 360 - 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);
+ points_start = (total_points * a_start) / 360;
+ points_stop = (total_points * a_start +
+ total_points * abs(a_length) + 359) / 360;
+ TRACE(("drawing arc with %d points from %d angle for %d degrees (from point %d to %d)\n",
+ total_points, a_start, a_length, points_start, points_stop));
+
+ points = 0;
+ for (iterations = 0; iterations < 8; iterations++) {
+ quad = iterations & 0x3;
+
+ rx = -ra;
+ ry = 0;
+ e2 = rb;
+ dx = (2 * rx + 1) * e2 * e2;
+ dy = rx * rx;
+ error = dx + dy;
+ do {
+ if (points >= points_start && points <= points_stop) {
+ draw_patterned_pixel(context,
+ (int) (cx +
+ quadmap[quad].dxx * rx +
+ quadmap[quad].dxy * ry),
+ (int) (cy +
+ quadmap[quad].dyx * rx +
+ quadmap[quad].dyy * ry));
+ }
+ 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);
+ }
+}
+
+/*
+ * 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_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, e2; /* error value e_xy */
+
+ for (;;) { /* loop */
+ 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);
+ long xy;
+ double dx, dy, err;
+ 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 */
+ 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, fx1, fx2, fx3, fy0 = y0, fy1, fy2, fy3;
+ 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 */
+ 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, 360);
+ 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, 360);
+ }
+ global_context->temporary_write_controls.foreground = 10;
+ draw_patterned_arc(global_context, x[i], y[n], x[i] + 2, y[i], 0, 360);
+ 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
+ int color = 0;
+#endif
+
+ assert(n > 2); /* need at least 4 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, 360);
+ 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, 360);
+ }
+ global_context->temporary_write_controls.foreground = 10;
+ draw_patterned_arc(global_context, x[i], y[i], x[i] + 2, y[i], 0, 360);
+ 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
+ plotCubicBezier(x4, y4,
+ ifloor((2 * x1 + x2) / 3 + 0.5),
+ ifloor((2 * y1 + y2) / 3 + 0.5),
+ ifloor((x1 + 2 * x2) / 3 + 0.5),
+ ifloor((y1 + 2 * y2) / 3 + 0.5),
+ 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,
+ ifloor((2 * x1 + x2) / 3 + 0.5),
+ ifloor((2 * y1 + y2) / 3 + 0.5),
+ ifloor((x1 + 2 * x2) / 3 + 0.5),
+ ifloor((y1 + 2 * y2) / 3 + 0.5),
+ 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 void
+init_fragment(RegisDataFragment *fragment, char const *str)
+{
+ fragment->start = str;
+ fragment->len = (unsigned) strlen(str);
+ fragment->pos = 0U;
+}
+
+static void
+copy_fragment(RegisDataFragment *dst, RegisDataFragment const *src)
+{
+ dst->start = src->start;
+ dst->len = src->len;
+ dst->pos = src->pos;
+}
+
+static char
+peek_fragment(RegisDataFragment const *fragment)
+{
+ if (fragment->pos < fragment->len) {
+ return fragment->start[fragment->pos];
+ }
+ return '\0';
+}
+
+static char
+pop_fragment(RegisDataFragment *fragment)
+{
+ if (fragment->pos < fragment->len) {
+ return fragment->start[fragment->pos++];
+ }
+ return '\0';
+}
+
+static size_t
+fragment_len(RegisDataFragment const *fragment)
+{
+ return fragment->len - fragment->pos;
+}
+
+#define MAX_FRAG 1024
+static char const *
+fragment_to_tempstr(RegisDataFragment const *fragment)
+{
+ static char tempstr[MAX_FRAG + 1];
+ size_t remaininglen = fragment_len(fragment);
+ size_t minlen = ((remaininglen < MAX_FRAG)
+ ? remaininglen
+ : MAX_FRAG);
+
+ (void) strncpy(tempstr, &fragment->start[fragment->pos], minlen);
+ tempstr[minlen] = '\0';
+ return tempstr;
+}
+
+static int
+skip_regis_whitespace(RegisDataFragment *input)
+{
+ int skipped = 0;
+ char ch;
+
+ assert(input);
+
+ for (; input->pos < input->len; input->pos++) {
+ /* FIXME: the semicolon isn't whitespace -- it also terminates the current command even if inside of an optionset or extent */
+ ch = input->start[input->pos];
+ if (ch != ',' && 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;
+
+ 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;
+ }
+ }
+ }
+
+ if (output->len < 1U)
+ return 0;
+
+ return 1;
+}
+
+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(ch) && !isupper(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;
+ char prev_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++) {
+ 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 during before closing quote\n"));
+ return 0;
+}
+
+static int
+extract_regis_optionset(RegisDataFragment *input, RegisDataFragment *output)
+{
+ char 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;
+
+ /* FIXME: handle strings with parens */
+ for (; input->pos < input->len; input->pos++, output->len++) {
+ ch = input->start[input->pos];
+ if (ch == ';')
+ 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 nesting;
+
+ assert(input);
+ assert(option);
+ assert(output);
+
+ /* LETTER suboptions* value? */
+ /* FIXME: can there be whitespace or commas inside of an option? */
+ /* 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];
+ if (ch == ';' || ch == ',' || ch == '(' || ch == ')' || isdigit(ch)) {
+ return 0;
+ }
+ *option = ch;
+ input->pos++;
+ output->start++;
+ nesting = 0;
+
+ /* FIXME: handle strings with parens, nested parens, etc. */
+ for (; input->pos < input->len; input->pos++, output->len++) {
+ ch = input->start[input->pos];
+ TRACE(("looking at option char %c\n", ch));
+ /* FIXME: any special rules for commas? any need to track parens? */
+ if (ch == '(') {
+ TRACE(("nesting++\n"));
+ nesting++;
+ }
+ if (ch == ')') {
+ TRACE(("nesting--\n"));
+ nesting--;
+ if (nesting < 0) {
+ TRACE(("DATA_ERROR: found ReGIS option has value with too many close parens \"%c\"\n", *option));
+ return 0;
+ }
+ }
+ /* top-level commas indicate the end of this option and the start of another */
+ if (nesting == 0 && ch == ',')
+ break;
+ if (ch == ';')
+ break;
+ }
+ if (nesting != 0) {
+ TRACE(("DATA_ERROR: mismatched parens 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(Graphic const *graphic, RegisDataFragment const *input, RegisterNum *out)
+{
+ int val;
+ RegisDataFragment colorspec;
+ RegisDataFragment coloroption;
+
+ copy_fragment(&colorspec, input);
+ TRACE(("looking at colorspec pattern: \"%s\"\n", fragment_to_tempstr(&colorspec)));
+
+ if (regis_num_to_int(&colorspec, &val)) {
+ if (val < 0 || val >= (int) graphic->valid_registers) { /* FIXME: wrap? */
+ TRACE(("DATA_ERROR: colorspec value %d\n", val));
+ return 0;
+ }
+ TRACE(("colorspec contains index for register %u\n", val));
+ *out = (RegisterNum) val;
+ return 1;
+ }
+
+ if (extract_regis_optionset(&colorspec, &coloroption)) {
+ short r, g, b;
+ TRACE(("option: \"%s\"\n", fragment_to_tempstr(&coloroption)));
+
+ if (fragment_len(&coloroption) == 1) {
+ char ch = pop_fragment(&coloroption);
+
+ TRACE(("got regis RGB colorspec pattern: \"%s\"\n",
+ fragment_to_tempstr(&coloroption)));
+ switch (ch) {
+ case 'D':
+ case 'd':
+ r = 0;
+ g = 0;
+ b = 0;
+ break;
+ case 'R':
+ case 'r':
+ r = 100;
+ g = 0;
+ b = 0;
+ break;
+ case 'G':
+ case 'g':
+ r = 0;
+ g = 100;
+ b = 0;
+ break;
+ case 'B':
+ case 'b':
+ r = 0;
+ g = 0;
+ b = 100;
+ break;
+ case 'C':
+ case 'c':
+ r = 0;
+ g = 100;
+ b = 100;
+ break;
+ case 'Y':
+ case 'y':
+ r = 100;
+ g = 100;
+ b = 0;
+ break;
+ case 'M':
+ case 'm':
+ r = 100;
+ g = 0;
+ b = 100;
+ break;
+ case 'W':
+ case 'w':
+ r = 100;
+ g = 100;
+ b = 100;
+ break;
+ default:
+ TRACE(("unknown RGB color name: \"%c\"\n", ch));
+ return 0;
+ }
+ } else {
+ short h, l, s;
+
+ if (sscanf(fragment_to_tempstr(&coloroption),
+ "%*1[Hh]%hd%*1[Ll]%hd%*1[Ss]%hd",
+ &h, &l, &s) != 3) {
+ TRACE(("unrecognized colorspec format: \"%s\"\n",
+ fragment_to_tempstr(&coloroption)));
+ return 0;
+ }
+ hls2rgb(h, l, s, &r, &g, &b);
+ }
+ /* FIXME: check for trailing junk? */
+ *out = find_color_register(graphic->color_registers, r, g, b);
+ TRACE(("colorspec maps to closest register %u\n", *out));
+ return 1;
+ }
+
+ TRACE(("unrecognized colorspec format: \"%s\"\n", fragment_to_tempstr(&colorspec)));
+ return 0;
+}
+
+static int
+load_regis_extent(char const *extent, int origx, int origy, int *xloc, int *yloc)
+{
+ int xsign, ysign;
+ char const *xpart;
+ char const *ypart;
+
+ xpart = extent;
+ if ((ypart = strchr(extent, ','))) {
+ ypart++;
+ } else {
+ 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] == ',') {
+ *xloc = origx;
+ } else if (xsign == 0) {
+ *xloc = atoi(xpart);
+ } else {
+ *xloc = origx + xsign * atoi(xpart);
+ }
+ if (ypart[0] == '\0') {
+ *yloc = origy;
+ } else if (ysign == 0) {
+ *yloc = atoi(ypart);
+ } else {
+ *yloc = origy + ysign * atoi(ypart);
+ }
+
+ return 1;
+}
+
+static int
+load_regis_pixelvector(char const *pixelvector,
+ unsigned mul,
+ int origx, int origy,
+ int *xloc, int *yloc)
+{
+ int dx = 0, dy = 0;
+ int i;
+
+ for (i = 0; pixelvector[i] != '\0'; i++) {
+ switch (pixelvector[i]) {
+ case '0':
+ dx += 1;
+ break;
+ case '1':
+ dx += 1;
+ dy -= 1;
+ break;
+ case '2':
+ dy -= 1;
+ break;
+ case '3':
+ dx -= 1;
+ dy -= 1;
+ break;
+ case '4':
+ dx -= 1;
+ break;
+ case '5':
+ dx -= 1;
+ dy += 1;
+ break;
+ case '6':
+ dy += 1;
+ break;
+ case '7':
+ dx += 1;
+ dy += 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ *xloc = origx + dx * (int) mul;
+ *yloc = origy + dy * (int) mul;
+
+ return 1;
+}
+
+static int
+load_regis_write_control(RegisParseState *state,
+ Graphic const *graphic,
+ 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 '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) 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_colorspec(graphic, arg, &out->foreground)) {
+ TRACE(("DATA_ERROR: write control foreground color specifier not recognized: \"%s\"\n",
+ fragment_to_tempstr(arg)));
+ return 0;
+ }
+ 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) {
+ skip_regis_whitespace(arg);
+ TRACE(("looking for option in \"%s\"\n", fragment_to_tempstr(arg)));
+ if (extract_regis_optionset(arg, &suboptionset)) {
+ TRACE(("got regis write pattern suboptionset: \"%s\"\n",
+ fragment_to_tempstr(&suboptionset)));
+ while (suboptionset.pos < suboptionset.len) {
+ skip_regis_whitespace(&suboptionset);
+ if (peek_fragment(&suboptionset) == ',') {
+ pop_fragment(&suboptionset);
+ continue;
+ }
+ if (extract_regis_option(&suboptionset, &suboption, &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;
+
+ skip_regis_whitespace(&suboptionarg);
+ 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;
+ char ch;
+
+ TRACE(("converting pattern bits \"%s\"\n",
+ fragment_to_tempstr(&item)));
+ for (bitcount = 0;; bitcount++) {
+ 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;
+ }
+
+ TRACE(("DATA_ERROR: skipping unknown token in pattern suboption: \"%s\"\n",
+ fragment_to_tempstr(arg)));
+ pop_fragment(arg);
+ }
+ }
+ break;
+ case 'C':
+ case 'c':
+ TRACE(("write control compliment writing mode \"%s\"\n",
+ fragment_to_tempstr(arg)));
+ out->write_style = WRITE_STYLE_COMPLEMENT;
+ 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;
+ int ref_x = cur_x, ref_y = cur_y;
+ int shading_enabled = 0;
+
+ while (arg->pos < arg->len) {
+ skip_regis_whitespace(arg);
+
+ if (extract_regis_string(arg, state->temp, state->templen)) {
+ TRACE(("found fill char \"%s\"\n", state->temp));
+ /* FIXME: allow longer strings ignoring extra chars? */
+ if (strlen(state->temp) != 1) {
+ TRACE(("DATA_ERROR: expected exactly one char in fill string FIXME\n"));
+ return 0;
+ }
+ shading_character = state->temp[0];
+ TRACE(("shading character is: %d\n", (int) shading_character));
+ continue;
+ }
+
+ if (extract_regis_optionset(arg, &suboptionset)) {
+ TRACE(("got regis shading control suboptionset: \"%s\"\n",
+ fragment_to_tempstr(&suboptionset)));
+ while (suboptionset.pos < suboptionset.len) {
+ skip_regis_whitespace(&suboptionset);
+ if (peek_fragment(&suboptionset) == ',') {
+ pop_fragment(&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 vertical shading suboption \"%s\"\n",
+ fragment_to_tempstr(&suboptionarg)));
+ if (fragment_len(&suboptionarg)) {
+ TRACE(("DATA_ERROR: unexpected value to vertical shading suboption FIXME\n"));
+ return 0;
+ }
+ reference_dim = WRITE_SHADING_REF_X;
+ 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)) {
+ if (!load_regis_extent(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;
+ }
+
+ 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;
+ } else {
+ /* FIXME: confirm there is no effect if shading isn't enabled
+ * in the same command
+ */
+ out->shading_enabled = 0U;
+ }
+ }
+ 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,
+ Graphic const *graphic,
+ int cur_x, int cur_y,
+ RegisDataFragment *controls,
+ RegisWriteControls *out)
+{
+ RegisDataFragment optionset;
+ RegisDataFragment arg;
+ char option;
+
+ while (controls->pos < controls->len) {
+ skip_regis_whitespace(controls);
+
+ if (extract_regis_optionset(controls, &optionset)) {
+ TRACE(("got regis write control optionset: \"%s\"\n",
+ fragment_to_tempstr(&optionset)));
+ while (optionset.pos < optionset.len) {
+ skip_regis_whitespace(&optionset);
+ if (peek_fragment(&optionset) == ',') {
+ pop_fragment(&optionset);
+ continue;
+ }
+ if (extract_regis_option(&optionset, &option, &arg)) {
+ TRACE(("got regis write control option and value: \"%c\" \"%s\"\n",
+ option, fragment_to_tempstr(&arg)));
+ if (!load_regis_write_control(state, graphic,
+ 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 */
+ case 240: /* FIXME */
+ case 241: /* FIXME */
+ case 330:
+ controls->foreground = 3U;
+ break;
+ case 340:
+ controls->foreground = 7U;
+ break;
+ default: /* FIXME */
+ controls->foreground = 63U;
+ 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_Y;
+ /* FIXME: add the rest */
+}
+
+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;
+}
+
+static void
+init_regis_graphics_context(int terminal_id, RegisGraphicsContext *context)
+{
+ context->terminal_id = terminal_id;
+ /*
+ * Generate a mask covering all valid color register address bits
+ * (but don't bother past 2**16).
+ */
+ context->all_planes = (unsigned) context->graphic->valid_registers;
+ 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;
+
+ 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);
+
+ /* FIXME: coordinates */
+ /* FIXME: scrolling */
+ /* FIXME: output maps */
+ 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 */
+ /* FIXME: text settings */
+}
+
+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 and name
+ * "ascii"xx,xx,xx,xx,xx,xx,xx,xx # pixel values
+ */
+ TRACE(("found ReGIS command \"%c\" (load charset)\n", ch));
+ state->command = 'l';
+ break;
+ case 'P':
+ case 'p':
+ /* Position
+
+ * P
+ * (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> # 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 == oneshot, 1 == multiple) (always returns CR)
+ * (L) # character set
+ * (M(<name>) # macrograph contents
+ * (M(=) # macrograph storage
+ * (P) # cursor position
+ * (P(I)) # interactive cursor position
+ */
+ 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
+ * (H(P<printer offset>)[<print area cornet>][<print area corner>)
+ * (I<color register>) # set the background to a specific register
+ * (I(<rgb>)) # set the background to the register closest to an RGB value
+ * (I(<hls>)) # set the background to the register closest to an HLS color
+ * (M<color index to set>(L<mono level>)...) # level is 0 ... 100 (sets grayscale registers only)
+ * (M<color index to set>(<RGB code>)...) # 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<RGB code>)...) # 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>(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)
+ * (P<graphics page number>) # 0 (default) or 1
+ * (T(<time delay ticks>) # 60 ticks per second, up to 32767 ticks
+ * (W(M<factor>) # PV value
+ * [scroll offset] # optional
+ */
+ TRACE(("found ReGIS command \"%c\" (screen)\n", ch));
+ state->command = 's';
+ break;
+ case 'T':
+ case 't':
+ /* Text
+
+ * T
+ * (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 tilt
+ * (E) # end temporary text control
+ * (H<factor>) # select a height multiplier (1-256)
+ * (I<angle>) # italics: 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 cell 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';
+ 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(<rgb>)) # set the foreground to the register closest to an RGB value
+ * (I(<hls>)) # set the foreground to the register closest to an HLS color
+ * (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 FIXME\n"));
+ /* FIXME: parse, handle :<name> */
+ 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;
+ }
+ break;
+ default:
+ TRACE(("DATA_ERROR: unknown ReGIS command %04x (%c)\n",
+ (int) ch, ch));
+ state->command = '_';
+ state->option = '_';
+ return 0;
+ }
+
+ state->option = '_';
+
+ return 1;
+}
+
+static int
+parse_regis_optionset(RegisParseState *state)
+{
+ if (!extract_regis_optionset(&state->input, &state->optionset))
+ return 0;
+
+ TRACE(("found ReGIS optionset \"%s\"\n", fragment_to_tempstr(&state->optionset)));
+ state->option = '_';
+
+ return 1;
+}
+
+static int
+parse_regis_option(RegisParseState *state, RegisGraphicsContext *context)
+{
+ RegisDataFragment optionarg;
+
+ if (!extract_regis_option(&state->optionset, &state->option, &optionarg))
+ return 0;
+
+ 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(&arclen)));
+ 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));
+ }
+ 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(("end curve \"%s\"\n", fragment_to_tempstr(&optionarg)));
+ switch (state->curve_mode) {
+ case CURVE_POSITION_CLOSED_CURVE:
+ {
+ int i;
+
+#ifdef DEBUG_SPLINE_POINTS
+ printf("points: \n");
+ for (i = 0; i < (int) state->num_points; i++)
+ printf(" %d,%d\n",
+ state->x_points[i], state->y_points[i]);
+#endif
+
+#ifdef DEBUG_SPLINE_WITH_ROTATION
+ {
+ static int shift = 0;
+ int temp_x[MAX_CURVE_POINTS], temp_y[MAX_CURVE_POINTS];
+ shift++;
+ shift = shift % state->num_points;
+ for (i = 0; i < (int) state->num_points; i++) {
+ temp_x[i] = state->x_points[i];
+ temp_y[i] = state->y_points[i];
+ }
+ for (i = 0; i < (int) 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 < (int) state->num_points; i++)
+ printf(" %d,%d\n",
+ state->x_points[i], state->y_points[i]);
+#endif
+ }
+#endif
+
+ for (i = (int) 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 = (int) 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 < (int) state->num_points; i++)
+ printf(" %d,%d\n",
+ state->x_points[i], state->y_points[i]);
+#endif
+ }
+ TRACE(("drawing closed spline\n"));
+ global_context = context; /* FIXME: remove after updating spline code */
+ plotCubicSpline((int) state->num_points - 1,
+ state->x_points, state->y_points,
+ 1);
+ break;
+ case CURVE_POSITION_OPEN_CURVE:
+ TRACE(("drawing open spline\n"));
+#ifdef DEBUG_SPLINE_POINTS
+ {
+ int i;
+
+ printf("points: \n");
+ for (i = 0; i < (int) state->num_points; i++)
+ printf(" %d,%d\n",
+ state->x_points[i], state->y_points[i]);
+ }
+#endif
+ global_context = context; /* FIXME: remove after updating spline code */
+ plotCubicSpline((int) state->num_points - 1,
+ state->x_points, state->y_points,
+ 1);
+ 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->graphic,
+ 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(("inspecting fill option \"%c\" with value \"%s\"\n",
+ state->option, fragment_to_tempstr(&optionarg)));
+ switch (state->option) {
+ case 'C':
+ case 'c':
+ state->command = 'c';
+ state->option = '_';
+ break;
+ case 'V':
+ case 'v':
+ state->command = 'v';
+ state->option = '_';
+ break;
+ case 'W':
+ case 'w':
+ TRACE(("found temporary write options \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ if (!load_regis_write_control_set(state, context->graphic,
+ 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 fill command option '%c' arg \"%s\"\n",
+ state->option, fragment_to_tempstr(&optionarg)));
+ break;
+ }
+ break;
+ case 'l':
+ TRACE(("inspecting load option \"%c\" with value \"%s\"\n",
+ state->option, fragment_to_tempstr(&optionarg)));
+ /* FIXME: parse options */
+ switch (state->option) {
+ case 'A':
+ case 'a':
+ TRACE(("found character specifier option \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ 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\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'E':
+ case 'e':
+ TRACE(("found end position stack \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'S':
+ case 's':
+ TRACE(("found begin unbounded position stack \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'W':
+ case 'w':
+ TRACE(("found temporary write options \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ if (!load_regis_write_control_set(state, context->graphic,
+ 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)));
+ /* FIXME: handle */
+ break;
+ case 'I':
+ case 'i':
+ TRACE(("found set input mode \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'L':
+ case 'l':
+ TRACE(("found character set report \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'M':
+ case 'm':
+ TRACE(("found macrograph report \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'P':
+ case 'p':
+ TRACE(("found cursor position report \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ 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\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen address definition option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ break;
+ case 'C':
+ case 'c':
+ TRACE(("found cursor control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen cursor control option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ 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 screen erase option \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ draw_solid_rectangle(context->graphic, 0, 0,
+ context->graphic->actual_width - 1,
+ context->graphic->actual_height - 1,
+ context->background);
+ break;
+ case 'H':
+ case 'h':
+ TRACE(("found hardcopy control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen hardcopy control option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ break;
+ case 'I':
+ case 'i':
+ TRACE(("found screen background color index \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ if (!load_regis_colorspec(context->graphic, &optionarg, &context->background)) {
+ TRACE(("DATA_ERROR: screen background color specifier not recognized: \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ break;
+ case 'M':
+ case 'm':
+ TRACE(("found screen color register mapping \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ {
+ RegisDataFragment regnum;
+ RegisDataFragment colorspec;
+ char ch;
+
+ 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 0;
+ }
+ if (register_num < 0 ||
+ register_num > (int) context->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_optionset(&optionarg, &colorspec)) {
+ TRACE(("DATA_ERROR: expected to find optionset after register number: \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+
+ 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 (fragment_len(&colorspec) == 1) {
+ short l;
+ ch = pop_fragment(&colorspec);
+
+ TRACE(("got regis RGB colorspec pattern: \"%s\"\n",
+ 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;
+ }
+ if (context->terminal_id == 240 ||
+ context->terminal_id == 330) {
+ /* The VT240 and VT330 models force saturation to zero. */
+ hls2rgb(0, l, 0, &r, &g, &b);
+ }
+ } else {
+ short h, l, s;
+
+ if (sscanf(fragment_to_tempstr(&colorspec),
+ "%*1[Hh]%hd%*1[Ll]%hd%*1[Ss]%hd",
+ &h, &l, &s) != 3) {
+ h = 0;
+ s = 0;
+ if (sscanf(fragment_to_tempstr(&colorspec),
+ "%*1[Ll]%hd", &l) != 1) {
+ TRACE(("unrecognized colorspec: \"%s\"\n",
+ fragment_to_tempstr(&colorspec)));
+ return 0;
+ }
+ }
+ if (context->terminal_id == 240 ||
+ context->terminal_id == 330) {
+ /* The VT240 and VT330 models force saturation to zero. */
+ h = 0;
+ s = 0;
+ }
+ hls2rgb(h, l, s, &r, &g, &b);
+ }
+
+ if (color_only &&
+ (context->terminal_id == 240 ||
+ context->terminal_id == 330))
+ continue;
+ update_color_register(context->graphic,
+ (RegisterNum) register_num,
+ r, g, b);
+ continue;
+ }
+
+ TRACE(("DATA_ERROR: ignoring unexpected character in ReGIS screen color register mapping value \"%c\"\n",
+ pop_fragment(&optionarg)));
+ return 0;
+ }
+ }
+ break;
+ case 'P':
+ case 'p':
+ TRACE(("found graphics page request \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen graphics page option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ break;
+ case 'T':
+ case 't':
+ TRACE(("found time delay \"%s\" FIXME\n", fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen time delay option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ break;
+ case 'W':
+ case 'w':
+ TRACE(("found PV \"%s\" FIXME\n", fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ if (!fragment_len(&optionarg)) {
+ TRACE(("DATA_ERROR: ignoring malformed ReGIS screen PV option value \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+ 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 0;
+ }
+ switch (state->option) {
+ case 'A':
+ case 'a':
+ TRACE(("found character set specifier \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'B':
+ case 'b':
+ TRACE(("found beginning of temporary text control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'D':
+ case 'd':
+ TRACE(("found string tilt control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'E':
+ case 'e':
+ TRACE(("found end of temporary text control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'H':
+ case 'h':
+ TRACE(("found height multiplier \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'I':
+ case 'i':
+ TRACE(("found italic control \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'M':
+ case 'm':
+ TRACE(("found size multiplier \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'S':
+ case 's':
+ TRACE(("found custom display cell size \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'U':
+ case 'u':
+ TRACE(("found custom display unit size \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ 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\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'E':
+ case 'e':
+ TRACE(("found end position stack \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'S':
+ case 's':
+ TRACE(("found begin unbounded position stack \"%s\" FIXME\n",
+ fragment_to_tempstr(&optionarg)));
+ /* FIXME: handle */
+ break;
+ case 'W':
+ case 'w':
+ TRACE(("found temporary write options \"%s\"\n",
+ fragment_to_tempstr(&optionarg)));
+ if (!load_regis_write_control_set(state, context->graphic,
+ 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':
+ TRACE(("inspecting write option \"%c\" with value \"%s\"\n",
+ state->option, fragment_to_tempstr(&optionarg)));
+ if (!load_regis_write_control(state, context->graphic,
+ context->graphics_output_cursor_x, context->graphics_output_cursor_y,
+ state->option, &optionarg, &context->persistent_write_controls)) {
+ TRACE(("DATA_ERROR: invalid write options"));
+ }
+ break;
+ default:
+ TRACE(("DATA_ERROR: unexpected option in \"%c\" command: \"%s\"\n",
+ state->command, fragment_to_tempstr(&optionarg)));
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+parse_regis_items(RegisParseState *state, RegisGraphicsContext *context)
+{
+ RegisDataFragment *input;
+ RegisDataFragment item;
+
+ switch (state->level) {
+ case INPUT:
+ input = &state->input;
+ break;
+ case OPTIONSET:
+ input = &state->optionset;
+ break;
+ default:
+ TRACE(("invalid parse level: %d\n", state->level));
+ return 0;
+ }
+
+ 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_extent(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 degrees;
+ int c_x, c_y;
+ int e_x, e_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) (new_y - orig_y),
+ (double) (new_x - orig_x));
+ degrees = (int) (360.0 * radians / (2.0 * M_PI));
+ if (degrees < 0)
+ degrees += 360;
+
+ TRACE(("drawing arc centered at location %d,%d to location %d,%d from %d degrees for %d degrees\n",
+ c_x, c_y,
+ e_x, e_y,
+ degrees, state->arclen));
+ draw_patterned_arc(context,
+ c_x, c_y,
+ e_x, e_y,
+ degrees, state->arclen);
+ }
+ 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':
+ /* FIXME TRACE(("DATA_ERROR: ignoring pen command with no location\n")); */
+ if (!load_regis_extent(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':
+ /* FIXME: parse, handle */
+ TRACE(("extent in screen command FIXME\n"));
+ break;
+ case 't':
+ /* FIXME: parse, handle */
+ TRACE(("extent in text command FIXME\n"));
+ 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_extent(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 (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_pixelvector(fragment_to_tempstr(&item), context->temporary_write_controls.pv_multiplier,
+ 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':
+ /* FIXME: parse, handle scroll argument */
+ TRACE(("pixelvector in screen command FIXME\n"));
+ break;
+ case 't':
+ /* FIXME: parse, handle */
+ TRACE(("pixelvector in text command FIXME\n"));
+ break;
+ case 'v':
+ /* FIXME: error checking */
+ {
+ int orig_x, orig_y;
+
+ orig_x = context->graphics_output_cursor_x;
+ orig_y = context->graphics_output_cursor_y;
+ if (!load_regis_pixelvector(fragment_to_tempstr(&item), context->temporary_write_controls.pv_multiplier,
+ 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':
+ TRACE(("found character to load: \"%s\" FIXME\n", state->temp));
+ /* FIXME: handle */
+ case 't':
+ TRACE(("found string to draw: \"%s\" FIXME\n", state->temp));
+ /* FIXME: handle */
+ break;
+ default:
+ TRACE(("DATA_ERROR: unexpected string in \"%c\" command: \"%s\"\n",
+ state->command, state->temp));
+ break;
+ }
+ return 1;
+ }
+
+ /* hex values */
+ if (state->command == 'l') {
+ char ch1 = peek_fragment(input);
+ char ch2 = peek_fragment(input);
+ if ((ch1 == '0' ||
+ ch1 == '1' ||
+ ch1 == '2' ||
+ ch1 == '3' ||
+ ch1 == '4' ||
+ ch1 == '5' ||
+ ch1 == '6' ||
+ ch1 == '7' ||
+ ch1 == '8' ||
+ ch1 == '9' ||
+ ch1 == 'a' ||
+ ch1 == 'b' ||
+ ch1 == 'c' ||
+ ch1 == 'd' ||
+ ch1 == 'e' ||
+ ch1 == 'f' ||
+ ch1 == 'A' ||
+ ch1 == 'B' ||
+ ch1 == 'C' ||
+ ch1 == 'D' ||
+ ch1 == 'E' ||
+ ch1 == 'F') &&
+ (ch2 == '0' ||
+ ch2 == '1' ||
+ ch2 == '2' ||
+ ch2 == '3' ||
+ ch2 == '4' ||
+ ch2 == '5' ||
+ ch2 == '6' ||
+ ch2 == '7' ||
+ ch2 == '8' ||
+ ch2 == '9' ||
+ ch2 == 'a' ||
+ ch2 == 'b' ||
+ ch2 == 'c' ||
+ ch2 == 'd' ||
+ ch2 == 'e' ||
+ ch2 == 'f' ||
+ ch2 == 'A' ||
+ ch2 == 'B' ||
+ ch2 == 'C' ||
+ ch2 == 'D' ||
+ ch2 == 'E' ||
+ ch2 == 'F')) {
+ /* FIXME: handle */
+ TRACE(("found hex number: \"%c%c\" FIXME\n", ch1, ch2));
+ pop_fragment(input);
+ pop_fragment(input);
+ if (peek_fragment(input) == ',')
+ pop_fragment(input);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * context:
+ * two pages of 800x480
+ * current page #
+ * current command
+ * persistent write options
+ * temporary write options
+ * output position stack
+ */
+void
+parse_regis(XtermWidget xw, ANSI *params, char const *string)
+{
+ TScreen *screen = TScreenOf(xw);
+ RegisGraphicsContext context;
+ RegisParseState state;
+ unsigned iterations;
+ int charrow = 0;
+ int charcol = 0;
+ unsigned type = 1; /* FIXME: use page number */
+
+ (void) xw;
+ (void) string;
+ (void) params;
+
+ TRACE(("ReGIS vector graphics mode, params=%d\n", params->a_nparam));
+
+ init_fragment(&state.input, string);
+ init_fragment(&state.optionset, "");
+ state.level = INPUT;
+ 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;
+ }
+ state.command = '_';
+ state.option = '_';
+
+ memset(&context, 0, sizeof(context));
+
+ context.graphic = get_new_or_matching_graphic(xw,
+ charrow, charcol,
+ 800, 480,
+ type);
+ init_regis_graphics_context(screen->terminal_id, &context);
+ context.graphic->valid = 1;
+ context.graphic->dirty = 1;
+ refresh_modified_displayed_graphics(screen);
+
+ iterations = 0U;
+ for (;;) {
+ state.level = INPUT;
+ TRACE(("parsing at top level: %d of %d (next char %c)\n",
+ state.input.pos,
+ state.input.len,
+ peek_fragment(&state.input)));
+ if (skip_regis_whitespace(&state.input))
+ continue;
+ iterations++;
+ if (parse_regis_command(&state)) {
+ if (iterations > ITERATIONS_BEFORE_REFRESH) {
+ iterations = 0U;
+ refresh_modified_displayed_graphics(screen);
+ }
+ context.graphic->dirty = 1;
+ /* FIXME: verify that these are the things reset on a new command */
+ copy_regis_write_controls(&context.persistent_write_controls, &context.temporary_write_controls);
+ context.pattern_count = 0U;
+ context.pattern_bit = 1U;
+ continue;
+ }
+ if (parse_regis_optionset(&state)) {
+ state.level = OPTIONSET;
+ TRACE(("parsing at optionset level: %d of %d\n",
+ state.optionset.pos,
+ state.optionset.len));
+ for (;;) {
+ if (state.optionset.pos >= state.optionset.len)
+ break;
+ TRACE(("looking at optionset character: \"%c\"\n",
+ peek_fragment(&state.optionset)));
+ if (skip_regis_whitespace(&state.optionset))
+ continue;
+ if (peek_fragment(&state.optionset) == ',') {
+ pop_fragment(&state.optionset);
+ continue;
+ }
+ if (parse_regis_option(&state, &context))
+ continue;
+ if (parse_regis_items(&state, &context))
+ continue;
+ if (state.optionset.pos >= state.optionset.len)
+ break;
+ TRACE(("DATA_ERROR: skipping unknown token in optionset: \"%c\"\n",
+ pop_fragment(&state.optionset)));
+ /* FIXME: suboptions */
+ }
+ state.option = '_';
+ continue;
+ }
+ if (parse_regis_items(&state, &context))
+ continue;
+ if (state.optionset.pos >= state.optionset.len)
+ break;
+ TRACE(("DATA_ERROR: skipping unknown token at top level: \"%c\"\n",
+ pop_fragment(&state.input)));
+ }
+
+ free(state.temp);
+
+ refresh_modified_displayed_graphics(screen);
+ TRACE(("DONE! Successfully parsed ReGIS data.\n"));
+}
diff --git a/app/xterm/graphics_regis.h b/app/xterm/graphics_regis.h
new file mode 100644
index 000000000..03758dec3
--- /dev/null
+++ b/app/xterm/graphics_regis.h
@@ -0,0 +1,48 @@
+/* $XTermId: graphics_regis.h,v 1.1 2014/04/11 19:36:41 Ross.Combs Exp $ */
+
+/*
+ * Copyright 2014 by Ross Combs
+ * Copyright 2014 by Thomas E. Dickey
+ *
+ * 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.
+ */
+
+#ifndef included_graphics_regis_h
+#define included_graphics_regis_h
+/* *INDENT-OFF* */
+
+#include <ptyx.h>
+
+#if OPT_REGIS_GRAPHICS
+extern void parse_regis(XtermWidget xw, ANSI *params, char const *string);
+#else
+#define parse_regis(xw, params, string) /* nothing */
+#endif
+
+/* *INDENT-ON* */
+
+#endif /* included_graphics_regis_h */
diff --git a/app/xterm/graphics_sixel.c b/app/xterm/graphics_sixel.c
new file mode 100644
index 000000000..e82984d9b
--- /dev/null
+++ b/app/xterm/graphics_sixel.c
@@ -0,0 +1,640 @@
+/* $XTermId: graphics_sixel.c,v 1.8 2014/05/02 22:53:20 tom Exp $ */
+
+/*
+ * Copyright 2014 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 <stdlib.h>
+
+#include <data.h>
+#include <VTparse.h>
+#include <ptyx.h>
+
+#include <assert.h>
+#include <graphics.h>
+#include <graphics_sixel.h>
+
+/***====================================================================***/
+/*
+ * Parse numeric parameters which have the operator as a prefix rather than a
+ * suffix as in ANSI format.
+ *
+ * # 0
+ * #1 1
+ * #1; 1
+ * "1;2;640;480 4
+ * #1;2;0;0;0 5
+ */
+static void
+parse_prefixedtype_params(ANSI *params, const char **string)
+{
+ const char *cp = *string;
+ ParmType nparam = 0;
+ int last_empty = 1;
+
+ memset(params, 0, sizeof(*params));
+ params->a_final = CharOf(*cp);
+ if (*cp != '\0')
+ cp++;
+
+ while (*cp != '\0') {
+ Char ch = CharOf(*cp);
+
+ if (isdigit(ch)) {
+ last_empty = 0;
+ if (nparam < NPARAM) {
+ params->a_param[nparam] =
+ (ParmType) ((params->a_param[nparam] * 10)
+ + (ch - '0'));
+ }
+ } else if (ch == ';') {
+ last_empty = 1;
+ nparam++;
+ } else if (ch == ' ' || ch == '\r' || ch == '\n') {
+ /* EMPTY */ ;
+ } else {
+ break;
+ }
+ cp++;
+ }
+
+ *string = cp;
+ if (!last_empty)
+ nparam++;
+ if (nparam > NPARAM)
+ params->a_nparam = NPARAM;
+ else
+ params->a_nparam = nparam;
+}
+
+typedef struct {
+ RegisterNum current_register;
+ RegisterNum background; /* current background color register or hole */
+ int aspect_vertical;
+ int aspect_horizontal;
+ int declared_width; /* size as reported by the application */
+ int declared_height; /* size as reported by the application */
+ int row; /* context used during parsing */
+ int col; /* context used during parsing */
+} SixelContext;
+
+/* sixel scrolling:
+ * VK100/GIGI ? (did it even support Sixel?)
+ * VT125 unsupported
+ * VT240 unsupported
+ * VT241 unsupported
+ * VT330 mode setting
+ * VT382 ?
+ * VT340 mode setting
+ * dxterm ?
+ */
+
+static void
+init_sixel_background(Graphic *graphic, SixelContext const *context)
+{
+ int r, c;
+
+ TRACE(("initializing sixel background to size=%dx%d bgcolor=%hu\n",
+ context->declared_width,
+ context->declared_height,
+ context->background));
+
+ if (context->background == COLOR_HOLE)
+ return;
+
+ for (r = 0; r < graphic->actual_height; r++) {
+ for (c = 0; c < graphic->actual_width; c++) {
+ graphic->pixels[r * graphic->max_width + c] = context->background;
+ }
+ }
+ graphic->color_registers_used[context->background] = 1;
+}
+
+static void
+set_sixel(Graphic *graphic, SixelContext const *context, int sixel)
+{
+ RegisterNum color;
+ int pix;
+
+ color = context->current_register;
+ TRACE(("drawing sixel at pos=%d,%d color=%hu (hole=%d, [%d,%d,%d])\n",
+ context->col,
+ context->row,
+ color,
+ color == COLOR_HOLE,
+ ((color != COLOR_HOLE)
+ ? (unsigned) graphic->color_registers[color].r : 0U),
+ ((color != COLOR_HOLE)
+ ? (unsigned) graphic->color_registers[color].g : 0U),
+ ((color != COLOR_HOLE)
+ ? (unsigned) graphic->color_registers[color].b : 0U)));
+ for (pix = 0; pix < 6; pix++) {
+ if (context->col < graphic->max_width &&
+ context->row + pix < graphic->max_height) {
+ if (sixel & (1 << pix)) {
+ if (context->col + 1 > graphic->actual_width) {
+ graphic->actual_width = context->col + 1;
+ }
+ if (context->row + pix + 1 > graphic->actual_height) {
+ graphic->actual_height = context->row + pix + 1;
+ }
+ graphic->pixels[
+ (((context->row + pix) * graphic->max_width)
+ + context->col)
+ ] = color;
+ }
+ } else {
+ TRACE(("sixel pixel %d out of bounds\n", pix));
+ }
+ }
+}
+
+static void
+update_sixel_aspect(SixelContext const *context, Graphic *graphic)
+{
+ /* We want to keep the ratio accurate but would like every pixel to have
+ * the same size so keep these as whole numbers.
+ */
+ /* FIXME: DEC terminals had pixels about twice as tall as they were wide,
+ * and it seems the VT125 and VT24x only used data from odd graphic rows.
+ * This means it basically cancels out if we ignore both, except that
+ * the even rows of pixels may not be written by the application such that
+ * they are suitable for display. In practice this doesn't seem to be
+ * an issue but I have very few test files/programs.
+ */
+ if (context->aspect_vertical < context->aspect_horizontal) {
+ graphic->pixw = 1;
+ graphic->pixh = ((context->aspect_vertical
+ + context->aspect_horizontal - 1)
+ / context->aspect_horizontal);
+ } else {
+ graphic->pixw = ((context->aspect_horizontal
+ + context->aspect_vertical - 1)
+ / context->aspect_vertical);
+ graphic->pixh = 1;
+ }
+ TRACE(("sixel aspect ratio: an=%d ad=%d -> pixw=%d pixh=%d\n",
+ context->aspect_vertical,
+ context->aspect_horizontal,
+ graphic->pixw,
+ graphic->pixh));
+}
+
+/*
+ * Interpret sixel graphics sequences.
+ *
+ * Resources:
+ * http://en.wikipedia.org/wiki/Sixel
+ * http://vt100.net/docs/vt3xx-gp/chapter14.html
+ * ftp://ftp.cs.utk.edu/pub/shuford/terminal/sixel_graphics_news.txt
+ * ftp://ftp.cs.utk.edu/pub/shuford/terminal/all_about_sixels.txt
+ */
+void
+parse_sixel(XtermWidget xw, ANSI *params, char const *string)
+{
+ TScreen *screen = TScreenOf(xw);
+ Graphic *graphic;
+ SixelContext context;
+ Char ch;
+
+ switch (screen->terminal_id) {
+ case 240:
+ case 241:
+ case 330:
+ case 340:
+ context.aspect_vertical = 2;
+ context.aspect_horizontal = 1;
+ break;
+ case 382:
+ context.aspect_vertical = 1;
+ context.aspect_horizontal = 1;
+ break;
+ default:
+ context.aspect_vertical = 2;
+ context.aspect_horizontal = 1;
+ break;
+ }
+
+ context.declared_width = 0;
+ context.declared_height = 0;
+
+ context.row = 0;
+ context.col = 0;
+
+ /* default isn't white on the VT240, but not sure what it is */
+ context.current_register = 3; /* FIXME: using green, but not sure what it should be */
+
+ if (xw->keyboard.flags & MODE_DECSDM) {
+ TRACE(("sixel scrolling enabled: inline positioning for graphic at %d,%d\n",
+ screen->cur_row, screen->cur_col));
+ graphic = get_new_graphic(xw, screen->cur_row, screen->cur_col, 0U);
+ } else {
+ TRACE(("sixel scrolling disabled: inline positioning for graphic at %d,%d\n",
+ 0, 0));
+ graphic = get_new_graphic(xw, 0, 0, 0U);
+ }
+
+ {
+ int Pmacro = params->a_param[0];
+ int Pbgmode = params->a_param[1];
+ int Phgrid = params->a_param[2];
+ int Pan = params->a_param[3];
+ int Pad = params->a_param[4];
+ int Ph = params->a_param[5];
+ int Pv = params->a_param[6];
+
+ (void) Phgrid;
+
+ TRACE(("sixel bitmap graphics sequence: params=%d (Pmacro=%d Pbgmode=%d Phgrid=%d) scroll_amt=%d\n",
+ params->a_nparam,
+ Pmacro,
+ Pbgmode,
+ Phgrid,
+ screen->scroll_amt));
+
+ switch (params->a_nparam) {
+ case 7:
+ if (Pan == 0 || Pad == 0) {
+ TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
+ return;
+ }
+ context.aspect_vertical = Pan;
+ context.aspect_horizontal = Pad;
+
+ if (Ph == 0 || Pv == 0) {
+ TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
+ Ph, Pv));
+ return;
+ }
+ if (Ph > graphic->max_width || Pv > graphic->max_height) {
+ TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
+ Ph, Pv));
+ return;
+ }
+ context.declared_width = Ph;
+ context.declared_height = Pv;
+ if (context.declared_width > graphic->actual_width) {
+ graphic->actual_width = context.declared_width;
+ }
+ if (context.declared_height > graphic->actual_height) {
+ graphic->actual_height = context.declared_height;
+ }
+ break;
+ case 3:
+ case 2:
+ case 1:
+ switch (Pmacro) {
+ case 0:
+ /* keep default aspect settings */
+ break;
+ case 1:
+ case 5:
+ case 6:
+ context.aspect_vertical = 2;
+ context.aspect_horizontal = 1;
+ break;
+ case 2:
+ context.aspect_vertical = 5;
+ context.aspect_horizontal = 1;
+ break;
+ case 3:
+ case 4:
+ context.aspect_vertical = 3;
+ context.aspect_horizontal = 1;
+ break;
+ case 7:
+ case 8:
+ case 9:
+ context.aspect_vertical = 1;
+ context.aspect_horizontal = 1;
+ break;
+ default:
+ TRACE(("DATA_ERROR: unknown sixel macro mode parameter\n"));
+ return;
+ }
+ break;
+ case 0:
+ break;
+ default:
+ TRACE(("DATA_ERROR: unexpected parameter count (found %d)\n", params->a_nparam));
+ return;
+ }
+
+ if (Pbgmode == 1) {
+ context.background = COLOR_HOLE;
+ } else {
+ /* FIXME: is the default background register always zero? what about in light background mode? */
+ context.background = 0;
+ }
+
+ /* Ignore the grid parameter because it seems only printers paid attention to it.
+ * The VT3xx was always 0.0195 cm.
+ */
+ }
+
+ update_sixel_aspect(&context, graphic);
+
+ for (;;) {
+ ch = CharOf(*string);
+ if (ch == '\0')
+ break;
+
+ if (ch >= 0x3f && ch <= 0x7e) {
+ int sixel = ch - 0x3f;
+ TRACE(("sixel=%x (%c)\n", sixel, (char) ch));
+ if (!graphic->valid) {
+ init_sixel_background(graphic, &context);
+ graphic->valid = 1;
+ }
+ set_sixel(graphic, &context, sixel);
+ context.col++;
+ } else if (ch == '$') { /* DECGCR */
+ /* ignore DECCRNLM in sixel mode */
+ TRACE(("sixel CR\n"));
+ context.col = 0;
+ } else if (ch == '-') { /* DECGNL */
+ int scroll_lines;
+ TRACE(("sixel NL\n"));
+ scroll_lines = 0;
+ while (graphic->charrow - scroll_lines +
+ (((context.row + 6) * graphic->pixh
+ + FontHeight(screen) - 1)
+ / FontHeight(screen)) > screen->bot_marg) {
+ scroll_lines++;
+ }
+ context.col = 0;
+ context.row += 6;
+ /* If we hit the bottom margin on the graphics page (well, we just use the
+ * text margin for now), the behavior is to either scroll or to discard
+ * the remainder of the graphic depending on this setting.
+ */
+ if (scroll_lines > 0) {
+ if (xw->keyboard.flags & MODE_DECSDM) {
+ Display *display = screen->display;
+ xtermScroll(xw, scroll_lines);
+ XSync(display, False);
+ TRACE(("graphic scrolled the screen %d lines. screen->scroll_amt=%d screen->topline=%d, now starting row is %d\n",
+ scroll_lines,
+ screen->scroll_amt,
+ screen->topline,
+ graphic->charrow));
+ } else {
+ break;
+ }
+ }
+ } else if (ch == '!') { /* DECGRI */
+ int Pcount;
+ const char *start;
+ int sixel;
+ int i;
+
+ start = ++string;
+ for (;;) {
+ ch = CharOf(*string);
+ if (ch != '0' &&
+ ch != '1' &&
+ ch != '2' &&
+ ch != '3' &&
+ ch != '4' &&
+ ch != '5' &&
+ ch != '6' &&
+ ch != '7' &&
+ ch != '8' &&
+ ch != '9' &&
+ ch != ' ' &&
+ ch != '\r' &&
+ ch != '\n')
+ break;
+ string++;
+ }
+ if (ch == '\0') {
+ TRACE(("DATA_ERROR: sixel data string terminated in the middle of a repeat operator\n"));
+ return;
+ }
+ if (string == start) {
+ TRACE(("DATA_ERROR: sixel data string contains a repeat operator with empty count\n"));
+ return;
+ }
+ Pcount = atoi(start);
+ sixel = ch - 0x3f;
+ TRACE(("sixel repeat operator: sixel=%d (%c), count=%d\n",
+ sixel, (char) ch, Pcount));
+ if (!graphic->valid) {
+ init_sixel_background(graphic, &context);
+ graphic->valid = 1;
+ }
+ for (i = 0; i < Pcount; i++) {
+ set_sixel(graphic, &context, sixel);
+ context.col++;
+ }
+ } else if (ch == '#') { /* DECGCI */
+ ANSI color_params;
+ int Pregister;
+
+ parse_prefixedtype_params(&color_params, &string);
+ Pregister = color_params.a_param[0];
+ if (Pregister >= (int) graphic->valid_registers) {
+ TRACE(("DATA_WARNING: sixel color operator uses out-of-range register %d\n", Pregister));
+ /* FIXME: supposedly the DEC terminals wrapped register indicies -- verify */
+ while (Pregister >= (int) graphic->valid_registers)
+ Pregister -= (int) graphic->valid_registers;
+ TRACE(("DATA_WARNING: converted to %d\n", Pregister));
+ }
+
+ if (color_params.a_nparam > 2 && color_params.a_nparam <= 5) {
+ int Pspace = color_params.a_param[1];
+ int Pc1 = color_params.a_param[2];
+ int Pc2 = color_params.a_param[3];
+ int Pc3 = color_params.a_param[4];
+ short r, g, b;
+
+ TRACE(("sixel set color register=%d space=%d color=[%d,%d,%d] (nparams=%d)\n",
+ Pregister, Pspace, Pc1, Pc2, Pc3, color_params.a_nparam));
+
+ switch (Pspace) {
+ case 1: /* HLS */
+ if (Pc1 > 360 || Pc2 > 100 || Pc3 > 100) {
+ TRACE(("DATA_ERROR: sixel set color operator uses out-of-range HLS color coordinates %d,%d,%d\n",
+ Pc1, Pc2, Pc3));
+ return;
+ }
+ hls2rgb(Pc1, Pc2, Pc3, &r, &g, &b);
+ break;
+ case 2: /* RGB */
+ if (Pc1 > 100 || Pc2 > 100 || Pc3 > 100) {
+ TRACE(("DATA_ERROR: sixel set color operator uses out-of-range RGB color coordinates %d,%d,%d\n",
+ Pc1, Pc2, Pc3));
+ return;
+ }
+ r = (short) Pc1;
+ g = (short) Pc2;
+ b = (short) Pc3;
+ break;
+ default: /* unknown */
+ TRACE(("DATA_ERROR: sixel set color operator uses unknown color space %d\n", Pspace));
+ return;
+ }
+ update_color_register(graphic,
+ (RegisterNum) Pregister,
+ r, g, b);
+ } else if (color_params.a_nparam == 1) {
+ TRACE(("sixel switch to color register=%d (nparams=%d)\n",
+ Pregister, color_params.a_nparam));
+ context.current_register = (RegisterNum) Pregister;
+ } else {
+ TRACE(("DATA_ERROR: sixel switch color operator with unexpected parameter count (nparams=%d)\n", color_params.a_nparam));
+ return;
+ }
+ continue;
+ } else if (ch == '"') /* DECGRA */ {
+ ANSI raster_params;
+
+ parse_prefixedtype_params(&raster_params, &string);
+ if (raster_params.a_nparam < 2) {
+ TRACE(("DATA_ERROR: sixel raster attribute operator with incomplete parameters (found %d, expected 2 or 4)\n", raster_params.a_nparam));
+ return;
+ } {
+ int Pan = raster_params.a_param[0];
+ int Pad = raster_params.a_param[1];
+ TRACE(("sixel raster attribute with h:w=%d:%d\n", Pan, Pad));
+ if (Pan == 0 || Pad == 0) {
+ TRACE(("DATA_ERROR: invalid raster ratio %d/%d\n", Pan, Pad));
+ return;
+ }
+ context.aspect_vertical = Pan;
+ context.aspect_horizontal = Pad;
+ update_sixel_aspect(&context, graphic);
+ }
+
+ if (raster_params.a_nparam >= 4) {
+ int Ph = raster_params.a_param[2];
+ int Pv = raster_params.a_param[3];
+
+ TRACE(("sixel raster attribute with h=%d v=%d\n", Ph, Pv));
+ if (Ph == 0 || Pv == 0) {
+ TRACE(("DATA_ERROR: raster image dimensions are invalid %dx%d\n",
+ Ph, Pv));
+ return;
+ }
+ if (Ph > graphic->max_width || Pv > graphic->max_height) {
+ TRACE(("DATA_ERROR: raster image dimensions are too large %dx%d\n",
+ Ph, Pv));
+ return;
+ }
+ context.declared_width = Ph;
+ context.declared_height = Pv;
+ if (context.declared_width > graphic->actual_width) {
+ graphic->actual_width = context.declared_width;
+ }
+ if (context.declared_height > graphic->actual_height) {
+ graphic->actual_height = context.declared_height;
+ }
+ }
+
+ continue;
+ } else if (ch == ' ' || ch == '\r' || ch == '\n') {
+ /* EMPTY */ ;
+ } else {
+ TRACE(("DATA_ERROR: unknown sixel command %04x (%c)\n",
+ (int) ch, ch));
+ }
+
+ string++;
+ }
+
+ /* update the screen */
+ if (screen->scroll_amt)
+ FlushScroll(xw);
+
+ if (xw->keyboard.flags & MODE_DECSDM) {
+ int new_row, new_col;
+
+ if (screen->sixel_scrolls_right) {
+ new_row = (graphic->charrow
+ + (((graphic->actual_height * graphic->pixh)
+ + FontHeight(screen) - 1)
+ / FontHeight(screen))
+ - 1);
+ new_col = (graphic->charcol
+ + (((graphic->actual_width * graphic->pixw)
+ + FontWidth(screen) - 1)
+ / FontWidth(screen)));
+ } else {
+ /* FIXME: At least of the VT382 the vertical position appears to be
+ * truncated (rounded toward zero after converting to character row.
+ * This code rounds up, which seems more useful, but it would be
+ * better to be compatible. Verify this is true on a VT3[34]0 as
+ * well.
+ */
+ new_row = (graphic->charrow
+ + (((graphic->actual_height * graphic->pixh)
+ + FontHeight(screen) - 1)
+ / FontHeight(screen)));
+ new_col = 0;
+ }
+
+ TRACE(("setting text position after %dx%d graphic starting on row=%d col=%d: cursor new_row=%d new_col=%d\n",
+ graphic->actual_width * graphic->pixw,
+ graphic->actual_height * graphic->pixh,
+ graphic->charrow,
+ graphic->charcol,
+ new_row, new_col));
+
+ if (new_col > screen->rgt_marg) {
+ new_col = screen->lft_marg;
+ new_row++;
+ TRACE(("column past left margin, overriding to row=%d col=%d\n",
+ new_row, new_col));
+ }
+
+ while (new_row > screen->bot_marg) {
+ xtermScroll(xw, 1);
+ new_row--;
+ TRACE(("bottom row was past screen. new start row=%d, cursor row=%d\n",
+ graphic->charrow, new_row));
+ }
+
+ if (new_row < 0) {
+ TRACE(("new row is going to be negative (%d)!", new_row)); /* FIXME: this was triggering, now it isn't */
+ goto finis;
+ }
+ set_cur_row(screen, new_row);
+ set_cur_col(screen, new_col <= screen->rgt_marg ? new_col : screen->rgt_marg);
+ }
+
+ finis:
+ refresh_modified_displayed_graphics(screen);
+
+ TRACE(("DONE successfully parsed sixel data\n"));
+ dump_graphic(graphic);
+}
diff --git a/app/xterm/graphics_sixel.h b/app/xterm/graphics_sixel.h
new file mode 100644
index 000000000..54211389c
--- /dev/null
+++ b/app/xterm/graphics_sixel.h
@@ -0,0 +1,48 @@
+/* $XTermId: graphics_sixel.h,v 1.1 2014/04/11 19:36:41 Ross.Combs Exp $ */
+
+/*
+ * Copyright 2014 by Ross Combs
+ * Copyright 2014 by Thomas E. Dickey
+ *
+ * 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.
+ */
+
+#ifndef included_graphics_sixel_h
+#define included_graphics_sixel_h
+/* *INDENT-OFF* */
+
+#include <ptyx.h>
+
+#if OPT_SIXEL_GRAPHICS
+extern void parse_sixel(XtermWidget xw, ANSI *params, char const *string);
+#else
+#define parse_sixel(xw, params, string) /* nothing */
+#endif
+
+/* *INDENT-ON* */
+
+#endif /* included_graphics_sixel_h */