/*
 * Electric(tm) VLSI Design System
 *
 * File: iopsout.c
 * Input/output analysis aid: 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 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 INTSML     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 INTSML     io_psusecolor;			/* nonzero to use color PostScript */
static INTSML     io_psusecolorstip;		/* nonzero to use stippled color PostScript */
static INTSML     io_psdotput;				/* PostScript for dot put out */
static INTSML     io_psdrawlineput;			/* PostScript for line put out */
static INTSML     io_pspolygonput;			/* PostScript for polygon put out */
static INTSML     io_psfilledpolygonput;	/* PostScript for filled polygon put out */
static INTSML     io_psstringput;			/* PostScript for strings put out */
static INTSML     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 */

GRAPHICS io_psblack = {LAYERO, BLACK, SOLIDC, SOLIDC,
	{0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF,0xFFFF}, NOVARIABLE, 0};
static INTBIG     io_psconvert[9] = {4, 6, 8, 10, 12, 14, 16, 18, 20};

/* prototypes for local routines */
void   io_psarc(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
void   io_pscircle(INTBIG, INTBIG, INTBIG, INTBIG);
void   io_psdisc(INTBIG, INTBIG, INTBIG, INTBIG, GRAPHICS*);
void   io_psdot(INTBIG, INTBIG);
void   io_psdumpcontents(NODEPROTO *np);
void   io_psdumpfacets(NODEPROTO *np);
char  *io_psfacetname(NODEPROTO *np);
void   io_psline(INTBIG, INTBIG, INTBIG, INTBIG, INTBIG);
INTSML io_pspattern(GRAPHICS*);
INTSML io_pspoly(POLYGON*, WINDOWPART*);
INTSML io_pspolycount(POLYGON*, WINDOWPART*);
void   io_pspolygon(INTBIG*, INTBIG*, INTSML, GRAPHICS*);
void   io_psputheader(INTBIG which);
void   io_pstext(INTSML, INTBIG, INTBIG, INTBIG, INTBIG, INTBIG, char*);
void   io_pswrite(char *s, ...);
INTSML io_pswritefacet(NODEPROTO*, char*, INTBIG);
void   io_pswritestring(char*);
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.
 */
INTSML 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_postscriptfilename);
			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(1);
	}

	/* 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_postscriptfilename);
		if (var == NOVARIABLE) continue;

		/* existing file: check the date to see if it should be overwritten */
		vardate = getvalkey((INTBIG)np, VNODEPROTO, VINTEGER, io_postscriptfiledate);
		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) != 0) return(1);
		numsyncs++;

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

	return(0);
}

/*
 * 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.
 */
