/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: iogdso.c
 * Input/output tool: GDS-II output
 * Written by: Sid Penstone, Queens University
 *
 * 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
 */

#include "config.h"
#include "global.h"
#include "eio.h"
#include "egraphics.h"
#include "tech.h"
#include "dbcontour.h"
#include "tecgen.h"
#include "tecart.h"
#include "edialogs.h"

/* #define OLDGDS 1 */ 

#define GDSVERSION            3
#define BYTEMASK           0xFF
#define NAMSIZE             100
#define DSIZE               512		/* data block */
#define MAXPOINTS           510		/* maximum points in a polygon */

/* GDSII bit assignments in STRANS record */
#define STRANS_REFLX     0x8000
#define STRANS_ABSA         0x2

/* data type codes */
#define DTYP_NONE             0

/* header codes */
#define HDR_HEADER       0x0002
#define HDR_BGNLIB       0x0102
#define HDR_LIBNAME      0x0206
#define HDR_UNITS        0x0305
#define HDR_ENDLIB       0x0400
#define HDR_BGNSTR       0x0502
#define HDR_STRNAME      0x0606
#define HDR_ENDSTR       0x0700
#define HDR_BOUNDARY     0x0800
#define HDR_PATH         0x0900
#define HDR_SREF         0x0A00
#define HDR_AREF	     0x0B00
#define HDR_TEXT	     0x0C00
#define HDR_LAYER        0x0D02
#define HDR_DATATYPE     0x0E02
#define HDR_XY           0x1003
#define HDR_ENDEL        0x1100
#define HDR_SNAME        0x1206
#define HDR_TEXTTYPE     0x1602
#define HDR_PRESENTATION 0x1701
#define HDR_STRING	     0x1906
#define HDR_STRANS       0x1A01
#define HDR_MAG          0x1B05
#define HDR_ANGLE        0x1C05

/* Header byte counts */
#define HDR_N_BGNLIB         28
#define HDR_N_UNITS          20
#define HDR_N_ANGLE          12

/* Maximum string sizes  */
#define HDR_M_SNAME          32
#define HDR_M_STRNAME        32
#define HDR_M_ASCII         256

/* contour gathering thresholds for polygon accumulation */
#define BESTTHRESH           0.001			/* 1/1000 of a millimeter */
#define WORSTTHRESH          0.1			/* 1/10 of a millimeter */

static char   *io_dbuffer= 0, *io_empty;	/* for buffering output data  */
static INTBIG  io_gds_curlayer;				/* Current layer for gds output */
static INTBIG  io_gds_pos;					/* Position of next byte in the buffer */
static INTBIG  io_gds_block;				/* Number data buffers output so far */
static FILE   *io_gds_outfd;				/* File descriptor for output */
static INTBIG *io_gds_outputstate;			/* current output state */
static INTBIG  io_gds_polypoints;			/* number of allowable points on a polygon */
static double  io_gds_scale;				/* constant */
static INTBIG  io_gds_layerkey;				/* key for "IO_gds_layer" */

/* prototypes for local routines */
static void io_gdswritecell(NODEPROTO*);
static void io_outputgdspoly(TECHNOLOGY*, POLYGON*, INTBIG, INTBIG, INTBIG, XARRAY, VARIABLE*);
static void io_gds_set_layer(INTBIG);
static void io_gds_initialize(void);
static void io_gds_closefile(void);
static void io_gds_bgnlib(NODEPROTO*);
static void io_gds_bgnstr(NODEPROTO*);
static void io_gds_date(time_t*);
static void io_gds_header(INTBIG, INTBIG);
static void io_gds_name(INTBIG, char*, INTBIG);
static void io_gds_angle(INTBIG);
static void io_gds_boundary(POLYGON*);
static void io_gds_path(POLYGON*);
static void io_gds_byte(char);
static void io_gds_int2(INTSML);
static void io_gds_int4(INTBIG);
static void io_gds_int2_arr(INTSML*, INTBIG);
static void io_gds_int4_arr(INTBIG*, INTBIG);
static void io_gds_string(char*, INTBIG);
static void io_gds_write_polygon(INTBIG, TECHNOLOGY*, INTBIG*, INTBIG*, INTBIG);
static char *io_gds_makename(char*);
static void io_gds_cvfloat8(double*, UINTBIG*, UINTBIG*);
static void io_gdsoptionsdlog(void);

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

/*
 * Routine to initialize GDS I/O.
 */
void io_initgds(void)
{
	extern COMCOMP io_gdsp;

	DiaDeclareHook("gdsopt", &io_gdsp, io_gdsoptionsdlog);
}

