#include	"defs.h"
#include	"global.h"
#include	<math.h>
#include	"emit.h"
#include	"ps.h"

char   *GetKeyStr();
int     GetKeyVal();
int     IsSame();
extern	double	cos(), sin(), sqrt();
static void ps_pensize();
static void ps_flushpath();
static void ps_flushdashedpath();
static void ps_drawto();
static void ps_arc();
static void ps_flushspline();
static int dist();
static void ps_whiten();
static void ps_blacken();
static void ps_shade();
static void push_location(), pop_location();
static void ps_setrgbcolor(), ps_sethsbcolor(), ps_setcmykcolor();
static void draw_line();
static void ps_texture();
static void ps_rotate();

#define	TWOPI		(3.14157926536*2.0)
#define	MAXPOINTS	600	/* Most number of points in a path */
#define	SPLINEPOINTS	5000	/* Most points in a spline */
#define	RADTODEG	(360/(TWOPI))

#define	xconv(x)	((int) ((x) * ( ((float) resolution) / 1000.0) + 0.5))
#define	yconv(y)	((int) ((y) * ( ((float) resolution) / 1000.0) + 0.5))

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

#define	NOFILL	0
#define	BLACK	1
#define	GRAY	2
#define	WHITE	3
static int
/*	texture_defined = FALSE,*//* Have we done a set_texture yet? */
	shade_next = NOFILL,	/* Should next object be shaded? */
	is_rotated = FALSE;

BOOLEAN usesspecial = FALSE;
BOOLEAN usescolor = FALSE;

#define	skipblank(p)	for (; *p == ' '; p++)

struct bangspecial {
    struct bangspecial *bs_next;
    char bs_string[1];
};
static struct bangspecial *bangspecials = NULL;
static struct bangspecial **nextbs = &bangspecials;

/*-->DoSpecial*/
/*********************************************************************/
/*****************************  DoSpecial  ***************************/
/*********************************************************************/

typedef enum {
	None, String, Integer, Number, Dimension
	} ValTyp;

typedef struct {
	char    *Key;           /* the keyword string */
	char    *Val;           /* the value string */
	ValTyp  vt;             /* the value type */
	union {                 /* the decoded value */
	    int  i;
	    float n;
	    } v;
	} KeyWord;

typedef struct {
	char    *Entry;
	ValTyp  Type;
	} KeyDesc;

#define PSFILE 0
#define EPSFILE 1

KeyDesc KeyTab[] = {{"psfile", String},
		    {"epsfile", String},
		    {"hsize", Dimension},
		    {"vsize", Dimension},
		    {"hoffset", Dimension},
		    {"voffset", Dimension},
		    {"hscale", Number},
		    {"vscale", Number},
		    {"rotation", Dimension},
		    {"angle", Number},
		    {"llx", Number},
		    {"lly", Number},
		    {"urx", Number},
		    {"ury", Number},
		    {"rwi", Number},
		    {"rhi", Number},
		    {"clip", None}
		};

#define NKEYS (sizeof(KeyTab)/sizeof(KeyTab[0]))

