/*
 * Electric(tm) VLSI Design System
 *
 * File: iopsout.c
 * Input/output analysis tool: PostScript generation
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

/*
 * Rules used in creating the PostScript file.
 *
 * 1) Plain PostScript - when using an area-highlight objects completely
 *                         outside the highlight area are not printed
 *                     - file created with a '.ps' extension
 *
 * 2) Encapsulated PostScript - when using an area-highlight the image clip
 *                                path is set to the highlight area size
 *                            - file created with a '.eps' extension
 *
 * The rules for EPS are such because in most cases the EPS file will be used
 * inside a publishing package.
 */

#include "config.h"
#include "global.h"
#include "egraphics.h"
#include "eio.h"
#include "usr.h"
#include "edialogs.h"

/* #define CLIP 1 */		/* set if the bounding box is used as the clip path, for EPS only */
/* #define DEBUGHIERPS 1 */	/* uncomment to debug hierarchical PostScript (shows memory usage) */

#define PSSCALE             4
#define CORNERDATESIZE     14			/* size of date information in corner */

#define PSHEADERDOT         1			/* write macros for dot drawing */
#define PSHEADERLINE        2			/* write macros for line drawing */
#define PSHEADERPOLYGON     3			/* write macros for polygon drawing */
#define PSHEADERFPOLYGON    4			/* write macros for filled polygon drawing */
#define PSHEADERSTRING      5			/* write macros for text drawing */

static FILE      *io_psout;
static WINDOWPART io_pswindow;
static INTBIG     io_whichlayer, io_maxpslayer;
static XARRAY     io_psmatrix = {{0,0,0},{0,0,0},{0,0,0}};
static GRAPHICS **io_psgraphicsseen;
static INTBIG     io_psgraphicsseenlimit = 0;
static INTBIG     io_psgraphicsseencount;
static BOOLEAN    io_psusecolor;			/* nonzero to use color PostScript */
static BOOLEAN    io_psusecolorstip;		/* nonzero to use stippled color PostScript */
static BOOLEAN    io_psdotput;				/* PostScript for dot put out */
static BOOLEAN    io_psdrawlineput;			/* PostScript for line put out */
static BOOLEAN    io_pspolygonput;			/* PostScript for polygon put out */
static BOOLEAN    io_psfilledpolygonput;	/* PostScript for filled polygon put out */
static BOOLEAN    io_psstringput;			/* PostScript for strings put out */
static BOOLEAN    io_psfake;				/* nonzero if faking output (just counting) */
static INTBIG    *io_redmap, *io_greenmap, *io_bluemap;
static INTBIG     io_lastred, io_lastgreen, io_lastblue;
static INTBIG     io_pspolygoncount;		/* number of polygons in job so far */
static INTBIG     io_pspolygontotal;		/* number of polygons in job */
static INTBIG     io_psopcount;				/* chunks of code */
static char       io_psprefix[10];			/* indentation for PostScript */

static GRAPHICS io_psblack = {LAYERO, BLACK, SOLIDC, SOLIDC,
	{0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF}, NOVARIABLE, 0};

/* prototypes for local routines */
static void   io_psarc(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
static void   io_pscircle(INTBIG, INTBIG, INTBIG, INTBIG);
static void   io_psdisc(INTBIG, INTBIG, INTBIG, INTBIG, GRAPHICS*);
static void   io_psdot(INTBIG, INTBIG);
static void   io_psdumpcontents(NODEPROTO *np);
static void   io_psdumpfacets(NODEPROTO *np);
static char  *io_psfacetname(NODEPROTO *np);
static void   io_psline(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
static char   io_pspattern(GRAPHICS*);
static void   io_pspoly(POLYGON*, WINDOWPART*);
static void   io_pspolycount(POLYGON*, WINDOWPART*);
static void   io_pspolygon(INTBIG*, INTBIG*, INTBIG, GRAPHICS*);
static void   io_psputheader(INTBIG which);
static void   io_pssendfiletoprinter(char *file);
static void   io_pstext(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, char*, UINTBIG*);
static INTBIG io_pstruefontsize(INTBIG font, WINDOWPART *w, TECHNOLOGY *tech);
static void   io_pswrite(char *s, ...);
static BOOLEAN io_pswritefacet(NODEPROTO*, char*, INTBIG);
static void   io_pswritestring(char*);
static void   io_psxform(INTBIG, INTBIG, INTBIG*, INTBIG*);

/*
 * Routine to free all memory associated with this module.
 */
void io_freepostscriptmemory(void)
{
	if (io_psgraphicsseenlimit != 0) efree((char *)io_psgraphicsseen);
}

/*
 * Routine to write out a PostScript library.
 * Actually prints the document if "printit" is nonzero.
 */
BOOLEAN io_writepostscriptlibrary(LIBRARY *lib, INTBIG printit)
{
	char *par[MAXPARS];
	REGISTER INTBIG i, numsyncs, synchother;
	REGISTER NODEPROTO *np;
	REGISTER LIBRARY *olib;
	REGISTER VARIABLE *var, *vardate;
	REGISTER UINTBIG mdate;
	extern COMCOMP us_yesnop;

	/* see if there are synchronization links */
	synchother = 0;
	if (printit == 0)
	{
		for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
			for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
		{
			var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING, io_postscriptfilenamekey);
			if (var != NOVARIABLE && np != lib->curnodeproto) synchother++;
		}
		if (synchother != 0)
		{
			i = ttygetparam(_("Would you like to synchronize all PostScript drawings?"),
				&us_yesnop, MAXPARS, par);
			if (i > 0 && namesamen(par[0], "no", strlen(par[0])) == 0)
				synchother = 0;
		}
	}

	np = lib->curnodeproto;
	if (np == NONODEPROTO && synchother == 0)
	{
		ttyputerr(_("No current facet to plot"));
		return(TRUE);
	}

	/* just write the current facet if no synchronization needed */
	if (synchother == 0)
		return(io_pswritefacet(np, 0, printit));

	/* synchronize all facets */
	numsyncs = 0;
	for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
		for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING, io_postscriptfilenamekey);
		if (var == NOVARIABLE) continue;

		/* existing file: check the date to see if it should be overwritten */
		vardate = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, io_postscriptfiledatekey);
		if (vardate != NOVARIABLE)
		{
			mdate = (UINTBIG)vardate->addr;
			if (np->revisiondate <= mdate)
			{
				/*
				 * facet revision date is not newer than last PostScript output date
				 * ignore writing this file if it already exists
				 */
				if (fileexistence((char *)var->addr) == 1) continue;
			}
		}
		if (io_pswritefacet(np, (char *)var->addr, 0)) return(TRUE);
		numsyncs++;

		/* this is tricky: because the "setvalkey" modifies the facet, the date must be set by hand */
		(void)setvalkey((INTBIG)np, VNODEPROTO, io_postscriptfiledatekey,
			(INTBIG)np->revisiondate, VINTEGER);
		vardate = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, io_postscriptfiledatekey);
		if (vardate != NOVARIABLE)
			vardate->addr = (INTBIG)np->revisiondate;
	}
	if (numsyncs == 0)
		ttyputmsg(_("No PostScript files needed to be written"));

	return(FALSE);
}

/*
 * Routine to write the facet "np" to a PostScript file.  If "synchronize" is nonzero,
 * synchronize the facet with that file.  If "printit" is nonzero, queue this for print.
 */
