DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T i

⟦abdef1f3b⟧ TextFile

    Length: 19452 (0x4bfc)
    Types: TextFile
    Names: »imagen1-special.c«

Derivation

└─⟦060c9c824⟧ Bits:30007080 DKUUG TeX 2/12/89
    └─⟦this⟧ »./DVIware/laser-setters/umd-dvi/dev/imagen1-special.c« 

TextFile

/*
 * Support drawing routines for Chris Torek's DVI->ImPress program.
 *
 * Requires v1.7 or later ImPress to handle paths.
 * Better if v1.9 or later for arc, circle, and ellipse primitives
 * (define USEGRAPHICS).
 *
 *	Tim Morgan, UC Irvine, 11/17/85
 *
 *
 * At the time these routines are called, the position of the Imagen should
 * have been updated to the upper left corner of the graph (the position
 * the \special appears at in the dvi file).  Then the coordinates in the
 * graphics commands are in terms of a virtual page with axes oriented the
 * same as the Imagen normally has:
 *
 *			0,0
 *			 +-----------> +x
 *			 |
 *			 |
 *			 |
 *			\ /
 *			 +y
 *
 * Angles are measured in the conventional way, from +x towards +y.
 * Unfortunately, that reverses the meaning of "counterclockwise" from
 * what you see in the output.
 *
 * Unfortunately, some 8/300's don't have an aspect ratio which is 1:1.
 * One of ours appears to be 295 dpi in the horizontal direction and 300dpi
 * vertically.  If ASPECT is defined, then dviimp/imagen1 and draw_imp/special
 * use two different variables for the horizontal and vertical resolution, and
 * otherwise, just one.  Because the drawing routines which are defined
 * in ImPress for circles, arcs, and ellipses in V1.9 and later assume that
 * the output device is 1:1, they can't be used if ASPECT is defined, and
 * because I don't want to hack up imagen1 to understand different horizontal
 * and vertical resolutions, we're currently ignoring this problem, and not
 * defining ASPECT.
 */

#define	USEGRAPHICS		/* Only if v1.9 or later imPRESS */
#undef	ASPECT

#ifdef	ASPECT			/* Can't have both! */
#undef	USEGRAPHICS
#endif

#include <stdio.h>
#include <ctype.h>
#include "types.h"
#include "imPcodes.h"

/* Put a two-byte (word) value to the Imagen */
#define putword(w) (putchar((w) >> 8), putchar(w))

extern char *malloc();


#define	TRUE	1
#define	FALSE	0

#define	TWOPI		(3.14157926536*2.0)
#define	MAXPOINTS	300	/* Most number of points in a path */
#define	SPLINEPOINTS	900	/* Most points in a spline */
#define	RADTOPXL	2607.435436	/* (16383 / (2pi)).  This
					converts from radians to the
					angle units used by ImPress */

/* Convert radian angles to 2**-14 angle units used by ImPress */
#define	RadToImpUnits(a)	((short) ((a)*RADTOPXL + 0.5))

/* Graphics operations */
#define	WHITE	0
#define	SHADE	3
#define	OR	7
#define	BLACK	15

extern	double	cos(), sin(), sqrt();
#ifndef	ASPECT
extern	int	DPI;		/* Resolution of device */
#define	PixPerInX	DPI
#define	PixPerInY	DPI
#else
extern	int	DPIx, DPIy;	/* x,y resolution of device */
#define	PixPerInX	DPIx
#define	PixPerInY	DPIy
#endif

extern	int	UserMag;
#define	conv(x, f)\
	((int) ((((double)(x)/1000.0) * ((double)(f)) * ((double)UserMag/1000.0)) + 0.5))
#define	xconv(x)	conv(x, PixPerInX)
#define	yconv(y)	conv(y, PixPerInY)

extern int	ImHH;		/* Imagen horizontal position */
extern int	ImVV;		/* Imagen vertical position */
extern int	hh;		/* current horizontal position, in DEVs */
extern int	vv;		/* current vertical position, in DEVs */

extern	int NextFamilyNumber;	/* Number of next ImPress family to use */
#define	fnum	NextFamilyNumber

static	int xx[MAXPOINTS], yy[MAXPOINTS], pathlen,
	pensize = 2;	/* Size we want Imagen to draw at, default 2 pixels */

#define	MAXPENSIZE 20		/* Imagen restriction */

static int
	family_defined = FALSE,	/* Have we chosen family yet? */
	texture_defined = FALSE,/* Have we done a set_texture yet? */
	whiten_next = FALSE,	/* Should next object be whitened? */
	blacken_next = FALSE,	/* Should next object be blackened? */
	shade_next = FALSE;	/* Should next object be shaded? */