INTSML io_pswritefacet(NODEPROTO *np, char *synchronize, INTBIG printit)
{
	char file[100], *facetname, *truename, *execpars[5], *printer, *defprinter;
	INTBIG lx, hx, ly, hy, hlx, hhx, hly, hhy, gridlx, gridly, gridx, gridy,
		cx, cy, i, j, psulx, psuhx, psuly, psuhy, sulx, suhx,
		suly, suhy, sslx, sshx, ssly, sshy, oldoptions, ac;
	REGISTER INTBIG pagewid, pagehei, pagemarginps, pagemargin, *curstate;
	REGISTER INTSML *layarr, plotdates, plotfocus, plotfocusdisplay, rotateplot,
		useplotter, hierpostscript, epsformat;
	INTBIG bblx, bbhx, bbly, bbhy, t1, t2, unitsx, unitsy, printprocess;
	UINTBIG curdate;
	REGISTER NODEPROTO *onp;
	REGISTER LIBRARY *lib;
	VARIABLE *var;
	REGISTER VARIABLE *varred, *vargreen, *varblue;
	static POLYGON *poly = NOPOLYGON;
	extern DIALOG us_progressdialog;

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

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

	var = getval((INTBIG)io_aid, VAID, VFRACT, "IO_postscript_width");
	if (var == NOVARIABLE) pagewid = DEFAULTPSWIDTH; else
		pagewid = muldiv(var->addr, 75, WHOLE);
	var = getval((INTBIG)io_aid, VAID, VFRACT, "IO_postscript_height");
	if (var == NOVARIABLE) pagehei = DEFAULTPSHEIGHT; else
		pagehei = muldiv(var->addr, 75, WHOLE);
	var = getval((INTBIG)io_aid, VAID, 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 != 0)
	{
		varred = getval((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, "USER_colormap_red");
		vargreen = getval((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, "USER_colormap_green");
		varblue = getval((INTBIG)us_aid, VAID, VINTEGER|VISARRAY, "USER_colormap_blue");
		if (varred == NOVARIABLE || vargreen == NOVARIABLE || varblue == NOVARIABLE)
		{
			ttyputmsg(_("Cannot get colors!"));
			return(1);
		}
		io_redmap = (INTBIG *)varred->addr;
		io_greenmap = (INTBIG *)vargreen->addr;
		io_bluemap = (INTBIG *)varblue->addr;
	}

	/* determine the area of interest */
	us_fullview(np, &lx, &hx, &ly, &hy);
	if (plotfocus != 0)
	{
		if (plotfocusdisplay != 0)
		{
			lx = el_curwindowpart->screenlx;
			hx = el_curwindowpart->screenhx;
			ly = el_curwindowpart->screenly;
			hy = el_curwindowpart->screenhy;
		} else
		{
			onp = (NODEPROTO *)askaid(us_aid, "get-highlighted-area",
				(INTBIG)&hlx, (INTBIG)&hhx, (INTBIG)&hly, (INTBIG)&hhy);
			if (onp == NONODEPROTO)
				ttyputerr(_("Warning: no highlighted area; plotting entire facet")); else
			{
				if (hhx == hlx || hhy == hly)
				{
					ttyputerr(_("Warning: no highlighted area; highlight area and reissue command"));
					return(1);
				}
				lx = hlx;   hx = hhx;
				ly = hly;   hy = hhy;
			}
		}
	}

	/* 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(1);
		}
	} 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(1);
		}
	} 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(1);
		}
	}

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

	/* build pseudowindow for text scaling */
	psulx = psuly = 0;
	psuhx = psuhy = 1000;
	io_pswindow.uselx = (INTSML)psulx;
	io_pswindow.usely = (INTSML)psuly;
	io_pswindow.usehx = (INTSML)psuhx;
	io_pswindow.usehy = (INTSML)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;
	if (epsformat != 0)
	{
		var = getvalkey((INTBIG)np, VNODEPROTO, VFRACT, io_postscriptepsscale);
		if (var != NOVARIABLE)
		{
			unitsx = muldiv(pagewid-pagemarginps*2, var->addr, WHOLE);
			unitsy = muldiv(pagehei-pagemarginps*2, var->addr, WHOLE);
		} else
		{
			unitsx = (pagewid-pagemarginps*2)*2;
			unitsy = (pagehei-pagemarginps*2)*2;
		}
	} else
	{
		unitsx = (pagewid-pagemarginps*2)*4;
		unitsy = (pagehei-pagemarginps*2)*4;
	}
	if (useplotter != 0)
	{
		i = (unitsx << 16) / (hx - lx);
		j = (unitsx << 16) / (hy - ly);
	} else
	{
		i = mini((unitsx << 16) / (hx - lx), (unitsy << 16) / (hy - ly));
		j = mini((unitsx << 16) / (hy - ly), (unitsy << 16) / (hx - lx));
	}
	if (rotateplot != 0) 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] = - (i*cx >> 16) + unitsx / 2 + pagemarginps * PSSCALE;
	if (useplotter != 0)
	{
		io_psmatrix[2][1] = - (i*ly >> 16) + pagemarginps * PSSCALE;
	} else
	{
		io_psmatrix[2][1] = - (i*cy >> 16) + 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");
	(void)initinfstr();
	(void)addstringtoinfstr(describenodeproto(np));
	facetname = returninfstr();
	io_pswrite("%%%%Title: %s\n", facetname);
	io_pswrite("%%%%Creator: Electric VLSI Design System version %s\n", el_version);
	curdate = getcurrenttime();
	io_pswrite("%%%%CreationDate: %s\n", timetostring(curdate));
	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 != 0)
	{
		/*
		 * 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);

	/*
	 * SRP920115 Write in EPS format:
	 * if the image is rotated on the page then subtract the offset from bblx and bbhx,
	 * otherwise add the offset.  Also increase the size of the bbox by one "pixel" to
	 * prevent the edges from being obscured by some drawing tools
	 */
	if (rotateplot != 0)
		io_pswrite("%%%%BoundingBox: %ld %ld %ld %ld\n",bblx-1, bbly-1, bbhx+1, bbhy+1); else
			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 and EPS 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 != 0)
	{
		t1 = bbhx - bblx + pagemarginps * 2;
		t2 = bbhy - bbly + pagemarginps * 2;
		io_pswrite("<< /PageSize [%ld %ld] >> setpagedevice\n", t1, t2);
	}

	/* 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("/scaleFont {\n");
	io_pswrite("    /Times-Roman 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)
	{
		/* 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 != 0)
		{
			io_pswrite("%s %s add 300 mul %s 300 mul translate 90 rotate\n",
				frtoa(muldiv(pagewid, WHOLE, 75)),
				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 == 0)
	{
		/* 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)askaid(us_aid, "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 != 0)
		{
			/* color: plot layers in proper order */
			io_maxpslayer = io_setuptechorder(el_curtech);
			io_pspolygontotal *= (io_maxpslayer+1);
			for(i=0; i<io_maxpslayer; i++)
			{
				io_whichlayer = io_nextplotlayer(i) + 1;
				(void)askaid(us_aid, "display-to-routine", io_pspoly);
			}
			io_whichlayer = 0;
			(void)askaid(us_aid, "display-to-routine", io_pspoly);
		} else
		{
			/* gray-scale: just plot it once */
			io_whichlayer = -1;
			(void)askaid(us_aid, "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 != 0)
		{
			/* 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 = (INTSML *)emalloc((io_maxpslayer+1) * SIZEOFINTSML,
						io_aid->cluster);
					if (layarr == 0) return(1);
					for(i=0; i<=io_maxpslayer; i++) layarr[i] = 0;
					onp->temp2 = (INTBIG)layarr;
				}
			}
		}

		/* count the number of symbols used by the code */
		io_psopcount = 0;
		io_psfake = 1;
		io_psdumpfacets(el_curwindowpart->curnodeproto);
		io_psfake = 0;
		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");
#ifndef DEBUGHIERPS
		io_pswrite("(FINISHED) dovmstats\n");
#else
		if (io_psusecolor != 0)
		{
			for(i=0; i<io_maxpslayer; i++)
			{
				io_whichlayer = io_nextplotlayer(i) + 1;
				if (((INTSML *)np->temp2)[io_whichlayer] != 0)
					io_pswrite("%s\n", io_psfacetname(np));
			}
			io_whichlayer = 0;
			if (((INTSML *)np->temp2)[io_whichlayer] != 0)
				io_pswrite("%s\n", io_psfacetname(np));
		} else
		{
			io_pswrite("%s\n", io_psfacetname(np));
		}
#endif

		/* free memory */
		if (io_psusecolor != 0)
		{
			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 %d units of VM"), io_psopcount);
	}

	/* draw the grid if requested */
	if ((el_curwindowpart->state&(GRIDON|GRIDTOOSMALL)) == GRIDON)
	{
		gridx = el_curwindowpart->gridx;
		gridy = el_curwindowpart->gridy;
		var = getvalkey((INTBIG)us_aid, VAID, VINTEGER, us_gridfloats);
		if (var == NOVARIABLE || var->addr == 0)
		{
			gridlx = np->lowx  / gridx * gridx;
			gridly = np->lowy  / gridy * gridy;
		} else
		{
			grabpoint(np, &gridlx, &gridly);
			var = getval((INTBIG)us_aid, VAID, VINTEGER, "USER_alignment_obj");
			if (var != NOVARIABLE) gridalign(&gridlx, &gridly, var->addr);
			gridlx += gridlx / gridx * gridx;
			gridly += gridly / gridy * gridy;
		}

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

		/* plot the loop to the printer */
		for (cx = gridlx; cx <= hx; cx += gridx)
			for (cy = gridly; cy <= hy; cy += gridy)
				io_psdot(cx, cy);

		/* 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_aid->cluster);
		io_whichlayer = -1;
		for(i=0; i<j; i++)
		{
			framepoly((INTSML)i, poly, np);
			(void)io_pspoly(poly, el_curwindowpart);
		}
	}

	/* put out dates if requested */
	if (plotdates != 0)
	{
		/* create the polygon if it doesn't exist */
		if (poly == NOPOLYGON) poly = allocstaticpolygon(1, io_aid->cluster);

		/* plot facet name */
		io_whichlayer = -1;
		poly->string = facetname;
		poly->xv[0] = np->highx;
		poly->yv[0] = np->lowy;
		poly->count = 1;
		poly->style = TEXTBOTRIGHT;
		poly->font = TXTMEDIUM;
		poly->tech = el_curtech;
		poly->desc = &io_psblack;
		(void)io_pspoly(poly, el_curwindowpart);

		/* plot creation date */
		(void)initinfstr();
		(void)formatinfstr(_("Created: %s"), timetostring(np->creationdate));
		poly->string = returninfstr();
		poly->yv[0] = np->lowy + (np->highy-np->lowy) / 20;
		(void)io_pspoly(poly, el_curwindowpart);

		/* plot revision date */
		(void)initinfstr();
		(void)formatinfstr(_("Revised: %s"), timetostring(np->revisiondate));
		poly->string = returninfstr();
		poly->yv[0] = np->lowy + (np->highy-np->lowy) / 10;
		(void)io_pspoly(poly, el_curwindowpart);
	}

	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 = (INTSML)sulx;
	el_curwindowpart->usehx = (INTSML)suhx;
	el_curwindowpart->usely = (INTSML)suly;
	el_curwindowpart->usehy = (INTSML)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)
	{
		/* determine printer to use */
		defprinter = 0;
		var = getval((INTBIG)io_aid, VAID, 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(0);
		}
		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);
		}
		if (defprinter == 0) ttyputmsg(_("Print queued")); else
			ttyputmsg(_("Printing to %s"), defprinter);
	} else
		ttyputmsg(_("%s written"), truename);
	return(0);
}

/*
 * 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 != 0)
	{
		(void)addtoinfstr('_');
		if (io_whichlayer == 0) (void)addstringtoinfstr("REST"); else
		{
			ch = layername(el_curtech, (INTSML)(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 (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 != 0 && io_psfake != 0)
		{
			for(i=0; i<=io_maxpslayer; i++)
				((INTSML *)np->temp2)[i] += ((INTSML *)ni->proto->temp2)[i];
		}
	}

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

	if (io_psfake != 0) io_psopcount += 2; else
		io_pswrite("/%s {\n", io_psfacetname(np));
	if (poly == NOPOLYGON) poly = allocstaticpolygon(4, io_aid->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 != 0 && ((INTSML *)ni->proto->temp2)[io_whichlayer] == 0)
			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 = ((tx * io_psmatrix[0][0] + ty * io_psmatrix[1][0]) >> 16);
		pty = ((tx * io_psmatrix[0][1] + ty * io_psmatrix[1][1]) >> 16);

		if (ni->rotation == 0 && ni->transpose == 0)
		{
			if (io_psfake != 0) 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 != 0)
			{
				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 != 0 && io_psfake != 0)
					((INTSML *)np->temp2)[0]++;
				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;
				poly->font = TXTLARGE;
				poly->string = describenodeproto(ni->proto);
				io_pspoly(poly, el_curwindowpart);
			}
		} else
		{
			/* primitive */
			makerot(ni, trans);
			tot = nodepolys(ni, 0);
			for(i=0; i<tot; i++)
			{
				shapenodepoly(ni, i, poly);
				if (io_psusecolor != 0 && io_psfake != 0)
				{
					if (poly->desc->bits != LAYERN && poly->desc->col != ALLOFF &&
						(poly->desc->colstyle&INVISIBLE) == 0)
							((INTSML *)np->temp2)[poly->layer+1]++;
				}
				xformpoly(poly, trans);
				io_pspoly(poly, el_curwindowpart);
			}
		}
	}

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

		/* 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_drawportprotovariables(pp, 1, trans, el_curwindowpart);
		}

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

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

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

/*
 * coroutine to plot the polygon "poly"
 */