BOOLEAN io_pswritefacet(NODEPROTO *np, char *synchronize, INTBIG printit)
{
	char file[100], *truename, *ptr;
	INTBIG lx, hx, ly, hy, gridlx, gridly, gridx, gridy,
		cx, cy, i, j, psulx, psuhx, psuly, psuhy, sulx, suhx,
		suly, suhy, sslx, sshx, ssly, sshy, oldoptions;
	REGISTER INTBIG pagewid, pagehei, pagemarginps, pagemargin, *curstate, lambda;
	REGISTER BOOLEAN *layarr;
	REGISTER BOOLEAN plotdates, rotateplot, useplotter, hierpostscript, epsformat;
	INTBIG bblx, bbhx, bbly, bbhy, t1, t2, unitsx, unitsy;
	UINTBIG curdate;
	REGISTER NODEPROTO *onp;
	REGISTER LIBRARY *lib;
	VARIABLE *var;
	REGISTER VARIABLE *varred, *vargreen, *varblue;
	static POLYGON *poly = NOPOLYGON;
	extern DIALOG us_progressdialog;

	/* cannot write text-only facets */
	if ((np->cellview->viewstate&TEXTVIEW) != 0)
	{
		if (printit == 0)
		{
			ttyputerr(_("Cannot write textual facets"));
			return(TRUE);
		}

		/* print a textual facet */
		strcpy(file, "/tmp/ElectricPSOut.XXXXXX");
		emaketemp(file);
		io_psout = xcreate(file, io_filetypeps, 0, &truename);
		if (io_psout == NULL)
		{
			ttyputerr(_("Cannot write temporary file %s"), file);
			return(TRUE);
		}

		/* print the header */
		(void)initinfstr();
		(void)formatinfstr(_("Library: %s   Facet: %s\n"), np->cell->lib->libname,
			describenodeproto(np));
		xprintf(io_psout, returninfstr());

		if ((us_useroptions&NODATEORVERSION) == 0)
		{
			(void)initinfstr();
			if (np->creationdate != 0)
				(void)formatinfstr(_("Created: %s"), timetostring(np->creationdate));
			if (np->revisiondate != 0)
				(void)formatinfstr(_("   Revised: %s"), timetostring(np->revisiondate));
			xprintf(io_psout,  returninfstr());
		}
		xprintf(io_psout, "\n\n\n");

		/* print the text of the facet */
		var = getvalkey((INTBIG)np, VNODEPROTO, VSTRING|VISARRAY, el_facet_message_key);
		if (var != NOVARIABLE)
		{
			j = getlength(var);
			for(i=0; i<j; i++)
			{
				ptr = ((char **)var->addr)[i];
				xprintf(io_psout, "%s\n", ptr);
			}
		}
		xclose(io_psout);
		io_pssendfiletoprinter(file);
		return(FALSE);
	}

	/* clear flags that tell whether headers have been included */
	io_psdotput = FALSE;
	io_psdrawlineput = FALSE;
	io_pspolygonput = FALSE;
	io_psfilledpolygonput = FALSE;
	io_psstringput = FALSE;

	/* get control options */
	io_psusecolor = FALSE;
	io_psusecolorstip = FALSE;
	epsformat = FALSE;
	hierpostscript = useplotter = rotateplot = plotdates = FALSE;
	io_psfake = FALSE;
	curstate = io_getstatebits();
	if ((curstate[0]&PSCOLOR) != 0)
	{
		io_psusecolor = TRUE;
		if ((curstate[0]&PSCOLORSTIP) != 0) io_psusecolorstip = TRUE;
	}
	if ((curstate[0]&EPSPSCRIPT) != 0) epsformat = TRUE;
	if ((curstate[0]&PSHIERARCHICAL) != 0) hierpostscript = TRUE;
	if ((curstate[0]&PSPLOTTER) != 0) useplotter = TRUE;
	if ((curstate[0]&PSROTATE) != 0) rotateplot = TRUE;
	if ((curstate[0]&PLOTDATES) != 0) plotdates = TRUE;
	if (printit != 0) epsformat = FALSE;

	var = getval((INTBIG)io_tool, VTOOL, VFRACT, "IO_postscript_width");
	if (var == NOVARIABLE) pagewid = DEFAULTPSWIDTH; else
		pagewid = muldiv(var->addr, 75, WHOLE);
	var = getval((INTBIG)io_tool, VTOOL, VFRACT, "IO_postscript_height");
	if (var == NOVARIABLE) pagehei = DEFAULTPSHEIGHT; else
		pagehei = muldiv(var->addr, 75, WHOLE);
	var = getval((INTBIG)io_tool, VTOOL, VFRACT, "IO_postscript_margin");
	if (var == NOVARIABLE)
	{
		pagemarginps = DEFAULTPSMARGIN;
		pagemargin = muldiv(DEFAULTPSMARGIN, WHOLE, 75);
	} else
	{
		pagemarginps = muldiv(var->addr, 75, WHOLE);
		pagemargin = var->addr;
	}

	/* cache color maps if using color */
	if (io_psusecolor)
	{
		varred = getval((INTBIG)us_tool, VTOOL, VINTEGER|VISARRAY, "USER_colormap_red");
		vargreen = getval((INTBIG)us_tool, VTOOL, VINTEGER|VISARRAY, "USER_colormap_green");
		varblue = getval((INTBIG)us_tool, VTOOL, VINTEGER|VISARRAY, "USER_colormap_blue");
		if (varred == NOVARIABLE || vargreen == NOVARIABLE || varblue == NOVARIABLE)
		{
			ttyputmsg(_("Cannot get colors!"));
			return(TRUE);
		}
		io_redmap = (INTBIG *)varred->addr;
		io_greenmap = (INTBIG *)vargreen->addr;
		io_bluemap = (INTBIG *)varblue->addr;
	}

	/* determine the area of interest */
	if (io_getareatoprint(np, &lx, &hx, &ly, &hy, FALSE)) return(TRUE);

	/* if plotting, compute height from width */
	if (useplotter)
	{
		if (rotateplot)
		{
			pagehei = muldiv(pagewid, hx-lx, hy-ly);
		} else
		{
			pagehei = muldiv(pagewid, hy-ly, hx-lx);
		}
	}

	/* create the PostScript file */
	if (printit != 0)
	{
		strcpy(file, "/tmp/ElectricPSOut.XXXXXX");
		emaketemp(file);
		io_psout = xcreate(file, io_filetypeps, 0, &truename);
		if (io_psout == NULL)
		{
			ttyputerr(_("Cannot write temporary file %s"), file);
			return(TRUE);
		}
	} else if (synchronize != 0)
	{
		(void)sprintf(file, "%s", synchronize);
		io_psout = xcreate(file, io_filetypeps, 0, &truename);
		if (io_psout == NULL)
		{
			ttyputerr(_("Cannot synchronize facet %s with file %s"),
				describenodeproto(np), file);
			return(TRUE);
		}
	} else
	{
		(void)sprintf(file, "%s.%s", np->cell->cellname, epsformat ? "eps" : "ps");
		io_psout = xcreate(file, io_filetypeps, _("PostScript File"), &truename);
		if (io_psout == NULL)
		{
			if (truename != 0) ttyputerr(_("Cannot create %s"), truename);
			return(TRUE);
		}
	}

	if (io_verbose < 0)
	{
		if (DiaInitDialog(&us_progressdialog)) return(TRUE);
		DiaPercent(1, 0);
		DiaSetText(2, _("Preparing PostScript Output..."));
	}

	/* build pseudowindow for text scaling */
	psulx = psuly = 0;
	psuhx = psuhy = 1000;
	io_pswindow.uselx = psulx;
	io_pswindow.usely = psuly;
	io_pswindow.usehx = psuhx;
	io_pswindow.usehy = psuhy;
	io_pswindow.screenlx = lx;
	io_pswindow.screenhx = hx;
	io_pswindow.screenly = ly;
	io_pswindow.screenhy = hy;
	io_pswindow.state = DISPWINDOW;
	computewindowscale(&io_pswindow);

	/* set the bounding box in internal units */
	bblx = lx;  bbhx = hx;  bbly = ly;  bbhy = hy;

	/* PostScript: compute the transformation matrix */
	cx = (hx + lx) / 2;
	cy = (hy + ly) / 2;
	unitsx = (pagewid-pagemarginps*2) * PSSCALE;
	unitsy = (pagehei-pagemarginps*2) * PSSCALE;
	if (epsformat)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VFRACT, io_postscriptepsscalekey);
		if (var != NOVARIABLE)
		{
			unitsx = muldiv(unitsx, var->addr, WHOLE*2);
			unitsy = muldiv(unitsy, var->addr, WHOLE*2);
		}
	}
	if (useplotter)
	{
		i = muldiv(unitsx, 65536, hx - lx);
		j = muldiv(unitsx, 65536, hy - ly);
	} else
	{
		i = mini(muldiv(unitsx, 65536, hx - lx), muldiv(unitsy, 65536, hy - ly));
		j = mini(muldiv(unitsx, 65536, hy - ly), muldiv(unitsy, 65536, hx - lx));
	}
	if (rotateplot) i = j;
	io_psmatrix[0][0] = i;   io_psmatrix[0][1] = 0;
	io_psmatrix[1][0] = 0;   io_psmatrix[1][1] = i;
	io_psmatrix[2][0] = - muldiv(i, cx, 65536) + unitsx / 2 + pagemarginps * PSSCALE;
	if (useplotter)
	{
		io_psmatrix[2][1] = - muldiv(i, ly, 65536) + pagemarginps * PSSCALE;
	} else
	{
		io_psmatrix[2][1] = - muldiv(i, cy, 65536) + unitsy / 2 + pagemarginps * PSSCALE;
	}

	/* write PostScript header */
	if (epsformat) io_pswrite("%%!PS-Adobe-2.0 EPSF-2.0\n"); else
		io_pswrite("%%!PS-Adobe-1.0\n");
	io_pswrite("%%%%Title: %s\n", describenodeproto(np));
	if ((us_useroptions&NODATEORVERSION) == 0)
	{
		io_pswrite("%%%%Creator: Electric VLSI Design System version %s\n", el_version);
		curdate = getcurrenttime();
		io_pswrite("%%%%CreationDate: %s\n", timetostring(curdate));
	} else
	{
		io_pswrite("%%%%Creator: Electric VLSI Design System\n");
	}
	if (epsformat) io_pswrite("%%%%Pages: 0\n"); else
		io_pswrite("%%%%Pages: 1\n");

	/* transform to PostScript units */
	io_psxform(bblx, bbly, &bblx, &bbly);
	io_psxform(bbhx, bbhy, &bbhx, &bbhy);

	if (rotateplot)
	{
		/*
		 * fiddle with the bbox if image rotated on page
		 * (at this point, bbox coordinates are absolute printer units)
		 */
		t1 = bblx;
		t2 = bbhx;
		bblx = -bbhy + muldiv(pagehei, 300, 75);
		bbhx = -bbly + muldiv(pagehei, 300, 75);
		bbly = t1 + muldiv(pagemargin*2, 300, 75);
		bbhy = t2 + muldiv(pagemargin*2, 300, 75);
	}

	if (bblx > bbhx) { i = bblx;  bblx = bbhx;  bbhx = i; }
	if (bbly > bbhy) { i = bbly;  bbly = bbhy;  bbhy = i; }
	bblx = roundfloat(((float)bblx) / (PSSCALE * 75.0f) * 72.0f) * (bblx>=0 ? 1 : -1);
	bbly = roundfloat(((float)bbly) / (PSSCALE * 75.0f) * 72.0f) * (bbly>=0 ? 1 : -1);
	bbhx = roundfloat(((float)bbhx) / (PSSCALE * 75.0f) * 72.0f) * (bbhx>=0 ? 1 : -1);
	bbhy = roundfloat(((float)bbhy) / (PSSCALE * 75.0f) * 72.0f) * (bbhy>=0 ? 1 : -1);

	/*
	 * Increase the size of the bbox by one "pixel" to
	 * prevent the edges from being obscured by some drawing tools
	 */
	io_pswrite("%%%%BoundingBox: %ld %ld %ld %ld\n", bblx-1, bbly-1, bbhx+1, bbhy+1);
	io_pswrite("%%%%DocumentFonts: Times-Roman\n");
	io_pswrite("%%%%EndComments\n");

	/* PostScript: add some debugging info */
	if (np != NONODEPROTO)
	{
		io_pswrite("%% facet dimensions: %ld wide x %ld high (database units)\n",
			np->highx-np->lowx, np->highy-np->lowy);
		io_pswrite("%% origin: %ld %ld\n", np->lowx, np->lowy);
	}

	/* disclaimers */
	if (epsformat)
	{
		io_pswrite("%% The EPS header should declare a private dictionary.\n");
	} else
	{
		io_pswrite("%% The non-EPS header does not claim conformance to Adobe-2.0\n");
		io_pswrite("%% because the structure may not be exactly correct.\n");
	}
	io_pswrite("%%\n");

	/* set the page size if this is a plotter */
	if (useplotter)
	{
		io_pswrite("<< /PageSize [%ld %ld] >> setpagedevice\n",
			muldiv(pagewid, 72, 75), muldiv(pagehei, 72, 75));
	}

	/* make the scale be exactly equal to one page pixel */
	io_pswrite("72 %ld div 72 %ld div scale\n", PSSCALE*75, PSSCALE*75);

	/* PostScript: set the proper typeface */
	io_pswrite("/DefaultFont /Times-Roman def\n");
	io_pswrite("/scaleFont {\n");
	io_pswrite("    DefaultFont findfont\n");
	io_pswrite("    exch scalefont setfont} def\n");

	/* PostScript: make the line width proper */
	io_pswrite("%ld setlinewidth\n", PSSCALE/2);

	/* PostScript: make the line ends look right */
	io_pswrite("1 setlinecap\n");

	if (epsformat)
	{
		/* EMPTY */
		/* EPS: set the clip path to be equal to the bounding box */
#ifdef CLIP
		io_pswrite("gsave\n");
		io_pswrite("newpath\n");
		io_pswrite("%ld %ld moveto %ld %ld lineto %ld %ld lineto %ld %ld lineto\n",
			bblx+t-1, bbly+t-1, bbhx+t+1, bbly+t-1, bbhx+t+1, bbhy+t+1, bblx+t-1, bbhy+t+1);
		io_pswrite("closepath\n");
		io_pswrite("clip\n");
		io_pswrite("newpath\n");
#endif
	} else
	{
		/* PostScript: rotate the image if requested */
		if (rotateplot)
		{
			if (useplotter)
			{
				io_pswrite("%s 300 mul %s 300 mul translate 90 rotate\n",
					frtoa(muldiv(pagewid, WHOLE, 75)),
					frtoa(muldiv((pagehei-pagewid)/2, WHOLE, 75)));
			} else
			{
				io_pswrite("%s 300 mul %s 300 mul translate 90 rotate\n",
					frtoa(muldiv((pagehei+pagewid)/2, WHOLE, 75)),
					frtoa(muldiv((pagehei-pagewid)/2, WHOLE, 75)));
			}
		}
	}

	/* initialize list of GRAPHICS modules that have been put out */
	io_psgraphicsseencount = 0;

	/* plot everything */
	sulx = el_curwindowpart->uselx;      suhx = el_curwindowpart->usehx;
	suly = el_curwindowpart->usely;      suhy = el_curwindowpart->usehy;
	sslx = el_curwindowpart->screenlx;   sshx = el_curwindowpart->screenhx;
	ssly = el_curwindowpart->screenly;   sshy = el_curwindowpart->screenhy;
	el_curwindowpart->uselx = io_pswindow.uselx;
	el_curwindowpart->usehx = io_pswindow.usehx;
	el_curwindowpart->usely = io_pswindow.usely;
	el_curwindowpart->usehy = io_pswindow.usehy;
	el_curwindowpart->screenlx = io_pswindow.screenlx;
	el_curwindowpart->screenhx = io_pswindow.screenhx;
	el_curwindowpart->screenly = io_pswindow.screenly;
	el_curwindowpart->screenhy = io_pswindow.screenhy;
	computewindowscale(el_curwindowpart);
	io_lastred = io_lastgreen = io_lastblue = -1;

	if (!hierpostscript)
	{
		/* flat PostScript: disable "tiny facet" removal */
		oldoptions = us_useroptions;
		us_useroptions |= DRAWTINYFACETS;

		/* count the number of polygons in the job */
		io_pspolygoncount = 0;
		if (io_verbose < 0)
		{
			(void)asktool(us_tool, "display-to-routine", io_pspolycount);
			io_pspolygontotal = io_pspolygoncount;
			if (io_pspolygontotal == 0) io_pspolygontotal = 1;
			DiaSetText(2, _("Writing PostScript..."));
		}
		io_pspolygoncount = 0;

		io_psprefix[0] = 0;
		if (io_psusecolor)
		{
			/* color: plot layers in proper order */
			io_maxpslayer = io_setuptechorder(el_curtech);
			io_pspolygontotal *= (io_maxpslayer+1);
			for(i=0; i<io_maxpslayer; i++)
			{
				if (stopping(STOPREASONDECK)) break;
				io_whichlayer = io_nextplotlayer(i) + 1;
				(void)asktool(us_tool, "display-to-routine", io_pspoly);
			}
			if (stopping(STOPREASONDECK))
			{
				xclose(io_psout);
				return(FALSE);
			}
			io_whichlayer = 0;
			(void)asktool(us_tool, "display-to-routine", io_pspoly);
		} else
		{
			/* gray-scale: just plot it once */
			io_whichlayer = -1;
			(void)asktool(us_tool, "display-to-routine", io_pspoly);
		}

		/* restore "tiny facet" removal option */
		us_useroptions = oldoptions;
	} else
	{
		/* hierarchical PostScript: figure out which layers are there */
		if (io_psusecolor)
		{
			/* color: setup proper order for plotting */
			io_maxpslayer = io_setuptechorder(el_curtech);

			for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
			{
				for(onp = lib->firstnodeproto; onp != NONODEPROTO; onp = onp->nextnodeproto)
				{
					onp->temp1 = 0;
					layarr = (BOOLEAN *)emalloc((io_maxpslayer+1) * (sizeof (BOOLEAN)),
						io_tool->cluster);
					if (layarr == 0) return(TRUE);
					for(i=0; i<=io_maxpslayer; i++) layarr[i] = FALSE;
					onp->temp2 = (INTBIG)layarr;
				}
			}
		}

		/* count the number of symbols used by the code */
		io_psopcount = 0;
		io_psfake = TRUE;
		io_psdumpfacets(el_curwindowpart->curnodeproto);
		io_psfake = FALSE;
		io_psgraphicsseencount = 0;

		/* clear flags of which facets have been written */
		for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
			for(onp = lib->firstnodeproto; onp != NONODEPROTO; onp = onp->nextnodeproto)
				onp->temp1 = 0;

		if (io_verbose < 0)
			DiaSetText(2, _("Writing PostScript..."));
		io_pspolygontotal = 0;

#ifdef DEBUGHIERPS
		io_pswrite("/ypos 100 def\n");
		io_pswrite("24 scaleFont\n");
		io_pswrite("300 ypos moveto (max) show\n");
		io_pswrite("100 ypos moveto (used) show\n");
		io_pswrite("/dovmstats {\n");
		io_pswrite("   ypos 26 add /ypos exch def\n");
		io_pswrite("   ypos 3200 gt\n");
		io_pswrite("     { /ypos 100 def showpage 72 300 div 72 300 div scale 24 scaleFont} if\n");
		io_pswrite("   vmstatus\n");
		io_pswrite("   300 ypos moveto 50 string cvs show\n");
		io_pswrite("   100 ypos moveto 50 string cvs show\n");
		io_pswrite("   pop\n");
		io_pswrite("   500 ypos moveto show\n");
		io_pswrite("} def\n");
#endif

		/* put out all macros first */
		io_psputheader(PSHEADERDOT);
		io_psputheader(PSHEADERLINE);
		io_psputheader(PSHEADERPOLYGON);
		io_psputheader(PSHEADERFPOLYGON);
		io_psputheader(PSHEADERSTRING);

		/* dump all necessary facets */
		strcpy(io_psprefix, "    ");
		io_psdumpfacets(el_curwindowpart->curnodeproto);

		/* dump the current facet */
		io_pswrite("\n%% Invocation of top-level facet\n");
#ifdef DEBUGHIERPS
		io_pswrite("(FINISHED) dovmstats\n");
#else
		if (io_psusecolor)
		{
			for(i=0; i<io_maxpslayer; i++)
			{
				io_whichlayer = io_nextplotlayer(i) + 1;
				if (((BOOLEAN *)np->temp2)[io_whichlayer])
					io_pswrite("%s\n", io_psfacetname(np));
			}
			io_whichlayer = 0;
			if (((BOOLEAN *)np->temp2)[io_whichlayer])
				io_pswrite("%s\n", io_psfacetname(np));
		} else
		{
			io_pswrite("%s\n", io_psfacetname(np));
		}
#endif

		/* free memory */
		if (io_psusecolor)
		{
			for(lib = el_curlib; lib != NOLIBRARY; lib = lib->nextlibrary)
				for(onp = lib->firstnodeproto; onp != NONODEPROTO; onp = onp->nextnodeproto)
					efree((char *)onp->temp2);
		}
		ttyputmsg(_("Hierarchical PostScript uses %ld units of VM"), io_psopcount);
	}

	/* draw the grid if requested */
	if ((el_curwindowpart->state&(GRIDON|GRIDTOOSMALL)) == GRIDON)
	{
		lambda = np->cell->lib->lambda[np->tech->techindex];
		gridx = muldiv(el_curwindowpart->gridx, lambda, WHOLE);
		gridy = muldiv(el_curwindowpart->gridy, lambda, WHOLE);
		var = getvalkey((INTBIG)us_tool, VTOOL, VINTEGER, us_gridfloatskey);
		if (var == NOVARIABLE || var->addr == 0)
		{
			gridlx = lx  / gridx * gridx;
			gridly = ly  / gridy * gridy;
		} else
		{
			grabpoint(np, &gridlx, &gridly);
			gridalign(&gridlx, &gridly, 1);
			gridlx += gridlx / gridx * gridx;
			gridly += gridly / gridy * gridy;
		}

		/* adjust to ensure that the first point is inside the range */
		if (gridlx > lx) gridlx -= (gridlx - lx) / gridx * gridx;
		if (gridly > ly) gridly -= (gridly - ly) / gridy * gridy;
		while (gridlx < lx) gridlx += gridx;
		while (gridly < ly) gridly += gridy;

		/* PostScript: write the grid loop */
		io_pswrite("%ld %ld %ld\n{\n", gridlx, gridx, hx);
		io_pswrite("    %ld %ld %ld\n    {\n", gridly, gridy, hy);	/* x y */
		io_pswrite("        dup 3 -1 roll dup dup\n");				/* y y x x x */
		io_pswrite("        5 1 roll 3 1 roll\n");					/* x y x y x */
		io_pswrite("        %ld mul exch %ld mul add 65536 div %ld add\n",
			io_psmatrix[0][0], io_psmatrix[1][0],
			io_psmatrix[2][0]);										/* x y x x' */
		io_pswrite("        3 1 roll\n");							/* x x' y x */
		io_pswrite("        %ld mul exch %ld mul add 65536 div %ld add\n",
			io_psmatrix[0][1], io_psmatrix[1][1],
			io_psmatrix[2][1]);										/* x x' y' */
		io_pswrite("        newpath moveto 0 0 rlineto stroke\n");
		io_pswrite("    } for\n");
		io_pswrite("} for\n");
	}

	/* draw frame if it is there */
	j = framepolys(np);
	if (j != 0)
	{
		if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_tool->cluster);
		io_whichlayer = -1;
		for(i=0; i<j; i++)
		{
			framepoly(i, poly, np);
			(void)io_pspoly(poly, el_curwindowpart);
		}
	}

	/* put out dates if requested */
	if (plotdates)
	{
		(void)initinfstr();
		(void)formatinfstr(_("Facet: %s"), describenodeproto(np));
		io_pswrite("%s%ld %ld ", io_psprefix, io_pswindow.uselx,
			io_pswindow.usely + 2 * CORNERDATESIZE * PSSCALE);
		io_pswritestring(returninfstr());
		io_pswrite(" %ld Botleftstring\n", CORNERDATESIZE * PSSCALE);

		(void)initinfstr();
		(void)formatinfstr(_("Created: %s"), timetostring(np->creationdate));
		io_pswrite("%s%ld %ld ", io_psprefix, io_pswindow.uselx,
			io_pswindow.usely + CORNERDATESIZE * PSSCALE);
		io_pswritestring(returninfstr());
		io_pswrite(" %ld Botleftstring\n", CORNERDATESIZE * PSSCALE);

		(void)initinfstr();
		(void)formatinfstr(_("Revised: %s"), timetostring(np->revisiondate));
		io_pswrite("%s%ld %ld ", io_psprefix, io_pswindow.uselx, io_pswindow.usely);
		io_pswritestring(returninfstr());
		io_pswrite(" %ld Botleftstring\n", CORNERDATESIZE * PSSCALE);
	}

	io_pswrite("showpage\n");