/* ARGSUSED */
void
dev_predospecial(str, n)
char *str;
int n;
{
    char spbuf[STRSIZE];
    char *sf = NULL;
    char *p, *q;
    struct bangspecial *b;
    KeyWord k;
    int i;

    str[n] = '\0';
    spbuf[0] = '\0';
    p = str;

    skipblank(p);
    if (*p == '\0')
	return;
    usesspecial = TRUE;

    if (*p == '!') {
	b = (struct bangspecial *)
	    alloc_check(malloc((unsigned)sizeof(struct bangspecial)+n-1),
			"bangspecials");
	strncpy(b->bs_string, p+1, n);
	b->bs_next = NULL;
	*nextbs = b;
	nextbs = &(b->bs_next);
	return;
    }
    if (strncmp(p, "landscape", 9) == 0) {
	landscape = TRUE;
	return;
    }
    if (strncmp(p, "papersize", 9) == 0) {
	p += 9;
	while (*p == '=' || *p == ' ')
	    p++;
	/* TODO */
	return;
   }
    if (strncmp(p, "header", 6) == 0) {
	p += 6;
	while ((*p <= ' ' || *p == '=' || *p == '(') && *p != '\0')
	    p++;
	q = p + strlen(p) - 1;
	while ((*q <= ' '|| *q == '(') && p <= q)
	    q--;
	*++q = '\0';
	if (p <= q)
	    ps_add_include(p);
	return;
    }
    if (strncmp(p, "background", 10) == 0) {
	usescolor = TRUE;
	p += 11;
	skipblank(p);
	background(p);
	return;
    }
    if (strncmp(p, "color", 5) == 0) {
	usescolor = TRUE;
	p += 6;
	skipblank(p);
	if (strncmp(p, "push", 4) == 0) {
	    p += 5;
	    skipblank(p);
	    pushcolor(p, FALSE);
	} else if (strncmp(p, "pop", 3) == 0) {
	    popcolor(FALSE);
	} else {
	    resetcolorstack(p, FALSE);
	}
	return;
    }
    if (strncmp(p, "src:", 4) == 0) {
	return;
    }
    if (strncmp(p, "pos:", 4) == 0) {
	return;
    }
    if (strncmp(p, "om:", 3) == 0) {
	return;
    }
    if (strncmp(p, "xtex:", 5) == 0) {
	return;
    }
    if (strncmp(p, "html:", 5) == 0) {
	return;
    }

    while ((p=GetKeyStr(p,&k)) != NULL) {	/* get all keyword-value pairs */
	/* for compatibility, single words are taken as file names */
	if (k.vt == None && access(k.Key,F_OK) == 0) {
/*
	    if (sf)
		Warning("  More than one \\special file name given. %s ignored", sf);
*/
	    (void)strcpy(spbuf, k.Key);
	    sf = spbuf;
	} else if (GetKeyVal(&k, KeyTab, NKEYS, &i) && i != -1) {
	    if (i == PSFILE || i == EPSFILE ) {
/*
		if (sf)
		    Warning("  More than one \\special file name given. %s ignored", sf);
*/
		(void)strcpy(spbuf, k.Val);
		sf = spbuf;
	    }	/* the keywords are ignored */
	} /*else
	    Warning("  Invalid keyword or value in \\special - \"%s\" ignored",
		    k.Key);*/
    }
    if (sf) {
	if (*sf == '`')
	    /* TO DO? */;
	else
	    ps_scanifont(sf);
    } /*else
	Warning("  No special file name provided.");*/
}

dev_outbangspecials()
{
    struct bangspecial *b;

    if (bangspecials != NULL) {
	EMITS("TeXDict begin @defspecial\n");
	for (b = bangspecials; b != NULL; b = b->bs_next) {
	    EMITC('\n');
	    ps_string(b->bs_string);
	    EMITC('\n');
	}
	EMITS("\n@fedspecial end\n");
    }
}