BOOLEAN io_writegdslibrary(LIBRARY *lib)
{
	char *name, *truename;
	REGISTER NODEPROTO *np;
	REGISTER VARIABLE *var;
	REGISTER TECHNOLOGY *tech;
	REGISTER LIBRARY *olib;
	REGISTER void *infstr;

	/* create the proper disk file for the GDS II */
	if (lib->curnodeproto == NONODEPROTO)
	{
		ttyputerr(_("Must be editing a facet to generate GDS output"));
		return(TRUE);
	}

	/* construct file name */
	infstr = initinfstr();
	addstringtoinfstr(infstr, lib->curnodeproto->cell->cellname);
	addstringtoinfstr(infstr, ".gds");
	(void)allocstring(&name, truepath(returninfstr(infstr)), el_tempcluster);

	/* get the file */
	io_gds_outfd = xcreate(name, io_filetypegds, _("GDS File"), &truename);
	if (io_gds_outfd == NULL)
	{
		if (truename != 0) ttyputerr(_("Cannot write %s"), truename);
		efree(name);
		return(TRUE);
	}

	/* get current output state */
	io_gds_outputstate = io_getstatebits();

	/* get the number of allowable points on a polygon */
	var = getval((INTBIG)el_curtech, VTECHNOLOGY, VINTEGER, "IO_gds_polypoints");
	if (var == NOVARIABLE) io_gds_polypoints = 200; else /* 200 is the MAX allowed in GDS-II */
		io_gds_polypoints = var->addr;

	/* initialize cache of layer information */
	for(tech = el_technologies; tech != NOTECHNOLOGY; tech = tech->nexttechnology)
	{
		var = getval((INTBIG)tech, VTECHNOLOGY, VINTEGER|VISARRAY, "IO_gds_layer_numbers");
		tech->temp1 = (var == NOVARIABLE ? 0 : var->addr);

		/* layer names for bloating */
		var = getval((INTBIG)tech, VTECHNOLOGY, VSTRING|VISARRAY, "TECH_layer_names");
		tech->temp2 = (var == NOVARIABLE ? 0 : var->addr);

		/* if ignoring DRC mask layer, delete Generic technology layer names */
		if (tech == gen_tech && (io_gds_outputstate[0]&GDSOUTADDDRC) == 0) tech->temp1 = 0;
	}

	/* make sure key for individual layer overrides is set */
	io_gds_layerkey = makekey("IO_gds_layer");

	/* warn if current technology has no GDS layers */
	if (el_curtech->temp1 == 0)
		ttyputmsg(_("Warning: there are no GDS II layers defined for the %s technology"),
			el_curtech->techname);

	if ((io_gds_outputstate[0]&GDSOUTMERGE) != 0) mrginit();

	io_gds_initialize();
	io_gds_bgnlib(lib->curnodeproto);

	/* write the GDS-II */
	for(olib = el_curlib; olib != NOLIBRARY; olib = olib->nextlibrary)
		for(np = olib->firstnodeproto; np != NONODEPROTO; np = np->nextnodeproto)
			np->temp1 = 0;
	io_gdswritecell(lib->curnodeproto);

	/* clean up */
	io_gds_header(HDR_ENDLIB, 0);
	io_gds_closefile();

	if ((io_gds_outputstate[0]&GDSOUTMERGE) != 0) mrgterm();

	/* tell the user that the file is written */
	ttyputmsg(_("%s written"), truename);
	efree(name);
	return(FALSE);
}