#ifdef CLIP /* restore the graphics state after clipping */
	if (epsformat) io_pswrite("grestore\n");
#endif
	io_pswrite("%%%%Trailer\n");
	xclose(io_psout);
	if (io_verbose < 0)
		DiaDoneDialog();

	/* restore window */
	el_curwindowpart->uselx = sulx;
	el_curwindowpart->usehx = suhx;
	el_curwindowpart->usely = suly;
	el_curwindowpart->usehy = suhy;
	el_curwindowpart->screenlx = sslx;   el_curwindowpart->screenhx = sshx;
	el_curwindowpart->screenly = ssly;   el_curwindowpart->screenhy = sshy;
	computewindowscale(el_curwindowpart);

	/* print the PostScript if requested */
	if (printit != 0)
	{
		io_pssendfiletoprinter(file);
	} else
		ttyputmsg(_("%s written"), truename);
	return(FALSE);
}

void io_pssendfiletoprinter(char *file)
{
	char *execpars[5], *printer, *defprinter;
	REGISTER INTBIG printprocess, ac;
	REGISTER VARIABLE *var;

	/* determine printer to use */
	defprinter = 0;
	var = getval((INTBIG)io_tool, VTOOL, VSTRING, "IO_default_printer");
	if (var != NOVARIABLE) defprinter = (char *)var->addr; else
	{
		printer = egetenv("PRINTER");
		if (printer != 0 && *printer != 0) defprinter = printer;
	}

	/* fork to run the print job */
	printprocess = efork();
	if (printprocess == 1)
	{
		ttyputerr(_("Cannot run print on this machine"));
		return;
	}
	if (printprocess == 0)
	{
		ac = 0;
		execpars[ac++] = "lpr";
		if (defprinter != 0)
		{
			(void)initinfstr();
			(void)addstringtoinfstr("-P");
			(void)addstringtoinfstr(defprinter);
			execpars[ac++] = returninfstr();
		}
		execpars[ac++] = file;
		execpars[ac] = 0;
		eexec("lpr", execpars);
		eunlink(file);
		ttyputmsg(_("Could not run 'lpr' to print"));
		exit(0);
	} else
	{
		ewait(printprocess);
	}
	if (defprinter == 0) ttyputmsg(_("Print queued")); else
		ttyputmsg(_("Printing to %s"), defprinter);
}