/* interpret a \special command, made up of keyword=value pairs */
void
dev_dospecial(str, n)
char *str;
int n;
{
    char spbuf[STRSIZE];
    char *sf = NULL;
    char *p, *q;
    KeyWord k;
    int i;
    float llx, lly, urx, ury, hsize, vsize;

    str[n] = '\0';
    spbuf[0] = '\0';
    p = str;

    end_string();
    dev_setposn(h, v);

    for (; *p == ' '; p++)
	;
    if (*p == '\0')
	return;

    if (*p == '!')
	return;
    if (strncmp(p, "landscape", 9) == 0)
	return;
    if (strncmp(p, "papersize", 9) == 0)
	return;
    if (strncmp(p, "header", 6) == 0)
	return;
    if (strncmp(p, "ps:", 3) == 0) {
	dev_initfont();
	p += 3;
	if (*p == ':') {
	    p++;
	    if (strncmp(p, "[begin]", 7) == 0) {
		EMITC('\n');
		ps_string(p+7);
		EMITC('\n');
	    } else if (strncmp(p, "[end]", 5) == 0) {
		EMITC('\n');
		ps_string(p+5);
		EMITC('\n');
	    } else {
		EMITC('\n');
		ps_string(p);
		EMITC('\n');
	    }
	} else if (strncmp(p, " plotfile ", 10) == 0) {
	    p += 10;
	    for (; *p == ' '; p++);
	    if (*p == '"')
		for (q = ++p; *q != '\0' && *q != '"'; q++);
	    else
		for (q = p; *q != '\0' && *q != ' '; q++);
	    *q = '\0';
	    if (*p == '`')
		ps_createpipe(++p);
	    else
		ps_copyfigfile(p);
	} else {
	    EMITC('\n');
	    ps_string(p);
	    EMITC('\n');
	    dev_setposn(h, v);
	    dev_initfont();
	}
	return;
    }
    if (*p == '"') {
	EMIT(outfp, "@beginspecial\n");
	EMITS("@setspecial\n");
	EMITS("TeXDict begin\n");
	ps_string(++p);
	EMITS("\nend\n");
	EMITS("@endspecial\n");
	return;
    }
    if (strncmp(p, "pn ", 3) == 0) {
	ps_pensize(p+3);
	return;
    }
    if (strcmp(p, "fp") == 0) {
	ps_flushpath(0);
	return;
    }
    if (strcmp(p, "ip") == 0) {	/* tpic 2.0 */
	ps_flushpath(1);
	return;
    }
    if (strncmp(p, "da ", 3) == 0) {
	ps_flushdashedpath(p+3, 0);
	return;
    }
    if (strncmp(p, "dt ", 3) == 0) {
	ps_flushdashedpath(p+3, 1);
	return;
    }
    if (strncmp(p, "pa ", 3) == 0) {
	ps_drawto(p+3);
	return;
    }
    if (strncmp(p, "ar ", 3) == 0) {	/* tpic 2.0 */
	ps_arc(p+3, 0);
	return;
    }
    if (strncmp(p, "ia ", 3) == 0) {	/* tpic 2.0 */
	ps_arc(p+3, 1);
	return;
    }
    if (strcmp(p, "sp") == 0) {		/* tpic 2.0 */
	ps_flushspline(p+2);
	return;
    }
    if (strncmp(p, "sp ", 3) == 0) {	/* tpic 2.0 */
	ps_flushspline(p+3);
	return;
    }
    if (strcmp(p, "sh") == 0) {		/* tpic 2.0 */
	ps_shade(p+2);
	return;
    }
    if (strncmp(p, "sh", 2) == 0) {	/* tpic 2.0 */
	ps_shade(p+3);
	return;
    }
    if (strcmp(p, "wh") == 0) {
	ps_whiten();
	return;
    }
    if (strcmp(p, "bk") == 0) {
	ps_blacken();
	return;
    }
    if (strncmp(p, "tx ", 3) == 0) {
	ps_texture(p+3);
	return;
    }
    if (strncmp(p, "rt ", 3) == 0) {	/* dviout/prt */
	ps_rotate(p+2);
	return;
    }
    if (strncmp(p, "background", 10) == 0)
	return;
    if (strncmp(p, "color", 5) == 0) {
	p += 6;
	skipblank(p);
	if (strncmp(p, "push", 4) == 0) {
	    p += 5;
	    skipblank(p);
	    pushcolor(p, TRUE);
	} else if (strncmp(p, "pop", 3) == 0) {
	    popcolor(TRUE);
	} else {
	    resetcolorstack(p, TRUE);
	}
	return;
    }
    if (strncmp(p, "RGB", 3) == 0) {	/* eclcolor.sty */
	p += 3;
	if (*p == '=')
	    p++;
	ps_setrgbcolor(p);
	return;
    }
    if (strncmp(p, "HSB", 3) == 0) {	/* eclcolor.sty */
	p += 3;
	if (*p == '=')
	    p++;
	ps_sethsbcolor(p);
	return;
    }
    if (strncmp(p, "CMYK", 4) == 0) {	/* eclcolor.sty */
	p += 4;
	if (*p == '=')
	    p++;
	ps_setcmykcolor(p);
	return;
    }
    if (strncmp(p, "postscriptbox", 13) == 0) { /* for jdvi2kps */
	EMIT(outfp, "@beginspecial\n");
	for (p += 13; *p == ' '; p++)
	    ;
	if (sscanf(p, "{ %fpt }{ %fpt }{ %[^ {}] }", &hsize, &vsize, spbuf) != 3)
	    Warning("  badly formed postscriptbox command - ignored");
	sf = spbuf;
	if (scanfile(sf, &llx, &lly, &urx, &ury)) {
	    EMIT(outfp, "%f %f %f %f false @bbox\n", llx, lly, urx, ury);
	    EMIT(outfp, "%f @hsize\n", hsize*72/72.27);
	    EMIT(outfp, "%f @vsize\n", vsize*72/72.27);
	}
	EMITS("@setspecial\n");
	if (sf) {
	    if (*sf == '`')
		ps_createpipe(++sf);
	    else
		ps_copyfigfile(sf);
	} else
	    Warning("  No special file name provided.");
	EMITS("@endspecial\n");
	return;
    }
    if (strncmp(p, "src:", 4) == 0) {
	return;
    }
    if (strncmp(p, "pos:", 4) == 0) {
	return;
    }
    if (strncmp(p, "om:", 3) == 0) {
	return;
    }
    if (strncmp(p, "xtex:", 5) == 0) {
	return;
    }
    if (strncmp(p, "html:", 5) == 0) {
	return;
    }

    EMIT(outfp, "@beginspecial\n");
    while ((p=GetKeyStr(p,&k)) != NULL) {	/* get all keyword-value pairs */
	/* for compatibility, single words are taken as file names */
	if (k.vt == None && access(k.Key,F_OK) == 0) {
	    if (sf)
		Warning("  More than one \\special file name given. %s ignored", sf);
	    (void)strcpy(spbuf, k.Key);
	    sf = spbuf;
	} else if (GetKeyVal(&k, KeyTab, NKEYS, &i) && i != -1) {
	    if (i == PSFILE || i == EPSFILE ) {
		if (sf)
		    Warning("  More than one \\special file name given. %s ignored", sf);
		(void)strcpy(spbuf, k.Val);
		sf = spbuf;
		if (i == EPSFILE && scanfile(sf, &llx, &lly, &urx, &ury)) {
			EMIT(outfp, "%f %f %f %f true @bbox\n",
			     llx, lly, urx, ury);
			EMIT(outfp, "@ecl\n");
		}
	    } else	/* the keywords are simply output as PS procedure calls */
		EMIT(outfp, "%f @%s\n", k.v.n, KeyTab[i].Entry);
	} else
	    Warning("  Invalid keyword or value in \\special - \"%s\" ignored",
		    k.Key);
    }

    EMITS("@setspecial\n");
    if (sf) {
	if (*sf == '`')
	    ps_createpipe(++sf);
	else
	    ps_copyfigfile(sf);
    } else
	Warning("  No special file name provided.");
    EMITS("@endspecial\n");
    return;
}