INTSML io_pspoly(POLYGON *poly, WINDOWPART *win)
{
	REGISTER INTBIG red, green, blue, k, type, font;
	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(1);
	if ((poly->desc->colstyle&INVISIBLE) != 0) return(1);

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

	/* 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(0);
		} else
		{
			if (io_whichlayer-1 != poly->layer) return(0);
		}
	}

	/* set color if requested */
	if (io_psusecolor != 0)
	{
		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 != 0) io_psopcount += 4; else
			{
				r = (float)red / 255.0f;
				g = (float)green / 255.0f;
				b = (float)blue / 255.0f;
				io_pswrite("%s%f %f %f 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;
			}

		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);

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

		case CIRCLEARC:
			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 = win->curnodeproto->tech;
			font = truefontsize(poly->font, el_curwindowpart, tech);
			getbbox(poly, &xl, &xh, &yl, &yh);
			io_pstext(poly->style, xl, xh, yl, yh, font, poly->string);
			break;
	}
	return(0);
}

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[] =
	{
		/*
		 * CS901126  Added 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",						/* CS901126 save size */
		"    dup scaleFont exch dup TSize StringLength", /* x y sca str xw yw */
		"    pop 3 -1 roll .8 mul",						/* x y str xw sca*.8 */
		"    exch 5 -1 roll exch 2 div sub",			/* y str sca*.8 x-xw/2 */
		"    exch 4 -1 roll exch 2 div sub",			/* str x-xw/2 y-sca*.8/2 */
		"    moveto TSize StringShow",
		"} def",

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

		"/Botstring {",									/* x y str sca */
		"    dup /TSize exch def",						/* CS901126 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",						/* CS901126 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",						/* CS901126 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",						/* CS901126 save size */
		"    dup scaleFont .8 mul",						/* x y str sca*.8 */
		"    3 -1 roll exch sub",						/* x str y-sca*.8 */
		"    3 -1 roll exch",							/* str x y-sca*.8 */
		"    moveto TSize StringShow",
		"} def",

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

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

		"/Botrightstring {",							/* x y str sca */
		"    dup /TSize exch def",						/* CS901126 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",						/* CS 901126 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 != 0) return;
			io_psdotput++;
			strings = putdot;
			break;
		case PSHEADERLINE:
			if (io_psdrawlineput != 0) return;
			io_psdrawlineput++;
			strings = drawline;
			break;
		case PSHEADERPOLYGON:
			if (io_pspolygonput != 0) return;
			io_pspolygonput++;
			strings = polygon;
			break;
		case PSHEADERFPOLYGON:
			if (io_psfilledpolygonput != 0) return;
			io_psfilledpolygonput++;
			strings = filledpolygon;
			break;
		case PSHEADERSTRING:
			if (io_psstringput != 0) return;
			io_psstringput++;
			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 != 0)
	{
		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 != 0)
	{
		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 1: io_pswrite("%s[%ld %ld] 0 setdash ", io_psprefix, i, i*3);    break;
		case 2: io_pswrite("%s[%ld %ld] 6 setdash ", io_psprefix, i*6, i*3);  break;
		case 3: io_pswrite("%s[%ld %ld] 0 setdash ", io_psprefix, i, i*7);    break;
	}
	io_pswrite("%s%ld %ld %ld %ld Drawline", io_psprefix, psx1, psy1, psx2, psy2);
	if (pattern != 0) io_pswrite("%s [] 0 setdash", io_psprefix);
	io_pswrite("\n");
}

/* 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 != 0)
	{
		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 != 0)
	{
		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 != 0)
	{
		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, INTSML 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 != 0)
	{
		if (io_psusecolorstip != 0)
		{
			/* 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 != 0) 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 != 0) 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);
	}
}

INTSML 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_aid->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;

	/* CS900228 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.

				ref Making a User Defined Font:  PostScript Cookbook
	*/
	if (io_psfake != 0) 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(INTSML type, INTBIG lx, INTBIG ux, INTBIG ly, INTBIG uy, INTBIG fnt,
	char *text)
{
	char *pt;
	INTBIG pslx, pshx, psly, pshy;

	if (io_psfake != 0)
	{
		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;

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

	io_psputheader(PSHEADERSTRING);
	switch (type)
	{
		case TEXTCENT:
			io_pswrite("%s%ld %ld ", io_psprefix, (pslx+pshx)/2, (psly+pshy)/2);
			io_pswritestring(text);
			io_pswrite(" %ld Centerstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTTOP:
			io_pswrite("%s%ld %ld ", io_psprefix, (pslx+pshx)/2, pshy);
			io_pswritestring(text);
			io_pswrite(" %ld Topstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTBOT:
			io_pswrite("%s%ld %ld ", io_psprefix, (pslx+pshx)/2, psly);
			io_pswritestring(text);
			io_pswrite(" %ld Botstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTLEFT:
			io_pswrite("%s%ld %ld ", io_psprefix, pslx, (psly+pshy)/2);
			io_pswritestring(text);
			io_pswrite(" %ld Leftstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTRIGHT:
			io_pswrite("%s%ld %ld ", io_psprefix, pshx, (psly+pshy)/2);
			io_pswritestring(text);
			io_pswrite(" %ld Rightstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTTOPLEFT:
			io_pswrite("%s%ld %ld ", io_psprefix, pslx, pshy);
			io_pswritestring(text);
			io_pswrite(" %ld Topleftstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTTOPRIGHT:
			io_pswrite("%s%ld %ld ", io_psprefix, pshx, pshy);
			io_pswritestring(text);
			io_pswrite(" %ld Toprightstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTBOTLEFT:
			io_pswrite("%s%ld %ld ", io_psprefix, pslx, psly);
			io_pswritestring(text);
			io_pswrite(" %ld Botleftstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case TEXTBOTRIGHT:
			io_pswrite("%s%ld %ld ", io_psprefix, pshx, psly);
			io_pswritestring(text);
			io_pswrite(" %ld Botrightstring\n", io_psconvert[fnt]*PSSCALE);
			break;
		case 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", io_psconvert[fnt]*PSSCALE);
			break;
	}
}

/*
 * 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 = ((x * io_psmatrix[0][0] + y * io_psmatrix[1][0]) >> 16) + io_psmatrix[2][0];
	*psy = ((x * io_psmatrix[0][1] + y * io_psmatrix[1][1]) >> 16) + 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);
	(void)vsprintf(theline, s, ap);
	va_end(ap);

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