/*
 * Routine to determine the PostScript name of facet "np".
 */
char *io_psfacetname(NODEPROTO *np)
{
	REGISTER char *ch;

	(void)initinfstr();
	(void)addstringtoinfstr("Facet_");
	(void)addstringtoinfstr(np->cell->cellname);
	if (np->cellview != el_unknownview)
	{
		(void)addtoinfstr('_');
		(void)addstringtoinfstr(np->cellview->sviewname);
	}
	if (io_psusecolor)
	{
		(void)addtoinfstr('_');
		if (io_whichlayer == 0) (void)addstringtoinfstr("REST"); else
		{
			ch = layername(el_curtech, io_whichlayer-1);
			(void)addstringtoinfstr(ch);
		}
	}
	return(returninfstr());
}

/*
 * Routine to recursively dump the contents of facet "np" and its contents.
 */
void io_psdumpfacets(NODEPROTO *np)
{
	REGISTER NODEINST *ni;
	REGISTER INTBIG i;

	if (stopping(STOPREASONDECK)) return;
	if (np->temp1 != 0) return;
	np->temp1 = 1;
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0) continue;
		if ((ni->userbits&NEXPAND) == 0) continue;
		io_psdumpfacets(ni->proto);
		if (io_psusecolor && io_psfake)
		{
			for(i=0; i<=io_maxpslayer; i++)
				((BOOLEAN *)np->temp2)[i] += ((BOOLEAN *)ni->proto->temp2)[i];
		}
	}

	if (!io_psfake)
	{
		io_pswrite("\n%% Definition of facet %s\n", describenodeproto(np));
#ifdef DEBUGHIERPS
		io_pswrite("(%s) dovmstats\n", np->cell->cellname);
#endif
	}
	if (io_psusecolor)
	{
		/* color: plot layers in proper order */
		for(i=0; i<io_maxpslayer; i++)
		{
			io_whichlayer = io_nextplotlayer(i) + 1;
			if (io_psfake || ((BOOLEAN *)np->temp2)[io_whichlayer])
				io_psdumpcontents(np);
		}
		io_whichlayer = 0;
		if (io_psfake || ((BOOLEAN *)np->temp2)[0])
			io_psdumpcontents(np);
	} else
	{
		io_psdumpcontents(np);
	}
}

/*
 * Routine to dump the contents of facet "np".
 */