/* Predefined shading (texture) glyph */
/* First, define size of glyph */
#define	THEIGHT	32		/* bits high */
#define	TWIDTH	4		/* bytes wide */
/* Next, declare the bit map for the glyph */
static char stexture[THEIGHT][TWIDTH]={
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}};

/*
 * Copy a default texture into the stexture array
 */
static void glyph_init()
{
	static char btexture[THEIGHT][TWIDTH]={
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00},
	{0xcc, 0xcc, 0xcc, 0xcc}, {0x00, 0x00, 0x00, 0x00},
	{0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00}};

	int i;
	for (i=0; i<THEIGHT; i++)
		bcopy(btexture[i],stexture[i],TWIDTH);

}


/*
 * Push the state of the Imagen and set up a new virtual coord system
 */
static void push_location()
{
    putchar(imP_Push);
    putchar(imP_SetHVSystem);
    putchar(0140);
}


/*
 * Create the pushed virtual page, and pop the state of the printer
 */
static void pop_location()
{
    putchar(imP_Pop);
}


/*
 * Set the pen size
 * Called as \special{pn size}
 *	 eg: \special{pn 8}
 * The size is the number of milli-inches for the diameter of the pen.
 * This routine converts that value to device-dependent pixels, and makes
 * sure that the resulting value is within legal bounds.
 */
static void im_pensize(cp)
char *cp;
{
    int size;
    
    if (sscanf(cp, " %d ", &size) != 1) return;
    pensize = yconv(size);
    if (pensize < 1) pensize = 1;
    else if (pensize > MAXPENSIZE) pensize = MAXPENSIZE;
}


/*
 * Make sure the pen size is set.  Since we push and pop the state,
 * this has to be sent for each different object (I think).
 */
static void set_pen_size()
{
    putchar(imP_SetPen);
    putchar(pensize);
}


/*
 * Actually apply the attributes (shade, whiten, or blacken) to the currently
 * defined path/figure.
 */
static void do_attributes()
{
    static int family;		/* Family of downloaded texture glyph */
    static int member;		/* Member of family */
    int i,j;			/* Loop through glyph array */

    if (shade_next) {
	shade_next = FALSE;
	if (!family_defined) {
	    family_defined = TRUE;
	    family = fnum++;
	    member = -1;
	}
	if (!texture_defined) {
	    texture_defined = TRUE;
	    member++;
	    putchar(imP_DefGlyph);
	    putchar((family & 0x7e) >> 1);
	    putchar((family & 0x01) << 7 | (member & 0x7f));
	    /*putword(0); */		/* Advance width */
	    putword(32);
	    putword(TWIDTH*8);	/* pixel width (8 x number of bytes) */
	    /*putword(0); */		/* left offset */
	    putword(32);
	    putword(THEIGHT);	/* and height of glyph */
	    /*putword(0); */		/* top offset */
	    putword(32);
	    for (i=0; i<THEIGHT; i++)/* Do rows */
		for (j=0; j<TWIDTH; j++) putchar(stexture[i][j]);
	}
	putchar(imP_SetTexture);
	putchar((family & 0x7e) >> 1);
	putchar((family & 0x01) << 7 | (member & 0x7f));
	putchar(imP_FillPath);
	putchar(SHADE);
	glyph_init(); /* reinitialize the array */
    }
    else if (whiten_next) {
	whiten_next = FALSE;
	putchar(imP_FillPath);
	putchar(WHITE);
    }
    else if (blacken_next) {
	blacken_next = FALSE;
	putchar(imP_FillPath);
	putchar(BLACK);
    }
}


/*
 * Flush the path that we've built up with im_drawto()
 * Called as \special{fp}
 */
static void im_flushpath()
{
    register int i;

    push_location();
    if (pathlen <= 0) return;
    set_pen_size();
    putchar(imP_CreatePath);
    putword(pathlen);
    for (i=1; i<=pathlen; i++) {
	putword(xx[i]);
	putword(yy[i]);
    }
    pathlen = 0;
    putchar(imP_DrawPath);
    putchar(BLACK);
    do_attributes();
    pop_location();
}


/* Helper routine for dashed_line() */
static void connect(x0, y0, x1, y1)
int x0, y0, x1, y1;
{
    set_pen_size();
    putchar(imP_CreatePath);
    putword(2);		/* Path length */
    putword(x0);	putword(y0);/* The path */
    putword(x1);	putword(y1);
    putchar(imP_DrawPath);
    putchar(BLACK);
}