void io_gdswritecell(NODEPROTO *np)
{
	REGISTER NODEINST *subno;
	REGISTER ARCINST *subar;
	REGISTER NODEPROTO *subnt, *onp;
	REGISTER VARIABLE *layervar, *var;
	REGISTER INTBIG i, j, displaytotal;
	REGISTER INTBIG offx, offy, laynum, bestthresh, worstthresh;
	XARRAY trans;
	INTBIG xpos, ypos, bx, by, fx, fy, tx, ty;
	INTBIG angle, transvalue, gatherpolygon;
	static POLYGON *poly = NOPOLYGON;
	char str[513];
	REGISTER CONTOUR *con, *nextcon, *contourlist;
	REGISTER CONTOURELEMENT *conel;
#ifdef OLDGDS
	REGISTER NODEPROTO *bodysubnt;
	INTBIG cx, cy;
#endif

	/* if there are any sub-cells that have not been written, write them */
	for(subno = np->firstnodeinst; subno != NONODEINST; subno = subno->nextnodeinst)
	{
		subnt = subno->proto;
		if (subnt->primindex != 0) continue;

		/* ignore recursive references (showing icon in contents) */
		if (subnt->cell == np->cell) continue;

		/* convert body cells to contents cells */
		onp = contentsview(subnt);
		if (onp != NONODEPROTO) subnt = onp;

		/* don't recurse if this cell has already been written */
		if (subnt->temp1 != 0) continue;

		/* recurse to the bottom */
		io_gdswritecell(subnt);
	}

	/* get polygon */
	(void)needstaticpolygon(&poly, 4, io_tool->cluster);

	/* write this cell */
	io_gds_set_layer(0);
	np->temp1 = 1;
#ifdef OLDGDS
	offx = (np->lowx + np->highx) / 2;
	offy = (np->lowy + np->highy) / 2;
#else
	offx = offy = 0;
#endif
	io_gds_bgnstr(np);

	/* see if there are any outline nodes (opened polygon or circle arcs) */
	gatherpolygon = 0;
	for(subno = np->firstnodeinst; subno != NONODEINST; subno = subno->nextnodeinst)
	{
		if (subno->proto == art_openedpolygonprim || subno->proto == art_circleprim ||
			subno->proto == art_thickcircleprim)
		{
			gatherpolygon = 1;
			break;
		}
	}
	if (gatherpolygon != 0)
	{
		bestthresh = scalefromdispunit((float)BESTTHRESH, DISPUNITMM);
		worstthresh = scalefromdispunit((float)WORSTTHRESH, DISPUNITMM);
		contourlist = gathercontours(np, 0, bestthresh, worstthresh);

		/* mark all nodes not in contours as not-written */
		for(subno = np->firstnodeinst; subno != NONODEINST; subno = subno->nextnodeinst)
			subno->temp1 = 0;

		/* write the contour polygons */
		for(con = contourlist; con != NOCONTOUR; con = con->nextcontour)
		{
			poly->count = 0;
			poly->style = OPENED;
			poly->layer = 0;

			/* add in first point of contour */
			if (poly->count+1 >= poly->limit)
				(void)extendpolygon(poly, poly->count+1);
			poly->xv[poly->count] = con->firstcontourelement->sx;
			poly->yv[poly->count] = con->firstcontourelement->sy;
			poly->count++;
			layervar = NOVARIABLE;
			for(conel = con->firstcontourelement; conel != NOCONTOURELEMENT; conel = conel->nextcontourelement)
			{
				conel->ni->temp1 = 1;
				switch (conel->elementtype)
				{
					case ARCSEGMENTTYPE:
					case REVARCSEGMENTTYPE:
					case CIRCLESEGMENTTYPE:
						initcontoursegmentgeneration(conel);
						for(;;)
						{
							if (nextcontoursegmentgeneration(&fx, &fy, &tx, &ty)) break;
							if (poly->count+1 >= poly->limit)
								(void)extendpolygon(poly, poly->count+1);
							poly->xv[poly->count] = tx;
							poly->yv[poly->count] = ty;
							poly->count++;
						}
						break;
					case LINESEGMENTTYPE:
						if (poly->count+1 >= poly->limit)
							(void)extendpolygon(poly, poly->count+1);
						poly->xv[poly->count] = conel->ex;
						poly->yv[poly->count] = conel->ey;
						poly->count++;
						break;
					default:
						break;
				}
				var = getvalkey((INTBIG)conel->ni, VNODEINST, VINTEGER, io_gds_layerkey);
				if (var != NOVARIABLE) layervar = var;
			}
			io_outputgdspoly(art_tech, poly, 0, offx, offy, el_matid, layervar);
		}

		/* dispose the data */
		for(con = contourlist; con != NOCONTOUR; con = nextcon)
		{
			nextcon = con->nextcontour;
			killcontour(con);
		}
	} else
	{
		/* mark all nodes as not-written */
		for(subno = np->firstnodeinst; subno != NONODEINST; subno = subno->nextnodeinst)
			subno->temp1 = 0;
	}

	/* write all nodes in the facet */
	for(subno = np->firstnodeinst; subno != NONODEINST; subno = subno->nextnodeinst)
	{
		if (subno->temp1 != 0) continue;
		subno->temp1 = 1;

		layervar = getvalkey((INTBIG)subno, VNODEINST, VINTEGER, io_gds_layerkey);
		subnt = subno->proto;
		angle = subno->rotation;
		if (subno->transpose != 0) angle = (angle + 900) % 3600;
		makerot(subno, trans);
		if (subnt->primindex != 0)
		{
			/* don't draw anything if the node is wiped out */
			if ((subno->userbits&WIPED) != 0) continue;

			/* check for displayable variables */
			displaytotal = tech_displayablenvars(subno, NOWINDOWPART, &tech_oneprocpolyloop);
			if (displaytotal != 0)
			{
				/* get the first polygon off the node, this is the text layer */
				shapenodepoly(subno, 0, poly);

				/* determine the layer name for this polygon */
				if (poly->layer < 0 || subnt->tech->temp1 == 0) continue;
				laynum = ((INTBIG *)subnt->tech->temp1)[poly->layer];
				io_gds_set_layer(laynum);

				for (i=0; i<displaytotal; i++)
				{
					(void)tech_filldisplayablenvar(subno, poly, NOWINDOWPART, 0, &tech_oneprocpolyloop);
					/* dump this text field */
					/* note that a presentation of 8 is bottomleft */
					io_gds_header(HDR_TEXT, 0);
					io_gds_header(HDR_LAYER, io_gds_curlayer);
					io_gds_header(HDR_TEXTTYPE, 0);
					io_gds_header(HDR_PRESENTATION, 8);
					/* now the orientation */
					transvalue = 0;
					if (subno->transpose != 0)
					{
						/* Angles are reversed  */
						transvalue |= STRANS_REFLX;
						angle = (3600 - angle)%3600;
					}
					io_gds_header(HDR_STRANS, transvalue);
					io_gds_angle(angle);
					io_gds_int2(12);
					io_gds_int2(HDR_XY);
					io_gds_int4(rounddouble(io_gds_scale*(double)poly->xv[0]));
					io_gds_int4(rounddouble(io_gds_scale*(double)poly->yv[0]));

					/* now the string */
					j = strlen (poly->string);
					if (j > 512) j = 512;
					strncpy (str, poly->string, j);

					/* pad with a blank */
					if (j&1) str[j++] = ' ';
					str[j] = '\0';
					io_gds_int2((INTSML)(4+j));
					io_gds_int2(HDR_STRING);
					io_gds_string(str, j);
					io_gds_header(HDR_ENDEL, 0);

					/* write out the node the text is attached to */
					/* RLW - Queen's */
					i = nodepolys(subno, 0, NOWINDOWPART);
					for (j=0; j<i; j++)
					{
						shapenodepoly(subno, j, poly);
						io_outputgdspoly(subnt->tech, poly, angle, offx, offy, trans, layervar);
					}
				}
			} else
			{
				/* write a primitive nodeinst */
				i = nodepolys(subno, 0, NOWINDOWPART);
				for(j=0; j<i; j++)
				{
					shapenodepoly(subno, j, poly);
					io_outputgdspoly(subnt->tech, poly, angle, offx, offy, trans, layervar);
				}
			}
		} else
		{
			/* ignore recursive references (showing icon in contents) */
			if (subno->proto->cell == np->cell) continue;

			/* write a call to a cell */
#ifndef OLDGDS
			var = getvalkey((INTBIG)subno->proto, VNODEPROTO, VINTEGER|VISARRAY, el_prototype_center_key);
			if (var != NOVARIABLE)
			{
				bx = ((INTBIG *)var->addr)[0] + (subno->lowx+subno->highx)/2 -
					(subno->proto->lowx+subno->proto->highx)/2;
				by = ((INTBIG *)var->addr)[1] + (subno->lowy+subno->highy)/2 -
					(subno->proto->lowy+subno->proto->highy)/2;
				xform(bx, by, &xpos, &ypos, trans);
			} else
			{
				/* now origin, normal placement */
				bx = (subno->lowx - subno->proto->lowx);
				by = (subno->lowy - subno->proto->lowy);
				xform(bx, by, &xpos, &ypos, trans);
			}
#else
			xpos = (subno->lowx + subno->highx) / 2 - offx;
			ypos = (subno->lowy + subno->highy) / 2 - offy;

			/* convert body cells to contents cells */
			onp = contentsview(subnt);
			if (onp != NONODEPROTO)
			{
				/* look for grab points in contents and body cells */
				bodysubnt = subnt;
				subnt = onp;
				corneroffset(subno, bodysubnt, subno->rotation, subno->transpose, &bx, &by, FALSE);
				corneroffset(NONODEINST, subnt, subno->rotation, subno->transpose, &cx, &cy, FALSE);
				xpos = subno->lowx + bx - cx + (subnt->highx-subnt->lowx)/2 - offx;
				ypos = subno->lowy + by - cy + (subnt->highy-subnt->lowy)/2 - offy;
			}
#endif

			/* Generate a symbol reference  */
			io_gds_header(HDR_SREF, 0);
			io_gds_name(HDR_SNAME, io_gds_makename(subnt->cell->cellname), HDR_M_SNAME);
			transvalue = 0;
			if (subno->transpose != 0)
			{
				/* Angles are reversed */
				transvalue |= STRANS_REFLX;
				angle = (3600 - angle)%3600;
			}
#ifdef OLDGDS
			if (angle != 0) transvalue |= STRANS_ABSA;
			if (transvalue != 0) io_gds_header(HDR_STRANS, transvalue);
			if (angle != 0) io_gds_angle(angle);
#else
			/* always output the angle and transvalue */
			io_gds_header(HDR_STRANS, transvalue);
			io_gds_angle(angle);
#endif
			io_gds_int2(12);
			io_gds_int2(HDR_XY);
			io_gds_int4(rounddouble(io_gds_scale*(double)xpos));
			io_gds_int4(rounddouble(io_gds_scale*(double)ypos));
			io_gds_header(HDR_ENDEL, 0);
		}
	}

	/*  Now do the arcs in the cell  */
	for(subar = np->firstarcinst; subar != NOARCINST; subar = subar->nextarcinst)
	{
		/* arcinst: ask the technology how to draw it */
		i = arcpolys(subar, NOWINDOWPART);
		layervar = getvalkey((INTBIG)subar, VARCINST, VINTEGER, io_gds_layerkey);

		/* plot each layer of the arcinst */
		for(j=0; j<i; j++)
		{
			/* write the box describing the layer */
			shapearcpoly(subar, j, poly);
			io_outputgdspoly(subar->proto->tech, poly, 0, offx, offy, el_matid, layervar);
		}
	}
	if ((io_gds_outputstate[0]&GDSOUTMERGE) != 0)
		mrgdonefacet(io_gds_write_polygon);

	io_gds_header(HDR_ENDSTR, 0);
}