/*-->GetKeyStr*/
/**********************************************************************/
/*****************************  GetKeyStr  ****************************/
/**********************************************************************/

/* extract first keyword-value pair from string (value part may be null)
 * return pointer to remainder of string
 * return NULL if none found
 */

char    KeyStr[STRSIZE];
char    ValStr[STRSIZE];

char *GetKeyStr(str, kw)
char    *str;
KeyWord *kw;
{
    char *s, *k, *v, t;

    if (!str)
	return NULL;
    for (s = str ; *s == ' '; s++ )
	;
    if (*s == '\0')
	return NULL;

    /* extract keyword portion */
    for (k = KeyStr; *s != ' ' && *s != '\0' && *s != '='; *k++ = *s++)
	;
    *k = '\0';

    kw->Key = KeyStr;
    kw->Val = v = NULL;
    kw->vt = None;
    for (; *s == ' '; s++)	/* skip over blanks */
	;
    if (*s != '=')		/* look for "=" */
	return s;

    for (s++ ; *s == ' '; s++)		/* skip over blanks */
	;
    if (*s == '\'' || *s == '\"')	/* get string delimiter */
	t = *s++;
    else
	t = ' ';
    /* copy value portion up to delim */
    for (v = ValStr; *s != t && *s != '\0'; *v++ = *s++)
	;
    if (t != ' ' && *s == t)
	s++;
    *v = '\0';
    kw->Val = ValStr;
    kw->vt = String;

    return s;
}