/* Another helper.  Draw a dot at the indicated point */
static void dot_at(x, y)
int x,y;
{
    set_pen_size();
    putchar(imP_CreatePath);
    putword(1);			/* Path length */
    putword(x);	putword(y);	/* The path */
    putchar(imP_DrawPath);
    putchar(BLACK);
}


/*
 * Draw a dashed or dotted line between the first pair of points in the array
 * Called as \special{da <inchesperdash>}	(dashed line)
 *	  or \special{dt <inchesperdot>}	(dotted line)
 *	eg:  \special{da 0.05}
 */
static void dashed_line(cp, dotted)
char *cp;
int dotted;			/* boolean */
{
    int i, numdots, x0, y0, x1, y1;
    double cx0, cy0, cx1, cy1;
    double d, spacesize, a, b, dx, dy, pixperdash;
    float inchesperdash;

    if (sscanf(cp, " %f ", &inchesperdash) != 1) return;
    if (pathlen <= 1) return;
    pixperdash = inchesperdash * ((float) PixPerInY);
    x0 = xx[1]; x1 = xx[2];
    y0 = yy[1]; y1 = yy[2];
    dx = x1 - x0;
    dy = y1 - y0;
    push_location();
    if (dotted) {
	numdots = sqrt(dx*dx + dy*dy) / pixperdash + 0.5;
	if (numdots > 0)
	    for (i = 0; i <= numdots; i++) {
		a = (float) i / (float) numdots;
		cx0 = ((float) x0) + (a*dx) + 0.5;
		cy0 = ((float) y0) + (a*dy) + 0.5;
		dot_at((int) cx0, (int) cy0);
	    }
    }
    else {
	d = sqrt(dx*dx + dy*dy);
	if (d <= 2 * pixperdash) {
	    connect(x0, y0, x1, y1);
	    pathlen = 0;
	    pop_location();
	    return;
	}
	numdots = d / (2 * pixperdash) + 1;
	spacesize = (d - numdots * pixperdash) / (numdots - 1);
	for (i=0; i<numdots-1; i++) {
	    a = i * (pixperdash + spacesize) / d;
	    b = a + pixperdash / d;
	    cx0 = ((float) x0) + (a*dx) + 0.5;
	    cy0 = ((float) y0) + (a*dy) + 0.5;
	    cx1 = ((float) x0) + (b*dx) + 0.5;
	    cy1 = ((float) y0) + (b*dy) + 0.5;
	    connect((int) cx0, (int) cy0, (int) cx1, (int) cy1);
	    b += spacesize / d;
	}
	cx0 = ((float) x0) + (b*dx) + 0.5;
	cy0 = ((float) y0) + (b*dy) + 0.5;
	connect((int) cx0, (int) cy0, x1, y1);
    }
    pathlen = 0;
    pop_location();
}


/*
 * Virtually draw to a given x,y position on the virtual page.
 * X and Y are expressed in thousandths of an inch, and this
 * routine converts them to pixels.
 *
 * Called as \special{pa <x> <y>}
 *	 eg:  \special{pa 0 1200}
 */
static void im_drawto(cp)
char *cp;
{
    int x,y;

    if (sscanf(cp, " %d %d ", &x, &y) != 2) return;

    if (++pathlen >= MAXPOINTS)
	error(1, 0, "Too many points specified");
    xx[pathlen] = xconv(x);
    yy[pathlen] = yconv(y);
}

#ifndef	USEGRAPHICS
/*
 * Helper routine for im_arc().
 * Convert x and y to integers, then call im_drawto() normally.
 */
static void im_fdraw(x, y)
float x, y;
{
    int ix,iy;

    ix = (int) x + 0.5;
    iy = (int) y + 0.5;
    im_drawto(ix, iy);
}


/*
 * Draw the indicated arc on the virtual page and flush it.
 * The arc is always drawn counter clockwise from start_angle to end_angle on
 * the virtual page.  That is, clockwise in the real world (since +y is down).
 * It is assumed that start_angle has been adjusted to be in the range
 *	0.0 <= start_angle < 2*PI
 * and that end_angle is the smallest suitable angle >= start_angle.  Thus
 * end_angle MAY be >= 2*PI.
 *
 * Called as \special{ar <xcenter> <ycenter> <xradius> <yradius>
 *			 <startangle> <endangle>}
 *
 * <xcenter>,<ycenter>,<xradius>,<yradius> are in 1/1000's of an inch.
 * <startangle> and <endangle> are in radians.
 *
 *	eg:	\special{ar 240 240 30 30 0.000 6.283}
 */