/*
 * routine to write polygon "poly" to the GDS-II file.  The polygon is from
 * technology "tech", is rotated by the angle "angle", and is offset
 * by "offx" and "offy" in its parent cell.  If "layervar" is not NOVARIABLE,
 * it is the layer number to use (an override).
 */
void io_outputgdspoly(TECHNOLOGY *tech, POLYGON *poly, INTBIG angle, INTBIG offx,
	INTBIG offy, XARRAY trans, VARIABLE *layervar)
{
	REGISTER INTBIG r, bloat;
	INTBIG i;
	INTBIG xl, xh, yl, yh, xpos, ypos, laynum;
	static POLYGON *poly2 = NOPOLYGON;
	REGISTER void *infstr;

	(void)needstaticpolygon(&poly2, 4, io_tool->cluster);

	/* determine the layer name for this polygon */
	if (layervar != NOVARIABLE) laynum = (INTBIG)layervar->addr; else
	{
		if (poly->layer < 0 || tech->temp1 == 0) return;
		laynum = ((INTBIG *)tech->temp1)[poly->layer];
		if (laynum < 0) return;
	}
	io_gds_set_layer(laynum);

	/* determine bloat factor */
	if (poly->layer < 0 || tech->temp2 == 0) bloat = 0; else
	{
		infstr = initinfstr();
		addstringtoinfstr(infstr, tech->techname);
		addtoinfstr(infstr, ':');
		addstringtoinfstr(infstr, ((char **)tech->temp2)[poly->layer]);
		bloat = io_getoutputbloat(returninfstr(infstr));
	}

	switch (poly->style)
	{
		case DISC:      /* Make a square of the size of the diameter  */
			xformpoly(poly, trans);
			r = computedistance(poly->xv[0], poly->yv[0], poly->xv[1], poly->yv[1]);
			if (r <= 0) break;
			xform(poly->xv[0], poly->yv[0], &xpos, &ypos, trans);
			xl = xpos - offx - (r + bloat/2);
			xh = xl + 2*r + bloat;
			yl = ypos - offy - (r + bloat/2);
			yh = yl + 2*r + bloat;
			makerectpoly(xl, xh, yl, yh, poly2);
			io_gds_boundary(poly2);
			return;

		default:
			if (isbox(poly, &xl,&xh, &yl,&yh))
			{
				/* rectangular manhattan shape */
				if (xh == xl || yh == yl) return;

				/* find the center of this polygon, inside the cell */
				xform((xl+xh)/2, (yl+yh)/2, &xpos, &ypos, trans);

				if ((io_gds_outputstate[0]&GDSOUTMERGE) != 0 && angle%900 == 0)
				{
					/* do polygon format output */
					if (angle%1800 == 0)
						mrgstorebox(laynum, tech, xh-xl+bloat, yh-yl+bloat, xpos-offx, ypos-offy);
					else	/* rotated by 90 degrees */
						mrgstorebox(laynum, tech, yh-yl+bloat, xh-xl+bloat, xpos-offx, ypos-offy);
				} else    /* Output as a single box */
				{
					makerectpoly(xl-bloat/2, xh+bloat/2, yl-bloat/2, yh+bloat/2, poly2);
					switch(poly->style)
					{
						case FILLEDRECT: poly2->style = FILLED;    break;
						case CLOSEDRECT: poly2->style = CLOSED;    break;
						default:         poly2->style = poly->style;
					}
					xformpoly(poly2, trans); /* in 4.05, did this only for angle = 0 */

					/* Now output the box as a set of points  */
					for (i = 0; i < poly2->count; i++)
					{
						poly2->xv[i] = poly2->xv[i] - offx;
						poly2->yv[i] = poly2->yv[i] - offy;
					}
					io_gds_boundary(poly2);
				}
				break;
			}

			/* non-manhattan or worse .. direct output */
			if (bloat != 0 && poly->count > 4)
				ttyputmsg(_("Warning: complex polygon cannot be bloated"));
			xformpoly(poly, trans); /* was missing in 4.05 (SRP) */
			if (poly->count == 1)
			{
				ttyputerr(_("Single point cannot be written in GDS-II"));
				break;
			}

			/* polygonally defined shape or a line */
			if (poly2->limit < poly->count)	 /* SRP */
				(void)extendpolygon(poly2, poly->count);

			for (i = 0; i < poly->count; i++)
			{
				poly2->xv[i] = poly->xv[i] - offx;
				poly2->yv[i] = poly->yv[i] - offy;
			}
			poly2->count = poly->count;
			if (poly->count == 2) io_gds_path(poly2); else
				io_gds_boundary(poly2);
			break;
	}
}