/*-->GetKeyVal*/
/**********************************************************************/
/*****************************  GetKeyVal  ****************************/
/**********************************************************************/

	/* get next keyword-value pair
	 * decode value according to table entry
	 */

int GetKeyVal(kw, tab, nt, tno)
KeyWord *kw;
KeyDesc tab[];
int     nt;
int     *tno;
{
    int i;
    char c = '\0';

    *tno = -1;

    for (i=0; i<nt; i++)
	if (IsSame(kw->Key, tab[i].Entry)) {
	    *tno = i;
	    switch (tab[i].Type) {
	    case None: 
		if (kw->vt != None)
		    return FALSE;
		break;
	    case String:
		if (kw->vt != String)
		    return FALSE;
		break;
	    case Integer:
		if (kw->vt != String)
		    return FALSE;
		if (sscanf(kw->Val,"%d%c", &(kw->v.i), &c) != 1 || c != '\0')
		    return FALSE;
		break;
	    case Number:
	    case Dimension:
		if (kw->vt != String)
		    return FALSE;
		if (sscanf(kw->Val,"%f%c", &(kw->v.n), &c) != 1 || c != '\0')
		    return FALSE;
		break;
	    }
	    kw->vt = tab[i].Type;
	    return TRUE;
	}

    return TRUE;
}

char ToLower(c)
char c;
{
    return (isupper(c)?tolower(c):c);
}

int IsSame(a, b)        /* compare strings, ignore case */
char *a, *b;
{
    for ( ; *a != '\0'; )
	if (ToLower(*a++) != ToLower(*b++))
	    return FALSE;
    return (*a == *b ? TRUE : FALSE);
}

/*-->scanfile*/   /* scan a file */
/*********************************************************************/
/***************************** scanfile ******************************/
/*********************************************************************/
BOOLEAN
scanfile(str, llx, lly, urx, ury)
char    *str;
float	*llx, *lly, *urx, *ury;
{
    FILE *spfp;
    char buffer[BUFSIZ];
    int	i;

    if ((spfp = fopen(str,"r")) == NULL) {
	Warning("Unable to open file %s", str);
	return FALSE;
    }
    while (fgets(buffer, BUFSIZ, spfp) != NULL) {
	i = 0;
	if (buffer[i++] == '%' && buffer[i++] == '%') {
	    if (!strncmp(&buffer[i], "BoundingBox:", 12)) {
		for (; buffer[i++] != ':'; )
		    ;
		for (; buffer[i] == ' '; i++)	/* skip space */
		    ;
		if (buffer[i] != '?' && strncmp(&buffer[i], "(atend)", 7)) {
		    (void)sscanf(&buffer[i], "%f %f %f %f", llx, lly, urx, ury);
		    (void)fclose(spfp);
		    return TRUE;
		}
	    }
	}
    }
    (void)fclose(spfp);
    return FALSE;
}

/*
 * 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
 *
 * PostScript version
 *	Kazuhiro Kazama, NTT Software Laboratories.
 */

/*
 * Save the graphic state and set up a new coordinate system
 */
static void push_location()
{
    EMIT(outfp, "%.3f @push\n", (float)mag/1000);
}

/*
 * Restore the graphic state
 */
static void pop_location()
{
    EMIT(outfp, "@pop\n");
}

/*
 * 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	ps_pensize(cp)
char *cp;
{
    int size;

    if (sscanf(cp, "%d ", &size) != 1) {
	Warning("Illegal format for pn: %s", cp);
	return;
    }
    pensize = xconv(size);
    if (pensize < 0) pensize = 0;
}

/*
 * 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()
{
    EMIT(outfp, "%d setlinewidth\n", pensize);
}

/*
 * Actually apply the attributes (shade, whiten, or blacken) to the currently
 * defined path/figure.
 */