void io_psdumpcontents(NODEPROTO *np)
{
	REGISTER NODEINST *ni;
	REGISTER ARCINST *ai;
	REGISTER PORTPROTO *pp;
	static POLYGON *poly = NOPOLYGON;
	REGISTER INTBIG i, tot, cx, cy, tx, ty, ptx, pty;
	INTBIG pcx, pcy;
	XARRAY trans;
	void (*savepolyroutine)(POLYGON*, WINDOWPART*);
	INTBIG savestate;

	if (stopping(STOPREASONDECK)) return;
	if (io_psfake) io_psopcount += 2; else
		io_pswrite("/%s {\n", io_psfacetname(np));
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_tool->cluster);
	io_lastred = io_lastgreen = io_lastblue = -1;

	/* first write calls to subfacets */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex != 0) continue;
		if ((ni->userbits&NEXPAND) == 0) continue;
		if (io_psusecolor && !((BOOLEAN *)ni->proto->temp2)[io_whichlayer])
			continue;

		/* expanded: invoke lower level facet */
		tx = (ni->lowx + ni->highx - ni->proto->lowx - ni->proto->highx)/2;
		ty = (ni->lowy + ni->highy - ni->proto->lowy - ni->proto->highy)/2;
		ptx = muldiv(tx, io_psmatrix[0][0], 65536) + muldiv(ty, io_psmatrix[1][0], 65536);
		pty = muldiv(tx, io_psmatrix[0][1], 65536) + muldiv(ty, io_psmatrix[1][1], 65536);

		if (ni->rotation == 0 && ni->transpose == 0)
		{
			if (io_psfake) io_psopcount += 7; else
			{
				io_pswrite("%s%ld %ld translate %s %ld %ld translate\n",
					io_psprefix, ptx, pty, io_psfacetname(ni->proto), -ptx, -pty);
			}
		} else
		{
			cx = (ni->proto->lowx + ni->proto->highx)/2;
			cy = (ni->proto->lowy + ni->proto->highy)/2;
			io_psxform(cx, cy, &pcx, &pcy);

			if (io_psfake)
			{
				io_psopcount += 13;
				if (ni->transpose != 0) io_psopcount += 10;
				if (ni->rotation != 0) io_psopcount += 4;
			} else
			{
				io_pswrite("%s%ld %ld translate", io_psprefix, ptx+pcx, pty+pcy);
				if (ni->transpose != 0)
					io_pswrite(" 90 rotate -1 1 scale");
				if (ni->rotation != 0)
					io_pswrite(" %g rotate", (float)ni->rotation/10.0f);
				io_pswrite(" %ld %ld translate\n", -pcx, -pcy);
				io_pswrite("%s    %s\n", io_psprefix, io_psfacetname(ni->proto));
				io_pswrite("%s%ld %ld translate", io_psprefix, pcx, pcy);
				if (ni->rotation != 0)
					io_pswrite(" %g rotate", -(float)ni->rotation/10.0f);
				if (ni->transpose != 0)
					io_pswrite(" -1 1 scale -90 rotate");
				io_pswrite(" %ld %ld translate\n", -ptx-pcx, -pty-pcy);
			}
		}
	}

	/* now write primitives and unexpanded subfacets */
	for(ni = np->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		if (ni->proto->primindex == 0)
		{
			/* subfacet */
			if ((ni->userbits&NEXPAND) == 0)
			{
				if (io_psusecolor && io_psfake)
					((BOOLEAN *)np->temp2)[0] = TRUE;
				makerot(ni, trans);
				maketruerectpoly(ni->lowx, ni->highx, ni->lowy, ni->highy, poly);
				xformpoly(poly, trans);
				poly->style = CLOSEDRECT;
				poly->desc = &io_psblack;
				poly->layer = -1;
				io_pspoly(poly, el_curwindowpart);

				poly->style = TEXTCENT;
				TDCLEAR(poly->textdescript);
				TDSETSIZE(poly->textdescript, TXTSETQLAMBDA(12));
				poly->string = describenodeproto(ni->proto);
				io_pspoly(poly, el_curwindowpart);
			}
		} else
		{
			/* primitive */
			makerot(ni, trans);
			tot = nodepolys(ni, 0, NOWINDOWPART);
			for(i=0; i<tot; i++)
			{
				shapenodepoly(ni, i, poly);
				if (io_psusecolor && io_psfake)
				{
					if (poly->desc->bits != LAYERN && poly->desc->col != ALLOFF &&
						(poly->desc->colstyle&INVISIBLE) == 0)
							((BOOLEAN *)np->temp2)[poly->layer+1] = TRUE;
				}
				xformpoly(poly, trans);
				io_pspoly(poly, el_curwindowpart);
			}
		}
	}

	/* now write arcs */
	for(ai = np->firstarcinst; ai != NOARCINST; ai = ai->nextarcinst)
	{
		tot = arcpolys(ai, NOWINDOWPART);
		for(i=0; i<tot; i++)
		{
			shapearcpoly(ai, i, poly);
			if (io_psusecolor && io_psfake)
			{
				if (poly->desc->bits != LAYERN && poly->desc->col != ALLOFF &&
					(poly->desc->colstyle&INVISIBLE) == 0)
						((BOOLEAN *)np->temp2)[poly->layer+1] = TRUE;
			}
			io_pspoly(poly, el_curwindowpart);
		}
	}


	/* save and alter graphics state so that text can be dumped */
	if (np == el_curwindowpart->curnodeproto)
	{
		savestate = el_curwindowpart->state;
		el_curwindowpart->state = (el_curwindowpart->state & ~WINDOWTYPE) | DISPWINDOW;
		savepolyroutine = us_displayroutine;
		us_displayroutine = io_pspoly;

		/* write facet text */
		us_drawnodeprotovariables(np, el_matid, el_curwindowpart, FALSE);

		/* write port text */
		for(pp = np->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			makerot(pp->subnodeinst, trans);
			us_writeprotoname(pp, LAYERA, trans, LAYERO,
				el_colfacettxt, el_curwindowpart, 0, 0, 0, 0,
					(us_useroptions&PORTLABELS) >> PORTLABELSSH);
			us_drawportprotovariables(pp, 1, trans, el_curwindowpart, FALSE);
		}

		/* restore graphics state */
		el_curwindowpart->state = savestate;
		us_displayroutine = savepolyroutine;
	}

	if (io_psfake) io_psopcount += 2; else
		io_pswrite("} def\n");
}

/*
 * coroutine to count the number of polygons that will be plotted (for progress info).
 */
void io_pspolycount(POLYGON *poly, WINDOWPART *win)
{
	io_pspolygoncount++;
}

/*
 * coroutine to plot the polygon "poly"
 */
void io_pspoly(POLYGON *poly, WINDOWPART *win)
{
	REGISTER INTBIG red, green, blue, k, type, size;
	REGISTER float r, g, b;
	INTBIG xl, xh, yl, yh, x, y, listx[4], listy[4];
	REGISTER TECHNOLOGY *tech;

	io_pspolygoncount++;
	if (io_pspolygontotal > 0 && (io_pspolygoncount % 100) == 99)
	{
		if (io_verbose < 0)
			DiaPercent(1, io_pspolygoncount*100/io_pspolygontotal);
	}

	/* ignore null layers */
	if (poly->desc->bits == LAYERN || poly->desc->col == ALLOFF) return;
	if ((poly->desc->colstyle&INVISIBLE) != 0) return;

	/* ignore grids */
	if (poly->style == GRIDDOTS) return;

	/* ignore layers that are not supposed to be dumped at this time */
	if (io_whichlayer >= 0)
	{
		if (io_whichlayer == 0)
		{
			for(k=0; k<io_maxpslayer; k++) if (io_nextplotlayer(k) == poly->layer)
				return;
		} else
		{
			if (io_whichlayer-1 != poly->layer) return;
		}
	}

	/* set color if requested */
	if (io_psusecolor)
	{
		red = io_redmap[poly->desc->col];
		green = io_greenmap[poly->desc->col];
		blue = io_bluemap[poly->desc->col];
		if (red != io_lastred || green != io_lastgreen || blue != io_lastblue)
		{
			io_lastred = red;
			io_lastgreen = green;
			io_lastblue = blue;
			if (io_psfake) io_psopcount += 4; else
			{
				r = (float)red / 255.0f;
				g = (float)green / 255.0f;
				b = (float)blue / 255.0f;
				io_pswrite("%s%g %g %g setrgbcolor\n", io_psprefix, r, g, b);
			}
		}
	}

	switch (poly->style)
	{
		case FILLED:
		case FILLEDRECT:
			if (isbox(poly, &xl, &xh, &yl, &yh))
			{
				if (xl == xh)
				{
					if (yl == yh) io_psdot(xl, yl); else
						io_psline(xl, yl, xl, yh, 0);
					break;
				} else if (yl == yh)
				{
					io_psline(xl, yl, xh, yl, 0);
					break;
				}
				listx[0] = xl;   listy[0] = yl;
				listx[1] = xl;   listy[1] = yh;
				listx[2] = xh;   listy[2] = yh;
				listx[3] = xh;   listy[3] = yl;
				io_pspolygon(listx, listy, 4, poly->desc);
			} else
			{
				if (poly->count == 1)
				{
					io_psdot(poly->xv[0], poly->yv[0]);
					break;
				}
				if (poly->count == 2)
				{
					io_psline(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1], 0);
					break;
				}
				io_pspolygon(poly->xv, poly->yv, poly->count, poly->desc);
			}
			break;

		case CLOSED:
		case CLOSEDRECT:
			if (isbox(poly, &xl, &xh, &yl, &yh))
			{
				io_psline(xl, yl, xl, yh, 0);
				io_psline(xl, yh, xh, yh, 0);
				io_psline(xh, yh, xh, yl, 0);
				io_psline(xh, yl, xl, yl, 0);
				break;
			}
			/* FALLTHROUGH */ 

		case OPENED:
		case OPENEDT1:
		case OPENEDT2:
		case OPENEDT3:
			switch (poly->style)
			{
				case OPENEDT1: type = 1; break;
				case OPENEDT2: type = 2; break;
				case OPENEDT3: type = 3; break;
				default:       type = 0; break;
			}
			for (k = 1; k < poly->count; k++)
				io_psline(poly->xv[k-1], poly->yv[k-1], poly->xv[k], poly->yv[k], type);
			if (poly->style == CLOSED)
			{
				k = poly->count - 1;
				io_psline(poly->xv[k], poly->yv[k], poly->xv[0], poly->yv[0], type);
			}
			break;

		case VECTORS:
			for(k=0; k<poly->count; k += 2)
				io_psline(poly->xv[k], poly->yv[k], poly->xv[k+1], poly->yv[k+1], 0);
			break;

		case CROSS:
		case BIGCROSS:
			getcenter(poly, &x, &y);
			io_psline(x-5, y, x+5, y, 0);
			io_psline(x, y+5, x, y-5, 0);
			break;

		case CROSSED:
			getbbox(poly, &xl, &xh, &yl, &yh);
			io_psline(xl, yl, xl, yh, 0);
			io_psline(xl, yh, xh, yh, 0);
			io_psline(xh, yh, xh, yl, 0);
			io_psline(xh, yl, xl, yl, 0);
			io_psline(xh, yh, xl, yl, 0);
			io_psline(xh, yl, xl, yh, 0);
			break;

		case DISC:
			io_psdisc(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1], poly->desc);
			/* FALLTHROUGH */ 

		case CIRCLE: case THICKCIRCLE:
			io_pscircle(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1]);
			break;

		case CIRCLEARC: case THICKCIRCLEARC:
			io_psarc(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1],
				poly->xv[2], poly->yv[2]);
			break;

		case TEXTCENT:
		case TEXTTOP:
		case TEXTBOT:
		case TEXTLEFT:
		case TEXTRIGHT:
		case TEXTTOPLEFT:
		case TEXTBOTLEFT:
		case TEXTTOPRIGHT:
		case TEXTBOTRIGHT:
		case TEXTBOX:
			tech = poly->tech;
			size = io_pstruefontsize(TDGETSIZE(poly->textdescript), win, tech);
			getbbox(poly, &xl, &xh, &yl, &yh);
			io_pstext(poly->style, xl, xh, yl, yh, size, poly->string, poly->textdescript);
			break;
	}
}