/*
 * routine to set the correct layer in "layer" for later output.
 */
void io_gds_set_layer(INTBIG layer)
{
	io_gds_curlayer = layer;
}

/*************************** GDS OUTPUT ROUTINES ***************************/

/* Initialize various fields, get some standard values */
void io_gds_initialize(void)
{
	INTBIG i;

	io_gds_block = 0;
	io_gds_pos = 0;
	io_gds_set_layer(0);
	if (io_dbuffer == 0)
	{
		io_dbuffer = (char *)emalloc(DSIZE, io_tool->cluster);
		io_empty = (char *)emalloc(DSIZE, io_tool->cluster);

		/* all zeroes, even if malloc() did it  */
		for (i=0; i<DSIZE;i++) io_empty[i] = 0;
	}
	io_gds_scale = 1000.0 / (double)scalefromdispunit(1.0, DISPUNITMIC);
}

/*
 * Close the file, pad to make the file match the tape format
 */
void io_gds_closefile(void)
{
	INTBIG i;

	/* Write out the current buffer */
	if (io_gds_pos > 0)
	{
		/* Pack with zeroes */
		for (i = io_gds_pos; i < DSIZE; i++) io_dbuffer[i] = 0;
		(void)xfwrite(io_dbuffer, DSIZE, 1, io_gds_outfd);
		io_gds_block++;
	}

	/*  Pad to 2048  */
	while (io_gds_block%4 != 0)
	{
		(void)xfwrite(io_empty, DSIZE, 1, io_gds_outfd);
		io_gds_block++;
	}
	xclose(io_gds_outfd);
}

/* Write a library header, get the date information */
void io_gds_bgnlib(NODEPROTO *np)
{
	char name[NAMSIZE];
	double xnum;
	static INTBIG units[4];
	/* GDS floating point values - -
	 * 0x3E418937,0x4BC6A7EF = 0.001
	 * 0x3944B82F,0xA09B5A53 = 1e-9
	 * 0x3F28F5C2,0x8F5C28F6 = 0.01
	 * 0x3A2AF31D,0xC4611874 = 1e-8
	 */

	/* set units */
	/* modified 9 april 92 - RLW */
	xnum = 1e-3;
	io_gds_cvfloat8(&xnum, (UINTBIG *)&units[0], (UINTBIG *)&units[1]);
	xnum = 1.0e-9;
	io_gds_cvfloat8(&xnum, (UINTBIG *)&units[2], (UINTBIG *)&units[3]);

	io_gds_header(HDR_HEADER, GDSVERSION);
	io_gds_header(HDR_BGNLIB, 0);
	io_gds_date((time_t *)&np->creationdate);
	io_gds_date((time_t *)&np->revisiondate);
	(void)strcpy(name, np->cell->cellname);
	io_gds_name(HDR_LIBNAME, io_gds_makename(name), HDR_M_ASCII);
	io_gds_int2(HDR_N_UNITS);
	io_gds_int2(HDR_UNITS);
	io_gds_int4_arr(units, 4);
}

void io_gds_bgnstr(NODEPROTO *np)
{
	time_t cdate, rdate;
	char name[NAMSIZE];

	io_gds_header(HDR_BGNSTR, 0);
	cdate = (time_t)np->creationdate;
	io_gds_date(&cdate);
	rdate = (time_t)np->revisiondate;
	io_gds_date(&rdate);
	(void)strcpy(name, np->cell->cellname);
	io_gds_name(HDR_STRNAME, io_gds_makename(name), HDR_M_STRNAME);
}

/* Utilities to put the data into the output file */

/* Output date array, converted from a date integer */
void io_gds_date(time_t *val)
{
	char buffer[28], save;
	INTSML date[6];

	(void)strcpy(buffer, timetostring(*val));
	date[0] = (INTSML)myatoi(&buffer[20]);      /* year */
	save = buffer[7];   buffer[7] = 0;
	date[1] = (INTSML)parsemonth(&buffer[4]);	/* month */
	buffer[7] = save;
	date[2] = (INTSML)myatoi(&buffer[8]);       /* day */
	date[3] = (INTSML)myatoi(&buffer[11]);      /* hours */
	date[4] = (INTSML)myatoi(&buffer[14]);      /* minutes */
	date[5] = (INTSML)myatoi(&buffer[17]);      /* seconds */
	io_gds_int2_arr(date, 6);
}

/*
 * Write a simple header, with a fixed length
 * Enter with the header as argument, the routine will output
 * the count, the header, and the argument (if present) in p1.
 */