static void do_attributes()
{
    switch (shade_next) {
    case BLACK:
	EMIT(outfp, "0 setgray ");
	break;
    case GRAY:
	EMIT(outfp, "%1.3f setgray ", graylevel);
	break;
    case WHITE:
	EMIT(outfp, "1 setgray ");
	break;
    }
    shade_next = NOFILL;
}

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

    push_location();
    if (pathlen <= 0) return;
    set_pen_size();
    EMIT(outfp, "%d %d p\n", xx[1], yy[1]);
    for (i=2; i<=pathlen; i++) {
	EMIT(outfp, "%d %d l\n", xx[i], yy[i]);
    }
    pathlen = 0;
    if (shade_next == NOFILL)
	EMIT(outfp, "stroke\n");
    else {
	if (invisible) {
	    do_attributes();
	    EMIT(outfp, "fill\n");
	} else {
	    EMIT(outfp, "gsave ");
	    do_attributes();
	    EMIT(outfp, "fill grestore stroke\n");
	}
    }
    pop_location();
}

/*
 * 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	ps_flushdashedpath(cp, dotted)
char *cp;
int dotted;			/* boolean */
{
    int i;
    int	ipd;
    float inchesperdash;

    if (sscanf(cp, "%f ", &inchesperdash) != 1) {
	Warning("Illegal format for dotted/dashed line da/dt: %s", cp);
	return;
    }
    ipd = (int)(1000.0 * inchesperdash + 0.5);
    if (ipd == 0)
	ipd = 1;
    push_location();
    if (pathlen <= 0) return;
    set_pen_size();
    EMIT(outfp, "%d %d p\n", xx[1], yy[1]);
    for (i=2; i<=pathlen; i++) {
	if (dotted) {
	    EMIT(outfp, "1 setlinecap\n");
	    EMIT(outfp, "[%d %d] 0 setdash\n",
		 pensize, (int)xconv((ipd)) - pensize);
	} else {
	    EMIT(outfp, "[%d] 0 setdash\n", (int)xconv(ipd));
	}
	EMIT(outfp, "%d %d l\n", xx[i], yy[i]);
    }
    pathlen = 0;
    if (shade_next == NOFILL)
	EMIT(outfp, "stroke\n");
    else {
	EMIT(outfp, "gsave ");
	do_attributes();
	EMIT(outfp, "fill grestore stroke\n");
    }
    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	ps_drawto(cp)
char *cp;
{
    int x,y;

    if (sscanf(cp, "%d %d ", &x, &y) != 2) {
	Warning("Illegal format for pa: %s", cp);
	return;
    }
    if (++pathlen >= MAXPOINTS)
	Fatal("Too many points specified\n", 0);
    xx[pathlen] = xconv(x);
    yy[pathlen] = yconv(y);
}

static void	ps_arc(cp, invisible)
char *cp;
int invisible;
{
    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) {
	Warning("Illegal format for arc: %s", cp);
	return;
    }
    push_location();
    set_pen_size();

    if (start_angle < 0.0)	/* for gpic */
	start_angle += TWOPI;
    if (end_angle < 0.0)
	end_angle += TWOPI;
    alpha0 = start_angle * RADTODEG + 0.5;
    alpha1 = end_angle * RADTODEG + 0.5;
    EMIT(outfp, "%d %d %d %d %d %d @ar\n",
	 xconv(xc), yconv(yc), xconv((double) xrad), yconv((double) yrad),
	 alpha0, alpha1);
    if (shade_next == NOFILL)
	EMIT(outfp, "stroke\n");
    else {
	if (invisible) {
	    do_attributes();
	    EMIT(outfp, "fill\n");
	} else {
	    EMIT(outfp, "gsave ");
	    do_attributes();
	    EMIT(outfp, "fill grestore stroke\n");
	}
    }
    pop_location();
}

/*
 * 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}
 */