INTBIG io_pstruefontsize(INTBIG font, WINDOWPART *w, TECHNOLOGY *tech)
{
	REGISTER INTBIG size, lambda, height;
	INTBIG psx1, psy1, psx2, psy2;
	REGISTER LIBRARY *lib;

	/* absolute font sizes are easy */
	if ((font&TXTPOINTS) != 0) return((font&TXTPOINTS) >> TXTPOINTSSH);

	/* detemine default, min, and max size of font */
	if (w->curnodeproto == NONODEPROTO) lib = el_curlib; else
		lib = w->curnodeproto->cell->lib;
	lambda = lib->lambda[tech->techindex];
	height = TXTGETQLAMBDA(font);
	height = height * lambda / 4;
	io_psxform(0, 0, &psx1, &psy1);
	io_psxform(0, height, &psx2, &psy2);
	size = computedistance(psx1, psy1, psx2, psy2);
	return(size);
}

void io_psputheader(INTBIG which)
{
	REGISTER char **strings;
	REGISTER INTBIG i;
	static char *putdot[] =
	{
		"/Putdot {",				/* print dot at stack pos */
		"    newpath moveto 0 0 rlineto stroke} def",
	0};
	static char *drawline[] =
	{
		"/Drawline {",				/* draw line on stack */
		"    newpath moveto lineto stroke} def",
	0};
	static char *polygon[] =
	{
		"/Polygon {",			/* put array into path */
		"    aload",
		"    length 2 idiv /len exch def",
		"    newpath",
		"    moveto",
		"    len 1 sub {lineto} repeat",
		"    closepath",
		"} def",
	0};
	static char *filledpolygon[] =
	{
		"/BuildCharDict 10 dict def",	/* ref Making a User Defined (PostScript Cookbook) */

		"/StippleFont1 7 dict def",
		"StippleFont1 begin",
		"    /FontType 3 def",
		"    /FontMatrix [1 0 0 1 0 0] def",
		"    /FontBBox [0 0 1 1] def",
		"    /Encoding 256 array def",
		"    0 1 255 {Encoding exch /.notdef put} for",
		"    /CharacterDefs 40 dict def",
		"    CharacterDefs /.notdef {} put",
		"    /BuildChar",
		"        { BuildCharDict begin",
		"            /char exch def",
		"            /fontdict exch def",
		"            /charname fontdict /Encoding get",
		"            char get def",
		"            /charproc fontdict /CharacterDefs get",
		"            charname get def",
		"            1 0 0 0 1 1 setcachedevice",
		"            gsave charproc grestore",
		"        end",
		"    } def",
		"end",

		"/StippleFont StippleFont1 definefont pop",

		"/StippleCharYSize 128 def",
		"/StippleCharXSize StippleCharYSize def",

		"/Filledpolygon {",
		"    gsave",
		"    /StippleFont findfont StippleCharYSize scalefont setfont",
		"    /LowY exch def /LowX exch def",
		"    /HighY exch LowY add def /HighX exch LowX add def",
		"    Polygon clip",
		"    /Char exch def",
		"    /LowY LowY StippleCharYSize div truncate StippleCharYSize mul def",
		"    /LowX LowX StippleCharXSize div truncate StippleCharXSize mul def",
		"    /HighY HighY StippleCharYSize div 1 add truncate StippleCharYSize mul def",
		"    /HighX HighX StippleCharXSize div 1 add truncate StippleCharXSize mul def",
		"    LowY StippleCharYSize HighY ",
		"    { LowX exch moveto ",
		"        LowX StippleCharXSize HighX ",
		"        { Char show pop ",
		"        } for ",
		"    } for",
		"    grestore",
		"} def",
	0};
	static char *stringheader[] =
	{
		/*
		 * Code to do super and subscripts:
		 *
		 * example:
		 *	"NORMAL\dSUB\}   NORMAL\uSUP\}"
		 *
		 * will subscript "SUB" and superscript "SUP", so "\d"  starts a
		 * subscript, "\u" starts a superscript, "\}" returns to
		 * normal.  Sub-subscripts, and super-superscripts are not
		 * supported.  To print a "\", use "\\".
		 *
		 * changes:
		 *
		 * all calls to stringwidth were changed to calls to StringLength,
		 *    which returns the same info (assumes non-rotated text), but
		 *    takes sub- and super-scripts into account.
		 * all calls to show were changes to calls to StringShow, which
		 *    handles sub- and super-scripts.
		 * note that TSize is set to the text height, and is passed to
		 *    StringLength and StringShow.
		 */
		"/ComStart 92 def",								/* "\", enter command mode */
		"/ComSub  100 def",								/* "d", start subscript */
		"/ComSup  117 def",								/* "u", start superscript */
		"/ComNorm 125 def",								/* "}", return to normal */
		"/SSSize .70 def",								/* sub- and super-script size */
		"/SubDy  -.20 def",								/* Dy for sub-script */
		"/SupDy   .40 def",								/* Dy for super-script*/

		"/StringShow {",								/* str size StringShow */
		"    /ComMode 0 def",							/* command mode flag */
		"    /TSize exch def",							/* text size */
		"    /TString exch def",						/* string to draw */
		"    /NormY currentpoint exch pop def",			/* save Y coord of string */
		"    TSize scaleFont",
		"    TString {",								/* scan string char by char */
		"        /CharCode exch def",					/* save char */
		"        ComMode 1 eq {",
		"            /ComMode 0 def",					/* command mode */
		"            CharCode ComSub eq {",				/* start subscript */
		"                TSize SSSize mul scaleFont",
		"                currentpoint pop NormY TSize SubDy mul add moveto",
		"            } if",
		"            CharCode ComSup eq {",				/* start superscript */
		"                TSize SSSize mul scaleFont",
		"                currentpoint pop NormY TSize SupDy mul add moveto",
		"            } if",
		"            CharCode ComNorm eq {",			/* end sub- or super-script */
		"                TSize scaleFont",
		"                currentpoint pop NormY moveto",
		"            } if",
		"            CharCode ComStart eq {",			/* print a "\" */
		"                ( ) dup 0 CharCode put show",
		"            } if",
		"        }",
		"        {",
		"            CharCode ComStart eq {",
		"                /ComMode 1 def",				/* enter command mode */
		"            }",
		"            {",
		"                ( ) dup 0 CharCode put show",	/* print char */
		"            } ifelse",
		"        } ifelse",
		"    } forall ",
		"} def",

		"/StringLength {",								/* str size StringLength */
		"    /ComMode 0 def",							/* command mode flag */
		"    /StrLen 0 def",							/* total string length */
		"    /TSize exch def",							/* text size */
		"    /TString exch def",						/* string to draw */
		"    TSize scaleFont",
		"    TString {",								/* scan string char by char */
		"        /CharCode exch def",					/* save char */
		"        ComMode 1 eq {",
		"            /ComMode 0 def",					/* command mode */
		"            CharCode ComSub eq {",				/* start subscript */
		"                TSize SSSize mul scaleFont",
		"            } if",
		"            CharCode ComSup eq {",				/* start superscript */
		"                TSize SSSize mul scaleFont",
		"            } if",
		"            CharCode ComNorm eq {",			/* end sub- or super-script */
		"                TSize scaleFont",
		"            } if",
		"            CharCode ComStart eq {",			/* add "\" to length */
		"                ( ) dup 0 CharCode put stringwidth pop StrLen add",
		"                /StrLen exch def",
		"            } if",
		"        }",
		"        {",
		"            CharCode ComStart eq {",
		"                /ComMode 1 def",				/* enter command mode */
		"            }",
		"            {",								/* add char to length */
		"                ( ) dup 0 CharCode put stringwidth pop StrLen add",
		"                /StrLen exch def",
		"            } ifelse",
		"        } ifelse",
		"    } forall ",
		"    StrLen 0",									/* return info like stringwidth */
		"} def",

		"/Centerstring {",								/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont exch dup TSize StringLength", /* x y sca str xw yw */
		"    pop 3 -1 roll .5 mul",						/* x y str xw sca*.5 (was .8) */
		"    exch 5 -1 roll exch 2 div sub",			/* y str sca*.5 x-xw/2 */
		"    exch 4 -1 roll exch 2 div sub",			/* str x-xw/2 y-sca*.5/2 */
		"    moveto TSize StringShow",
		"} def",

		"/Topstring {",									/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont exch dup TSize StringLength", /* x y sca str xw yw */
		"    pop 3 -1 roll .5 mul",						/* x y str xw sca*.5 (was .8) */
		"    exch 5 -1 roll exch 2 div sub",			/* y str sca*.5 x-xw/2 */
		"    exch 4 -1 roll exch sub",					/* str x-xw/2 y-sca*.5 */
		"    moveto TSize StringShow",
		"} def",

		"/Botstring {",									/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    scaleFont dup TSize StringLength pop",		/* x y str xw */
		"    4 -1 roll exch 2 div sub",					/* y str x-xw/2 */
		"    3 -1 roll moveto TSize StringShow",
		"} def",

		"/Leftstring {",								/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont .4 mul",						/* x y str sca*.4 */
		"    3 -1 roll exch sub",						/* x str y-sca*.4 */
		"    3 -1 roll exch",							/* str x y-sca*.4 */
		"    moveto TSize StringShow",
		"} def",

		"/Rightstring {",								/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont exch dup TSize StringLength", /* x y sca str xw yw */
		"    pop 3 -1 roll .4 mul",						/* x y str xw sca*.4 */
		"    exch 5 -1 roll exch sub",					/* y str sca*.4 x-xw */
		"    exch 4 -1 roll exch sub",					/* str x-xw y-sca*.4 */
		"    moveto TSize StringShow",
		"} def",

		"/Topleftstring {",								/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont .5 mul",						/* x y str sca*.5 (was .8) */
		"    3 -1 roll exch sub",						/* x str y-sca*.5 */
		"    3 -1 roll exch",							/* str x y-sca*.5 */
		"    moveto TSize StringShow",
		"} def",

		"/Toprightstring {",							/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont exch dup TSize StringLength", /* x y sca str xw yw */
		"    pop 3 -1 roll .5 mul",						/* x y str xw sca*.5 (was .8) */
		"    exch 5 -1 roll exch sub",					/* y str sca*.5 x-xw */
		"    exch 4 -1 roll exch sub",					/* str x-xw y-sca*.5 */
		"    moveto TSize StringShow",
		"} def",

		"/Botleftstring {",								/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    scaleFont 3 1 roll moveto TSize StringShow",
		"} def",

		"/Botrightstring {",							/* x y str sca */
		"    dup /TSize exch def",						/* save size */
		"    scaleFont dup TSize StringLength",
		"    pop 4 -1 roll exch",
		"    sub 3 -1 roll",
		"    moveto TSize StringShow",
		"} def",

		"/Min {",										/* leave minimum of top two */
		"    dup 3 -1 roll dup",
		"    3 1 roll gt",
		"    {exch} if pop",
		"} def",

		"/Boxstring {",									/* x y mx my str sca */
		"    dup /TSize exch def",						/* save size */
		"    dup scaleFont",							/* x y mx my str sca */
		"    exch dup TSize StringLength pop",			/* x y mx my sca str xw */
		"    3 -1 roll dup",							/* x y mx my str xw sca sca */
		"    6 -1 roll mul",							/* x y my str xw sca sca*mx */
		"    3 -1 roll div",							/* x y my str sca sca*mx/xw */
		"    4 -1 roll",								/* x y str sca sca*mx/xw my */
		"    Min Min",									/* x y str minsca */
		"    Centerstring",
		"} def",
	0};

	switch (which)
	{
		case PSHEADERDOT:
			if (io_psdotput) return;
			io_psdotput = TRUE;
			strings = putdot;
			break;
		case PSHEADERLINE:
			if (io_psdrawlineput) return;
			io_psdrawlineput = TRUE;
			strings = drawline;
			break;
		case PSHEADERPOLYGON:
			if (io_pspolygonput) return;
			io_pspolygonput = TRUE;
			strings = polygon;
			break;
		case PSHEADERFPOLYGON:
			if (io_psfilledpolygonput) return;
			io_psfilledpolygonput = TRUE;
			strings = filledpolygon;
			break;
		case PSHEADERSTRING:
			if (io_psstringput) return;
			io_psstringput = TRUE;
			strings = stringheader;
			break;
	}
	for(i=0; strings[i] != 0; i++)
		io_pswrite("%s\n", strings[i]);
}