static void im_arc(cp)
char *cp;
{
    int xc, yc, xrad, yrad;
    float start_angle, end_angle;
    double angle, theta, r, xcenter, ycenter, xradius, yradius;
    int n;

    if (sscanf(cp, " %d %d %d %d %f %f ", &xc, &yc, &xrad, &yrad, &start_angle,
	&end_angle) != 6) return;
    xcenter = xc;		/* Convert to floating point */
    ycenter = yc;
    xradius = xrad;
    yradius = yrad;
    r = (xradius+yradius)/2.0;
    theta = sqrt(1.0 / r);
    n = TWOPI / theta + 0.5;
    if (n<6) n = 6;
    if (n>80) n = 80;
    theta = TWOPI / n;

    im_fdraw( xcenter + xradius*cos(start_angle),
		ycenter + yradius*sin(start_angle) );
    angle = start_angle + theta;
    while (angle < end_angle) {
	im_fdraw(xcenter + xradius*cos(angle),
	    ycenter + yradius*sin(angle) );
	angle += theta;
    }
    im_fdraw( xcenter + xradius*cos(end_angle),
		ycenter + yradius*sin(end_angle) );
    im_flushpath();
}

#else	USEGRAPHICS

/* Same routine as above, but it uses the special graphics primitives */
static void im_arc(cp)
char *cp;
{
    int xc, yc, xrad, yrad;
    float start_angle, end_angle;
    short alpha0, alpha1;

    if (sscanf(cp, " %d %d %d %d %f %f ", &xc, &yc, &xrad, &yrad, &start_angle,
	&end_angle) != 6) return;
    push_location();
    set_pen_size();
    putchar(imP_SetHAbs);
    putword(xconv(xc));
    putchar(imP_SetVAbs);
    putword(yconv(yc));

/*
 * If end_angle > TWOPI, we can't simply use it, since it will be > 16383,
 * and thus an illegal angle.  Simply subtracting TWOPI will make it <
 * start_angle, which will reverse the direction the arc is drawn, resulting
 * in the wrong arc.  So we also exchange start_angle and end_angle.  But then
 * start_angle < end_angle, so the arc goes back to its original direction!
 * So we also subtract TWOPI from the original start_angle, foring end_angle
 * to be negative (since 0<=start_angle<TWOPI originally), which will cause
 * the Imagen to draw in a true CCW direction (opposite of normal).
 */
    if (end_angle > TWOPI) {
	double temp;
	temp = end_angle - TWOPI;
	end_angle = start_angle - TWOPI;
	start_angle = temp;
    }
    if (xrad >= yrad-1 && xrad <= yrad+1) {		/* Circle or arc */
	alpha0 = RadToImpUnits(start_angle);
	alpha1 = RadToImpUnits(end_angle);
	putchar(imP_CircleArc);
	putword(xconv((double) xrad));
	putword(alpha0);
	putword(alpha1);
    }
    else {			/* Ellipse */
	putchar(imP_EllipseArc);
	putword(xconv((double) xrad));
	putword(yconv((double) yrad));
	putword(0);		/* alphaoff */
	putword(0);		/* zero start angle */
	putword(16383);		/* two pi end angle */
    }
    putchar(imP_DrawPath);
    putchar(BLACK);
    do_attributes();
    pop_location();
}
#endif	USEGRAPHICS


/*
 * Create a spline through the points in the array.
 * Called like flush path (fp) command, after points
 * have been defined via pa command(s).
 *
 * eg:	\special{sp}
 */
static void flush_spline()
{
    int xp, yp, N;
    float t1, t2, t3, w;
    int i, j, steps;
    int splinex[SPLINEPOINTS], spliney[SPLINEPOINTS], splinelen;

    push_location();
    set_pen_size();
    putchar(imP_CreatePath);
    splinelen = 0;
    N = pathlen + 1;
    xx[0] = xx[1];
    yy[0] = yy[1];
    xx[N] = xx[N-1];
    yy[N] = yy[N-1];
    for (i = 0; i < N-1; i++) {	/* interval */
	steps = (dist(xx[i],yy[i], xx[i+1],yy[i+1]) +
		dist(xx[i+1],yy[i+1], xx[i+2],yy[i+2])) / 20;
	for (j = 0; j < steps; j++) {	/* points within */
		w = ((float) j) / ((float) steps);
		t1 = 0.5 * w * w;
		w -= 0.5;
		t2 = 0.75 - w * w;
		w -= 0.5;
		t3 = 0.5 * w * w;
		xp = t1 * xx[i+2] + t2 * xx[i+1] + t3 * xx[i] + 0.5;
		yp = t1 * yy[i+2] + t2 * yy[i+1] + t3 * yy[i] + 0.5;
		if (splinelen >= SPLINEPOINTS)
		    error(1, 0, "Too many points in spline");
		splinex[splinelen] = xp;
		spliney[splinelen++] = yp;
	}
    }
    putword(splinelen);
    for (i=0; i<splinelen; i++) {
	putword(splinex[i]);
	putword(spliney[i]);
    }

    pathlen = 0;
    putchar(imP_DrawPath);
    putchar(BLACK);
    pop_location();
}


