diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/umddvi/dev/imagen1-special.c |
Initial commit
Diffstat (limited to 'dviware/umddvi/dev/imagen1-special.c')
-rw-r--r-- | dviware/umddvi/dev/imagen1-special.c | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/dviware/umddvi/dev/imagen1-special.c b/dviware/umddvi/dev/imagen1-special.c new file mode 100644 index 0000000000..628b26c483 --- /dev/null +++ b/dviware/umddvi/dev/imagen1-special.c @@ -0,0 +1,718 @@ +/* + * 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); +} |