/* draw a dot */
void io_psdot(INTBIG x, INTBIG y)
{
	INTBIG psx, psy;

	if (io_psfake)
	{
		io_psopcount += 3;
		return;
	}

	io_psxform(x, y, &psx, &psy);
	io_psputheader(PSHEADERDOT);
	io_pswrite("%s%ld %ld Putdot\n", io_psprefix, psx, psy);
}

/* draw a line */
void io_psline(INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2, INTBIG pattern)
{
	INTBIG psx1, psy1, psx2, psy2, i;

	if (io_psfake)
	{
		io_psopcount += 5;
		if (pattern != 0) io_psopcount += 10;
		return;
	}

	io_psxform(x1, y1, &psx1, &psy1);
	io_psxform(x2, y2, &psx2, &psy2);
	io_psputheader(PSHEADERLINE);
	i = PSSCALE / 2;
	switch (pattern)
	{
		case 0:
			io_pswrite("%s%ld %ld %ld %ld Drawline\n", io_psprefix, psx1, psy1, psx2, psy2);
			break;
		case 1:
			io_pswrite("%s[%ld %ld] 0 setdash ", io_psprefix, i, i*3);
			io_pswrite("%s%ld %ld %ld %ld Drawline ", io_psprefix, psx1, psy1, psx2, psy2);
			io_pswrite("%s [] 0 setdash\n", io_psprefix);
			break;
		case 2:
			io_pswrite("%s[%ld %ld] 6 setdash ", io_psprefix, i*6, i*3);
			io_pswrite("%s%ld %ld %ld %ld Drawline ", io_psprefix, psx1, psy1, psx2, psy2);
			io_pswrite("%s [] 0 setdash\n", io_psprefix);
			break;
		case 3:
			io_pswrite("%ld setlinewidth ", PSSCALE);
			io_pswrite("%s%ld %ld %ld %ld Drawline ", io_psprefix, psx1, psy1, psx2, psy2);
			io_pswrite("%ld setlinewidth\n", PSSCALE/2);
			break;
	}
}

/* draw an arc of a circle */
void io_psarc(INTBIG centerx,INTBIG centery, INTBIG x1, INTBIG y1, INTBIG x2, INTBIG y2)
{
	INTBIG radius, pscx, pscy, psx1, psy1, psx2, psy2, startangle, endangle;

	if (io_psfake)
	{
		io_psopcount += 8;
		return;
	}

	io_psxform(centerx, centery, &pscx, &pscy);
	io_psxform(x1, y1, &psx1, &psy1);
	io_psxform(x2, y2, &psx2, &psy2);
	radius = computedistance(pscx, pscy, psx1, psy1);
	startangle = (figureangle(pscx, pscy, psx2, psy2) + 5) / 10;
	endangle = (figureangle(pscx, pscy, psx1, psy1) + 5) / 10;
	io_pswrite("%snewpath %ld %ld %ld %ld %ld arc stroke\n", io_psprefix, pscx,
		pscy, radius, startangle, endangle);
}

/* draw a circle (unfilled) */
void io_pscircle(INTBIG atx, INTBIG aty, INTBIG ex, INTBIG ey)
{
	INTBIG radius, pscx, pscy, psex, psey;

	if (io_psfake)
	{
		io_psopcount += 8;
		return;
	}

	io_psxform(atx, aty, &pscx, &pscy);
	io_psxform(ex, ey, &psex, &psey);
	radius = computedistance(pscx, pscy, psex, psey);
	io_pswrite("%snewpath %ld %ld %ld 0 360 arc stroke\n", io_psprefix, pscx,
		pscy, radius);
}

/* draw a filled circle */
void io_psdisc(INTBIG atx, INTBIG aty, INTBIG ex, INTBIG ey, GRAPHICS *desc)
{
	INTBIG radius, pscx, pscy, psex, psey;

	if (io_psfake)
	{
		io_psopcount += 8;
		return;
	}

	io_psxform(atx, aty, &pscx, &pscy);
	io_psxform(ex, ey, &psex, &psey);
	radius = computedistance(pscx, pscy, psex, psey);
	io_pswrite("%snewpath %ld %ld %ld 0 360 arc fill\n",
		io_psprefix, pscx, pscy, radius);
}

/* draw a polygon */
void io_pspolygon(INTBIG *x, INTBIG *y, INTBIG count, GRAPHICS *desc)
{
	REGISTER INTBIG lx, hx, ly, hy, i, stipplepattern;
	INTBIG psx, psy;

	if (count == 0) return;

	/* use solid color if solid pattern or no pattern */
	stipplepattern = 0;
	if (io_psusecolor)
	{
		if (io_psusecolorstip)
		{
			/* use stippled color if possible */
			if ((desc->colstyle&NATURE) == PATTERNED ||
				(desc->bwstyle&NATURE) == PATTERNED) stipplepattern = 1;
		} else
		{
			/* use solid color if default color style is solid */
			if ((desc->colstyle&NATURE) == PATTERNED) stipplepattern = 1;
		}
	} else
	{
		if ((desc->bwstyle&NATURE) == PATTERNED) stipplepattern = 1;
	}

	/* if stipple pattern is solid, just use solid fill */
	if (stipplepattern != 0)
	{
		for(i=0; i<8; i++)
			if ((desc->raster[i]&0xFFFF) != 0xFFFF) break;
		if (i >= 8) stipplepattern = 0;
	}

	/* put out solid fill if appropriate */
	if (stipplepattern == 0)
	{
		if (io_psfake) io_psopcount += count*2+4; else
		{
			io_psputheader(PSHEADERPOLYGON);
			io_pswrite("%s[", io_psprefix);
			for(i=0; i<count; i++)
			{
				if (i != 0) io_pswrite(" ");
				io_psxform(x[i], y[i], &psx, &psy);
				io_pswrite("%ld %ld", psx, psy);
			}
			io_pswrite("] Polygon fill\n");
		}
		return;
	}

	/*
	 * patterned fill: the hard one
	 * Generate filled polygons by defining a stipple font and then tiling the
	 * polygon to fill with 128x128 pixel characters, clipping to the polygon edge.
	 */
	if (io_psfake) io_psopcount += count*2+9; else
	{
		io_psputheader(PSHEADERPOLYGON);
		io_psputheader(PSHEADERFPOLYGON);
		io_pswrite("%s(%c) [", io_psprefix, io_pspattern(desc));
		io_psxform(x[0], y[0], &psx, &psy);
		lx = hx = psx;
		ly = hy = psy;
		for(i=0; i<count; i++)
		{
			if (i != 0) io_pswrite(" ");
			io_psxform(x[i], y[i], &psx, &psy);
			io_pswrite("%ld %ld", psx, psy);
			if (psx < lx) lx = psx;   if (psx > hx) hx = psx;
			if (psy < ly) ly = psy;   if (psy > hy) hy = psy;
		}
		io_pswrite("] %ld %ld %ld %ld Filledpolygon\n", hx-lx+1, hy-ly+1, lx, ly);
	}
}