void io_gds_header(INTBIG header, INTBIG p1)
{
	INTBIG type, count;

	/* func = (header>>8) & BYTEMASK; */
	type = header & BYTEMASK;
	if (type == DTYP_NONE) count = 4; else
		switch(header)
	{
		case HDR_HEADER:
		case HDR_LAYER:
		case HDR_DATATYPE:
		case HDR_TEXTTYPE:
		case HDR_STRANS:
		case HDR_PRESENTATION:
			count = 6;
			break;
		case HDR_BGNSTR:
		case HDR_BGNLIB:
			count = HDR_N_BGNLIB;
			break;
		case HDR_UNITS:
			count = HDR_N_UNITS;
			break;
		default:
			ttyputerr(_("No entry for header 0x%x"), header);
			return;
	}
	io_gds_int2((INTSML)count);
	io_gds_int2((INTSML)header);
	if (type == DTYP_NONE) return;
	if (count == 6) io_gds_int2((INTSML)p1);
	if (count == 8) io_gds_int4(p1);
}

/*
 * Add a name (STRNAME, LIBNAME, etc.) to the file. The header
 * to be used is in header; the string starts at p1
 * if there is an odd number of bytes, then output the 0 at
 * the end of the string as a pad. The maximum length of string is "max"
 */
void io_gds_name(INTBIG header, char *p1, INTBIG max)
{
	INTBIG count;

	count = mini(strlen(p1), max);
	if ((count&1) != 0) count++;
	io_gds_int2((INTSML)(count+4));
	io_gds_int2((INTSML)header);
	io_gds_string(p1, count);
}

/* Output an angle as part of a STRANS */
void io_gds_angle(INTBIG ang)
{
	double gdfloat;
	static INTBIG units[2];

	gdfloat = (double)ang / 10.0;
	io_gds_int2(HDR_N_ANGLE);
	io_gds_int2(HDR_ANGLE);
	io_gds_cvfloat8(&gdfloat, (UINTBIG *)&units[0], (UINTBIG *)&units[1]);
	io_gds_int4_arr(units, 2);
}

/* Output the pairs of XY points to the file  */
void io_gds_boundary(POLYGON *poly)
{
	INTBIG count, i, j, sofar;
	REGISTER INTBIG *xv, *yv;
	INTBIG lx, hx, ly, hy;
	POLYGON *side1, *side2;

	if (poly->count > MAXPOINTS)
	{
		getbbox(poly, &lx, &hx, &ly, &hy);
		if (hx-lx > hy-ly)
		{
			if (polysplitvert((lx+hx)/2, poly, &side1, &side2)) return;
		} else
		{
			if (polysplithoriz((ly+hy)/2, poly, &side1, &side2)) return;
		}
		io_gds_boundary(side1);
		io_gds_boundary(side2);
		freepolygon(side1);
		freepolygon(side2);
		return;
	}

	xv = poly->xv;   yv = poly->yv;
	count = poly->count;
	for(;;)
	{
		/* look for a closed section */
		for(sofar=1; sofar<count; sofar++)
			if (xv[sofar] == xv[0] && yv[sofar] == yv[0]) break;
		if (sofar < count) sofar++;

		io_gds_header(HDR_BOUNDARY, 0);
		io_gds_header(HDR_LAYER, io_gds_curlayer);
		io_gds_header(HDR_DATATYPE, 0);
		io_gds_int2((INTSML)(8 * (sofar+1) + 4));
		io_gds_int2(HDR_XY);
		for (i = 0; i <= sofar; i++)
		{
			if (i == sofar) j = 0; else j = i;
			io_gds_int4(rounddouble(io_gds_scale*(double)xv[j]));
			io_gds_int4(rounddouble(io_gds_scale*(double)yv[j]));
		}
		io_gds_header(HDR_ENDEL, 0);
		if (sofar >= count) break;
		count -= sofar;
		xv = &xv[sofar];
		yv = &yv[sofar];
	}
}

void io_gds_path(POLYGON *poly)
{
	INTBIG count, i;

	io_gds_header(HDR_PATH, 0);
	io_gds_header(HDR_LAYER, io_gds_curlayer);
	io_gds_header(HDR_DATATYPE, 0);
	count = 8 * poly->count + 4;
	io_gds_int2((INTSML)count);
	io_gds_int2(HDR_XY);
	for (i = 0; i < poly->count; i ++)
	{
		io_gds_int4(rounddouble(io_gds_scale*(double)poly->xv[i]));
		io_gds_int4(rounddouble(io_gds_scale*(double)poly->yv[i]));
	}
	io_gds_header(HDR_ENDEL, 0);
}

/*  Primitive output routines : */

/* Add one byte to the file io_gds_outfd */
void io_gds_byte(char val)
{
	io_dbuffer[io_gds_pos++] = val;
	if (io_gds_pos >= DSIZE)
	{
		(void)xfwrite(io_dbuffer, DSIZE, 1, io_gds_outfd);
		io_gds_block++;
		io_gds_pos = 0;
	}
}

/* Add a 2-byte integer */
void io_gds_int2(INTSML val)
{
	io_gds_byte((char)((val>>8)&BYTEMASK));
	io_gds_byte((char)(val&BYTEMASK));
}

/* Four byte integer */
void io_gds_int4(INTBIG val)
{
	io_gds_int2((INTSML)(val>>16));
	io_gds_int2((INTSML)val);
}

/* Array of 2 byte integers in array ptr, count n */
void io_gds_int2_arr(INTSML *ptr, INTBIG n)
{
	INTBIG i;

	for (i = 0; i < n; i++) io_gds_int2(ptr[i]);
}

/* Array of 4-byte integers or floating numbers in array  ptr, count n */
void io_gds_int4_arr(INTBIG *ptr, INTBIG n)
{
	INTBIG i;

	for (i = 0; i < n; i++) io_gds_int4(ptr[i]);
}

/*
 * String of n bytes, starting at ptr
 * Revised 90-11-23 to convert to upper case (SRP)
 */
void io_gds_string(char *ptr, INTBIG n)
{
	INTBIG i;

	for (i = 0; i < n; i++)
		io_gds_byte((char)(islower(ptr[i]) ? toupper(ptr[i]):ptr[i]));
}