int splinex[SPLINEPOINTS], spliney[SPLINEPOINTS], splinelen;
static void ps_flushspline(cp)
char	*cp;
{
    int xp, yp, N;
    double t1, t2, t3, w;
    int i, j, steps;

    push_location();
    set_pen_size();
    if (*cp) {
	float	f;
	int	ipd;

	(void) sscanf(cp, "%f", &f);
	ipd = (int)(1000.0 * (f > 0 ? f : -f) + 0.5);
	if (ipd == 0)
	    ipd  = 1;
	if (f < 0.0) {	/* dotted */
	    EMIT(outfp, "1 setlinecap\n");
	    EMIT(outfp, "[%d %d] 0 setdash\n",
		 pensize, (int)xconv((ipd)) - pensize);
	} else if (f > 0.0)	/* dashed */
	    EMIT(outfp, "[%d] 0 setdash\n", (int)xconv(ipd));
    }
    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 = ((double) j + 0.5) / ((double) 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)
		    Fatal("Too many points in spline\n", 0);
		splinex[splinelen] = xp;
		spliney[splinelen++] = yp;
	}
    }
    EMIT(outfp, "%d %d p\n", splinex[0], spliney[0]);
    for (i=1; i<splinelen; i++) {
	EMIT(outfp, "%d %d l\n", splinex[i], spliney[i]);
    }

    pathlen = 0;
    if (shade_next == NOFILL)
	EMIT(outfp, "stroke\n");
    else {
	EMIT(outfp, "gsave ");
	do_attributes();
	EMIT(outfp, "fill grestore stroke\n");
    }
    pop_location();
}

static int dist(x1, y1, x2, y2)	/* integer distance from x1,y1 to x2,y2 */
{
    double 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 ps_whiten()
{
    shade_next = WHITE;
}

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

/*
 * Shade the interior of the next figure (path) with the predefined
 * texture.  Command is:
 *	\special{sh}
 */
static void ps_shade(cp)
char	*cp;
{
    if (*cp) {
	float     	f;

	if (sscanf(cp, "%f", &f) != 1) {
	    Warning("Illegal format for shading value sh: %s", cp);
	    return;
	}
	if (f >= 0.0 && f <= 1.0)
	    graylevel = 1.0 - f;
    } else
	graylevel = 0.5;
    shade_next = GRAY;
}

static void ps_texture(cp)
char	*cp;
{
    int blackbits = 0, totalbits = 0;

    while (*cp) {
	switch (*cp) {
	case '0':
	    totalbits += 4;
	    break;
	case '1':
	case '2':
	case '4':
	case '8':
	    blackbits++;
	    totalbits += 4;
	    break;
	case '3':
	case '5':
	case '6':
	case '9':
	case 'a':
	case 'A':
	case 'c':
	case 'C':
	    blackbits += 2;
	    totalbits += 4;
	    break;
	case '7':
	case 'b':
	case 'B':
	case 'd':
	case 'D':
	case 'e':
	case 'E':
	    blackbits += 3;
	    totalbits += 4;
	    break;
	case 'f':
	case 'F':
	    blackbits += 4;
	    totalbits += 4;
	    break;
	case ' ':
	    break;
	default:
	    /* error */
	    break;
	}
	cp++;
    }
    graylevel = 1.0 - ((double) blackbits / (double) totalbits);
    shade_next = GRAY;
}

static void ps_rotate(cp)
char	*cp;
{
    int	x, y;
    float     	angle;

    if (sscanf(cp, "%d %d %f", &x, &y, &angle) != 3) {
	Warning("Illegal format for rt: %s", cp);
	return;
    }
    dev_initfont();
    if (angle == 0 && is_rotated) {
	EMIT(outfp, "currentpoint grestore moveto\n");
	is_rotated = FALSE;
    } else {
	if (is_rotated)
	    EMIT(outfp, "currentpoint grestore moveto\n");
	else
	    is_rotated = TRUE;
	EMIT(outfp, "gsave currentpoint %d add exch %d add exch 2 copy\n",
	     yconv(y), xconv(x));
	EMIT(outfp, "translate %f rotate neg exch neg exch translate\n",
	     angle * RADTODEG);
    }
}

static void ps_setrgbcolor(cp)
char *cp;
{
    float r, g, b;

    (void)sscanf(cp, "%f %f %f", &r, &g, &b);
    EMIT(outfp, "%f %f %f @RGB\n", r, g, b);
}

static void ps_sethsbcolor(cp)
char *cp;
{
    float h, s, b;

    (void)sscanf(cp, "%f %f %f", &h, &s, &b);
    EMIT(outfp, "%f %f %f @HSB\n", h, s, b);
}

static void ps_setcmykcolor(cp)
char *cp;
{
    float c, m, y, k;

    (void)sscanf(cp, "%f %f %f %f", &c, &m, &y, &k);
    EMIT(outfp, "%f %f %f %f @CMYK\n", c, m, y, k);
}