char io_pspattern(GRAPHICS *col)
{
	INTBIG i, j, k, bl, bh, bld, bhd;
	GRAPHICS **newgraphicsseen;

	/* see if this graphics has been seen already */
	for(i=0; i<io_psgraphicsseencount; i++)
		if (io_psgraphicsseen[i] == col) return(i+'A');

	/* add to list */
	if (io_psgraphicsseencount >= io_psgraphicsseenlimit)
	{
		newgraphicsseen = (GRAPHICS **)emalloc((io_psgraphicsseenlimit + 50) *
			(sizeof (GRAPHICS *)), io_tool->cluster);
		if (newgraphicsseen == 0) return(0);
		for(i=0; i<io_psgraphicsseencount; i++)
			newgraphicsseen[i] = io_psgraphicsseen[i];
		if (io_psgraphicsseenlimit != 0) efree((char *)io_psgraphicsseen);
		io_psgraphicsseen = newgraphicsseen;
		io_psgraphicsseenlimit += 50;
	}
	io_psgraphicsseen[io_psgraphicsseencount++] = col;

	/*
	 * Generate filled polygons by defining a stipple font,
	 * and then tiling the polygon to fill with 128x128 pixel
	 * characters, clipping to the polygon edge.
	 *
	 * Take Electric's 16x8 bit images, double each bit,
	 * and then output 4 times to get 128 bit wide image.
	 * Double vertically by outputting each row twice.
	 * Note that full vertical size need not be generated,
	 * as PostScript will just reuse the lines until the 128
	 * size is reached.
	 *
	 * see: "Making a User Defined Font", PostScript Cookbook
	 */
	if (io_psfake) io_psopcount += 96; else
	{
		io_pswrite("%sStippleFont1 begin\n", io_psprefix);
		io_pswrite("%s    Encoding (%c) 0 get /Stipple%c put\n", io_psprefix,
			i+'A', i+'A');
		io_pswrite("%s    CharacterDefs /Stipple%c {\n", io_psprefix, i+'A');
		io_pswrite("%s        128 128 true [128 0 0 -128 0 128]\n", io_psprefix);
		io_pswrite("%s        { <\n", io_psprefix);
		for(i=0; i<8; i++)
		{
			bl = col->raster[i] & 0x00FF;
			bh = (col->raster[i] & 0xFF00) >> 8;
			bld = bhd = 0;
			for (k=0; k<8; ++k)
			{
				bld = (bld << 1);
				bld |= (bl & 0x1);
				bld = (bld << 1);
				bld |= (bl & 0x1);
				bl = (bl >> 1);
				bhd = (bhd << 1);
				bhd |= (bh & 0x1);
				bhd = (bhd << 1);
				bhd |= (bh & 0x1);
				bh = (bh >> 1);
			}
			for (k=0; k<2; k++)
			{
				io_pswrite("%s            ", io_psprefix);
				for(j=0; j<4; j++)
					io_pswrite("%04x %04x ", bhd&0xFFFF, bld&0xFFFF);
				io_pswrite("\n");
			}
		}
		io_pswrite("%s        > } imagemask\n", io_psprefix);
		io_pswrite("%s    } put\n", io_psprefix);
		io_pswrite("%send\n", io_psprefix);
	}
	return(io_psgraphicsseencount+'A'-1);
}

/* draw text */
void io_pstext(INTBIG type, INTBIG lx, INTBIG ux, INTBIG ly, INTBIG uy, INTBIG size,
	char *text, UINTBIG *textdescript)
{
	char *pt, psfontname[300];
	INTBIG pslx, pshx, psly, pshy, changedfont, descenderoffset,
		facenumber, x, y, rot, xoff, yoff;
	static char defaultfontname[200];
	static INTBIG defaultfontnameknown = 0;
	char **facelist, *facename, *opname;

	/* get the font size */
	if (size <= 0) return;

	if (io_psfake)
	{
		io_psopcount += 5;
		if (type == TEXTBOX) io_psopcount += 2;
		return;
	}

	/* make sure the string is valid */
	for(pt = text; *pt != 0; pt++) if (*pt != ' ' && *pt != '\t') break;
	if (*pt == 0) return;

	/* get the default font name */
	if (defaultfontnameknown == 0)
	{
		defaultfontnameknown = 1;
		strcpy(defaultfontname, screengetdefaultfacename());
	}

	io_psxform(lx, ly, &pslx, &psly);
	io_psxform(ux, uy, &pshx, &pshy);
	io_psputheader(PSHEADERSTRING);

	changedfont = 0;
	facenumber = TDGETFACE(textdescript);
	if (facenumber == 0) facename = 0; else
	{
		(void)screengetfacelist(&facelist);
		facename = facelist[facenumber];
	}
	if (facename != 0)
	{
		(void)initinfstr();
		for(pt = facename; *pt != 0; pt++)
		{
			if (*pt == ' ') (void)addtoinfstr('-'); else
				(void)addtoinfstr(*pt);
		}
		io_pswrite("/DefaultFont /%s def\n", returninfstr());
		changedfont = 1;
	} else
	{
		if (TDGETITALIC(textdescript) != 0)
		{
			if (TDGETBOLD(textdescript) != 0)
			{
				sprintf(psfontname, "/DefaultFont /%s-BoldItalic def\n", defaultfontname);
				io_pswrite(psfontname);
				changedfont = 1;
			} else
			{
				sprintf(psfontname, "/DefaultFont /%s-Italic def\n", defaultfontname);
				io_pswrite(psfontname);
				changedfont = 1;
			}
		} else if (TDGETBOLD(textdescript) != 0)
		{
			sprintf(psfontname, "/DefaultFont /%s-Bold def\n", defaultfontname);
			io_pswrite(psfontname);
			changedfont = 1;
		}
	}
	if (type == TEXTBOX)
	{
		io_pswrite("%s%ld %ld %ld %ld ", io_psprefix, (pslx+pshx)/2,
			(psly+pshy)/2, pshx-pslx, pshy-psly);
		io_pswritestring(text);
		io_pswrite(" %ld Boxstring\n", size);
	} else
	{
		switch (type)
		{
			case TEXTCENT:
				x = (pslx+pshx)/2;   y = (psly+pshy)/2;
				opname = "Centerstring";
				break;
			case TEXTTOP:
				x = (pslx+pshx)/2;   y = pshy;
				opname = "Topstring";
				break;
			case TEXTBOT:
				x = (pslx+pshx)/2;   y = psly;
				opname = "Botstring";
				break;
			case TEXTLEFT:
				x = pslx;   y = (psly+pshy)/2;
				opname = "Leftstring";
				break;
			case TEXTRIGHT:
				x = pshx;   y = (psly+pshy)/2;
				opname = "Rightstring";
				break;
			case TEXTTOPLEFT:
				x = pslx;   y = pshy;
				opname = "Topleftstring";
				break;
			case TEXTTOPRIGHT:
				x = pshx;   y = pshy;
				opname = "Toprightstring";
				break;
			case TEXTBOTLEFT:
				x = pslx;   y = psly;
				opname = "Botleftstring";
				break;
			case TEXTBOTRIGHT:
				x = pshx;   y = psly;
				opname = "Botrightstring";
				break;
		}
		descenderoffset = size / 12;
		rot = TDGETROTATION(textdescript);
		switch (rot)
		{
			case 0: y += descenderoffset;   break;
			case 1: x -= descenderoffset;   break;
			case 2: y -= descenderoffset;   break;
			case 3: x += descenderoffset;   break;
		}
		if (rot != 0)
		{
			if (rot == 1 || rot == 3)
			{
				switch (type)
				{
					case TEXTTOP:      opname = "Rightstring";     break;
					case TEXTBOT:      opname = "Leftstring";      break;
					case TEXTLEFT:     opname = "Botstring";       break;
					case TEXTRIGHT:    opname = "Topstring";       break;
					case TEXTTOPLEFT:  opname = "Botrightstring";  break;
					case TEXTBOTRIGHT: opname = "Topleftstring";   break;
				}
			}
			xoff = x;   yoff = y;
			x = y = 0;
			switch (rot)
			{
				case 1:		/* 90 degrees counterclockwise */
					io_pswrite("%s%ld %ld translate 90 rotate\n", io_psprefix, xoff, yoff);
					break;
				case 2:		/* 180 degrees */
					io_pswrite("%s%ld %ld translate 180 rotate\n", io_psprefix, xoff, yoff);
					break;
				case 3:		/* 90 degrees clockwise */
					io_pswrite("%s%ld %ld translate 270 rotate\n", io_psprefix, xoff, yoff);
					break;
			}
		}
		io_pswrite("%s%ld %ld ", io_psprefix, x, y);
		io_pswritestring(text);
		io_pswrite(" %ld %s\n", size, opname);
		if (rot != 0)
		{
			switch (rot)
			{
				case 1:		/* 90 degrees counterclockwise */
					io_pswrite("%s270 rotate %ld %ld translate\n", io_psprefix, -xoff, -yoff);
					break;
				case 2:		/* 180 degrees */
					io_pswrite("%s180 rotate %ld %ld translate\n", io_psprefix, -xoff, -yoff);
					break;
				case 3:		/* 90 degrees clockwise */
					io_pswrite("%s90 rotate %ld %ld translate\n", io_psprefix, -xoff, -yoff);
					break;
			}
		}
	}

	if (changedfont != 0)
	{
		sprintf(psfontname, "/DefaultFont /%s def\n", defaultfontname);
		io_pswrite(psfontname);
	}
}

/*
 * Routine to convert the coordinates (x,y) for display.  The coordinates for
 * printing are placed back into (x,y) and the PostScript coordinates are placed
 * in (psx,psy).
 */
void io_psxform(INTBIG x, INTBIG y, INTBIG *psx, INTBIG *psy)
{
	*psx = muldiv(x, io_psmatrix[0][0], 65536) + muldiv(y, io_psmatrix[1][0], 65536) + io_psmatrix[2][0];
	*psy = muldiv(x, io_psmatrix[0][1], 65536) + muldiv(y, io_psmatrix[1][1], 65536) + io_psmatrix[2][1];
}

void io_pswritestring(char *str)
{
	io_pswrite("(");
	for( ; *str != 0; str++)
	{
		if (*str == '(' || *str == ')' || *str == '\\') io_pswrite("\\");
		io_pswrite("%c", *str);
	}
	io_pswrite(")");
}

void io_pswrite(char *s, ...)
{
	char theline[100];
	va_list ap;

	var_start(ap, s);
	evsnprintf(theline, 100, s, ap);
	va_end(ap);

	xprintf(io_psout, "%s", theline);
}