/*
 * Write out a merged polygon created by the merging algorithm
 * we have to scan it to make sure that it is closed
 * We assume that it does not exceed 200 points.
 */
void io_gds_write_polygon(INTBIG layer, TECHNOLOGY *tech, INTBIG *xbuf, INTBIG *ybuf,
	INTBIG count)
{
	static POLYGON *gds_poly = NOPOLYGON;
	INTBIG i;

	/* check the number of points on the polygon */
	if (count > io_gds_polypoints)
		ttyputerr(_("WARNING: Polygon has too many points (%ld)"), count);

	(void)needstaticpolygon(&gds_poly, 4, io_tool->cluster);
	if (count > gds_poly->limit) (void)extendpolygon(gds_poly, count);
	gds_poly->count = count;
	for (i = 0; i < count; i++)
	{
		gds_poly->xv[i] = xbuf[i];
		gds_poly->yv[i] = ybuf[i];
	}
	io_gds_set_layer(layer);
	io_gds_boundary(gds_poly);       /* Now write it out */
}

/*
 * function to create proper GDSII names with restricted character set
 * from input string str.
 * Uses only 'A'-'Z', '_', $, ?, and '0'-'9'
 */
char *io_gds_makename(char *str)
{
	char *k, ch;
	REGISTER void *infstr;

	/* filter the name string for the GDS output cell */
	infstr = initinfstr();
	for (k = str; *k != 0; k++)
	{
		ch = *k;
		if (islower(ch))
		{
			/* upcase all */
			ch = toupper(ch);
		} else if (ch != '$' && !isdigit(ch) && ch != '?' && !isupper(ch))
		{
			ch = '_';
		}
		addtoinfstr(infstr, ch);
	}
	return(returninfstr(infstr));
}

/*
 * Create 8-byte GDS representation of a floating point number
 */
void io_gds_cvfloat8(double *a, UINTBIG *m, UINTBIG *n)
{
	double temp, top, frac;
	BOOLEAN negsign;
	INTBIG exponent, i;
	UINTBIG highmantissa;

	/* handle default */
	if (*a == 0)
	{
		*m = 0x40000000;
		*n = 0;
		return;
	}

	/* identify sign */
	temp = *a;
	if (temp < 0)
	{
		negsign = TRUE;
		temp = -temp;
	} else negsign = FALSE;

	/* establish the excess-64 exponent value */
	exponent = 64;

	/* scale the exponent and mantissa */
	for (; temp < 0.0625 && exponent > 0; exponent--) temp *= 16.0;

	if (exponent == 0) ttyputerr(_("Exponent underflow"));

	for (; temp >= 1 && exponent < 128; exponent++) temp /= 16.0;

	if (exponent > 127) ttyputerr(_("Exponent overflow"));

	/* set the sign */
	if (negsign) exponent |= 0x80;

	/* convert temp to 7-byte binary integer */
	top = temp;
	for (i = 0; i < 24; i++) top *= 2;
	highmantissa = (UINTBIG)top;
	frac = top - highmantissa;
	for (i = 0; i < 32; i++) frac *= 2;
	*n = (UINTBIG)frac;   *m = highmantissa | (exponent<<24);
}

/* GDS Options */
static DIALOGITEM io_gdsoptionsdialogitems[] =
{
 /*  1 */ {0, {224,344,248,416}, BUTTON, N_("OK")},
 /*  2 */ {0, {224,248,248,320}, BUTTON, N_("Cancel")},
 /*  3 */ {0, {8,16,248,231}, SCROLL, ""},
 /*  4 */ {0, {8,240,24,320}, MESSAGE, N_("GDS Layer:")},
 /*  5 */ {0, {8,324,24,430}, EDITTEXT, ""},
 /*  6 */ {0, {32,240,48,464}, CHECK, N_("Input Includes Text")},
 /*  7 */ {0, {56,240,72,464}, CHECK, N_("Input Expands Facets")},
 /*  8 */ {0, {80,240,96,464}, CHECK, N_("Input Instantiates Arrays")},
 /*  9 */ {0, {128,240,144,464}, CHECK, N_("Output Merges Boxes")},
 /* 10 */ {0, {152,240,168,430}, MESSAGE, N_("Output Arc Conversion:")},
 /* 11 */ {0, {176,250,192,394}, MESSAGE, N_("Maximum arc angle:")},
 /* 12 */ {0, {176,396,192,464}, EDITTEXT, ""},
 /* 13 */ {0, {200,250,216,394}, MESSAGE, N_("Maximum arc sag:")},
 /* 14 */ {0, {200,396,216,464}, EDITTEXT, ""},
 /* 15 */ {0, {104,240,120,464}, CHECK, N_("Input Ignores Unknown Layers")}
};
static DIALOG io_gdsoptionsdialog = {{50,75,307,548}, N_("GDS Options"), 0, 15, io_gdsoptionsdialogitems, 0, 0};

/* special items for the "GDS Options" dialog: */
#define DGDO_LAYERLIST       3		/* Layer list (scroll list) */
#define DGDO_NEWLAYER        5		/* New layer (edit text) */
#define DGDO_IINCLUDETEXT    6		/* Input Includes Text (check) */
#define DGDO_IEXPANDFACET    7		/* Input Expands Facets (check) */
#define DGDO_IINSTARRAY      8		/* Input Instantiates Arrays (check) */
#define DGDO_OMERGEBOX       9		/* Output Merges Boxes (check) */
#define DGDO_OARCANGLE      12		/* Output Arc Angle (edit text) */
#define DGDO_OARCSAG        14		/* Output Arc Sag (edit text) */
#define DGDO_IIGNOREUNKNOWN 15		/* Input Ignores unknown layers (check) */