static int dist(x1, y1, x2, y2)	/* integer distance from x1,y1 to x2,y2 */
{
	float dx, dy;

	dx = x2 - x1;
	dy = y2 - y1;
	return sqrt(dx*dx + dy*dy) + 0.5;
}


/*
 * Whiten the interior of the next figure (path).  Command is:
 *	\special{wh}
 */
static void im_whiten()
{
    whiten_next = TRUE;
}


/*
 * Blacken the interior of the next figure (path).  Command is:
 *	\special{bk}
 */
static void im_blacken()
{
    blacken_next = TRUE;
}


/*
 * Shade the interior of the next figure (path) with the predefined
 * texture.  Command is:
 *	\special{sh}
 */
static void im_shade()
{
    shade_next = TRUE;
}


/*
 * Define the texture array.  Command is:
 *	\special{tx 32bits 32bits ....}
 */
static void im_texture(pcount,bitpattern)
int pcount, bitpattern[32];
{
    int i,j,k;
    unsigned long ul_one;

#ifdef	DEBUG
    if (sizeof ul_one != TWIDTH)
	error(1, 0, "pointer/size mismatch");
#endif
    j = 0;
    for (k=0; k < THEIGHT/pcount; k++) {
	for (i=0; i<pcount; i++) {
	    ul_one = htonl((unsigned long) bitpattern[i]);
	    bcopy((char *) &ul_one, stexture[j++], TWIDTH);
	}
    }
    texture_defined = FALSE;
}


/*
 * This routine takes the string argument for a tx command and
 * parses out the separate bitpatterns to call im_texture with.
 * Written by Tinh Tang
 */
static void do_texture(t)
char *t;
{
    int bitpattern[32];
    int pcount = 0;

    while (isspace (*t)) t++;
    while (*t) {
	if (sscanf(t, "%x", &bitpattern[pcount++]) != 1) {
	    error(0, 0, "malformed tx command");
	    return;
	}
	while (*t && !isspace(*t)) t++;/* Skip to space */
	while (*t && isspace(*t)) t++;/* Skip to nonspace */
    }
    if (pcount != 4 && pcount != 8 && pcount != 16 && pcount != 32) {
	error(0, 0, "malformed tx command");
	return;
    }
    im_texture(pcount, bitpattern);
}

	
#define	COMLEN	3		/* Length of a tpic command plus one */

DoSpecial(k)
	i32 k;
{
    char *spstring, *cp, command[COMLEN];
    register int len;

    spstring = malloc((unsigned) (k+1));
    if (spstring == NULL) error(2, 0, "Out of memory");
    len = 0;
    while (k--) spstring[len++] = GetByte(stdin);
    spstring[len] = '\0';
    cp = spstring;
    while (isspace(*cp)) ++cp;
    len = 0;
    while (!isspace(*cp) && *cp && len < COMLEN-1) command[len++] = *cp++;
    command[len] = '\0';

	if (ImHH != hh || ImVV != vv)
		ImSetPosition(hh, vv);

    if (strcmp(command, "pn") == 0) im_pensize(cp);
    else if (strcmp(command, "fp") == 0) im_flushpath();
    else if (strcmp(command, "da") == 0) dashed_line(cp, 0);
    else if (strcmp(command, "dt") == 0) dashed_line(cp, 1);
    else if (strcmp(command, "pa") == 0) im_drawto(cp);
    else if (strcmp(command, "ar") == 0) im_arc(cp);
    else if (strcmp(command, "sp") == 0) flush_spline();
    else if (strcmp(command, "sh") == 0) im_shade();
    else if (strcmp(command, "wh") == 0) im_whiten();
    else if (strcmp(command, "bk") == 0) im_blacken();
    else if (strcmp(command, "tx") == 0) im_texture(cp);
    else error(0, 0, "warning: ignoring \\special");

    free(spstring);
}