void io_gdsoptionsdlog(void)
{
	REGISTER INTBIG i, itemHit, *layernumbers, *curstate, val, newres, newsag, numberschanged;
	INTBIG arcres, arcsag, newstate[NUMIOSTATEBITWORDS];
	char buf[50];
	REGISTER VARIABLE *gdsvar;
	REGISTER void *infstr, *dia;

	dia = DiaInitDialog(&io_gdsoptionsdialog);
	if (dia == 0) return;
	DiaInitTextDialog(dia, DGDO_LAYERLIST, DiaNullDlogList, DiaNullDlogItem, DiaNullDlogDone, 0,
		SCSELMOUSE|SCREPORT);
	layernumbers = (INTBIG *)emalloc(el_curtech->layercount * SIZEOFINTBIG, el_tempcluster);
	gdsvar = getval((INTBIG)el_curtech, VTECHNOLOGY, VINTEGER|VISARRAY,
		"IO_gds_layer_numbers");
	for(i=0; i<el_curtech->layercount; i++)
	{
		if (gdsvar == NOVARIABLE) val = -1; else
			val = ((INTBIG *)gdsvar->addr)[i];
		layernumbers[i] = val;
		infstr = initinfstr();
		addstringtoinfstr(infstr, layername(el_curtech, i));
		sprintf(buf, " (%ld)", val);
		addstringtoinfstr(infstr, buf);
		DiaStuffLine(dia, DGDO_LAYERLIST, returninfstr(infstr));
	}
	DiaSelectLine(dia, DGDO_LAYERLIST, 0);
	sprintf(buf, "%ld", layernumbers[0]);
	DiaSetText(dia, DGDO_NEWLAYER, buf);
	curstate = io_getstatebits();
	for(i=0; i<NUMIOSTATEBITWORDS; i++) newstate[i] = curstate[i];
	if ((curstate[0]&GDSINTEXT) != 0) DiaSetControl(dia, DGDO_IINCLUDETEXT, 1);
	if ((curstate[0]&GDSINEXPAND) != 0) DiaSetControl(dia, DGDO_IEXPANDFACET, 1);
	if ((curstate[0]&GDSINARRAYS) != 0) DiaSetControl(dia, DGDO_IINSTARRAY, 1);
	if ((curstate[0]&GDSOUTMERGE) != 0) DiaSetControl(dia, DGDO_OMERGEBOX, 1);
	if ((curstate[0]&GDSINIGNOREUKN) != 0) DiaSetControl(dia, DGDO_IIGNOREUNKNOWN, 1);
	getcontoursegmentparameters(&arcres, &arcsag);
	DiaSetText(dia, DGDO_OARCANGLE, frtoa(arcres*WHOLE/10));
	DiaSetText(dia, DGDO_OARCSAG, latoa(arcsag, 0));

	/* loop until done */
	numberschanged = 0;
	for(;;)
	{
		itemHit = DiaNextHit(dia);
		if (itemHit == OK || itemHit == CANCEL) break;
		if (itemHit == DGDO_IINCLUDETEXT || itemHit == DGDO_IEXPANDFACET ||
			itemHit == DGDO_IINSTARRAY || itemHit == DGDO_OMERGEBOX ||
			itemHit == DGDO_IIGNOREUNKNOWN)
		{
			DiaSetControl(dia, itemHit, 1 - DiaGetControl(dia, itemHit));
			continue;
		}
		if (itemHit == DGDO_LAYERLIST)
		{
			i = DiaGetCurLine(dia, DGDO_LAYERLIST);
			sprintf(buf, "%ld", layernumbers[i]);
			DiaSetText(dia, DGDO_NEWLAYER, buf);
			continue;
		}
		if (itemHit == DGDO_NEWLAYER)
		{
			i = DiaGetCurLine(dia, DGDO_LAYERLIST);
			val = atoi(DiaGetText(dia, DGDO_NEWLAYER));
			if (val == layernumbers[i]) continue;
			numberschanged++;
			layernumbers[i] = val;
			infstr = initinfstr();
			addstringtoinfstr(infstr, layername(el_curtech, i));
			sprintf(buf, " (%ld)", val);
			addstringtoinfstr(infstr, buf);
			DiaSetScrollLine(dia, DGDO_LAYERLIST, i, returninfstr(infstr));
			continue;
		}
	}

	if (itemHit != CANCEL)
	{
		if (DiaGetControl(dia, DGDO_IINCLUDETEXT) != 0) newstate[0] |= GDSINTEXT; else
			newstate[0] &= ~GDSINTEXT;
		if (DiaGetControl(dia, DGDO_IEXPANDFACET) != 0) newstate[0] |= GDSINEXPAND; else
			newstate[0] &= ~GDSINEXPAND;
		if (DiaGetControl(dia, DGDO_IINSTARRAY) != 0) newstate[0] |= GDSINARRAYS; else
			newstate[0] &= ~GDSINARRAYS;
		if (DiaGetControl(dia, DGDO_OMERGEBOX) != 0) newstate[0] |= GDSOUTMERGE; else
			newstate[0] &= ~GDSOUTMERGE;
		if (DiaGetControl(dia, DGDO_IIGNOREUNKNOWN) != 0) newstate[0] |= GDSINIGNOREUKN; else
			newstate[0] &= ~GDSINIGNOREUKN;
		for(i=0; i<NUMIOSTATEBITWORDS; i++) if (curstate[i] != newstate[i]) break;
		if (i < NUMIOSTATEBITWORDS) io_setstatebits(newstate);
		if (numberschanged != 0)
			setval((INTBIG)el_curtech, VTECHNOLOGY, "IO_gds_layer_numbers",
				(INTBIG)layernumbers, VINTEGER|VISARRAY|
					(el_curtech->layercount<<VLENGTHSH));
		newres = atofr(DiaGetText(dia, DGDO_OARCANGLE)) * 10 / WHOLE;
		newsag = atola(DiaGetText(dia, DGDO_OARCSAG), 0);
		if (newres != arcres || newsag != arcsag)
			setcontoursegmentparameters(newres, newsag);
	}
	efree((char *)layernumbers);
	DiaDoneDialog(dia);
}
