/*-----------------------------------------------------------------------*/
/* files.c --- file handling routines for xcircuit			 */
/* Copyright (c) 2002  Tim Edwards, Johns Hopkins University        	 */
/*-----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <pwd.h>
#include <ctype.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#ifdef TCL_WRAPPER 
#include <tk.h>
#else
#include "Xw/TextEdit.h"   /* for XwTextCopyBuffer() */
#endif

/*------------------------------------------------------------------------*/
/* Local includes                                                         */
/*------------------------------------------------------------------------*/

#include "colordefs.h"
#include "xcircuit.h"

/*----------------------------------------------------------------------*/
/* Function prototype declarations                                      */
/*----------------------------------------------------------------------*/
#include "prototypes.h"

#ifdef ASG
extern void Route(Clientdata *, Boolean);
extern int ReadHSpice(char *);
#endif

/*------------------------------------------------------------------------*/
/* Useful (local) defines						  */
/*------------------------------------------------------------------------*/

#define OUTPUTWIDTH 80	/* maximum text width of output */

#define S_OBLIQUE   13	/* position of Symbol-Oblique in font array */

#define VEPS	    1e-3

/*------------------------------------------------------------------------*/
/* External Variable definitions                                          */
/*------------------------------------------------------------------------*/

#ifdef TCL_WRAPPER
extern Tcl_Interp *xcinterp;
#endif

extern char _STR2[250], _STR[150];
extern Globaldata xobjs;
extern Clientdata areastruct;
extern fontinfo *fonts;
extern short fontcount;
extern XtAppContext app;
extern Display *dpy;
extern Window win;
extern short eventmode;
extern short beeper;
extern int *appcolors;
extern int number_colors;
extern colorindex *colorlist;

/*------------------------------------------------------*/
/* Global variable definitions				*/
/*------------------------------------------------------*/

float version;

/* Structure for remembering what names refer to the same object */

aliasptr aliastop;
   
/*------------------------------------------------------*/
/* Simple utility---get rid of newline character	*/
/*------------------------------------------------------*/

char *ridnewline(char *sptr)
{
   char *tstrp;

   for (tstrp = sptr; *tstrp != '\0' && *tstrp != '\n'; tstrp++);
   if (*tstrp == '\n') *tstrp = '\0';
   return tstrp;
}

/*------------------------------------------------------*/
/* Free memory allocated to a label string		*/
/*------------------------------------------------------*/

void freelabel(stringpart *string)
{
   stringpart *strptr = string, *tmpptr;

   while (strptr != NULL) {
      if (strptr->type == TEXT_STRING || strptr->type == PARAM_START)
         free(strptr->data.string);
      tmpptr = strptr->nextpart;
      free(strptr);
      strptr = tmpptr;
   }
}

/*------------------------------------------------------*/
/* Free memory for a single object			*/
/*------------------------------------------------------*/

void free_single(genericptr *genobj)
{
   objinstptr geninst;
   oparamptr ops, fops;

   if (IS_POLYGON(*genobj)) free(((polyptr)(*genobj))->points);
   else if (IS_LABEL(*genobj)) freelabel(((labelptr)(*genobj))->string);
   else if (IS_PATH(*genobj)) free(((pathptr)(*genobj))->plist);
   else if (IS_OBJINST(*genobj)) {
      geninst = TOOBJINST(genobj);
      ops = geninst->params;
      while (ops != NULL) {
	 switch(ops->type) {
	    case XC_STRING:
	       freelabel(ops->parameter.string);
	       break;
	    case XC_EXPR:
	       free(ops->parameter.expr);
	       break;
	 }
	 free(ops->key);
	 fops = ops;
	 ops = ops->next;
	 free(fops);
      }
   }
   free(*genobj);
}

/*---------------------------------------------------------*/
/* Reset an object structure by freeing all alloc'd memory */
/*---------------------------------------------------------*/

void reset(objectptr localdata, short mode)
{
   short i;

   if (localdata->polygons != NULL || localdata->labels != NULL)
      destroynets(localdata);

   if (localdata->parts > 0) {
      genericptr *genobj;

      if (mode != SAVE) {
	 for (genobj = localdata->plist; genobj < localdata->plist
	        + localdata->parts; genobj++)

	    /* (*genobj == NULL) only on library pages		*/
	    /* where the instances are kept in the library	*/
	    /* definition, and are only referenced on the page.	*/

	    if (*genobj != NULL)
	       free_single(genobj);
      }
      free(localdata->plist);
      
      removeparams(localdata);

      initmem(localdata);
      if (mode == DESTROY)
	 free(localdata->plist);
   }
}

/*---------------------------------------------------------*/

void pagereset(short rpage)
{
   /* free alloc'd filename */

   if (xobjs.pagelist[rpage]->filename != NULL)
      free(xobjs.pagelist[rpage]->filename);

   if (xobjs.pagelist[rpage]->background.name != NULL)
      free(xobjs.pagelist[rpage]->background.name);

   clearselects();

   /* New pages pick up their properties from page 0, which can be changed */
   /* from the .xcircuitrc file on startup (or loaded from a script).	   */
   /* Thanks to Norman Werner (norman.werner@student.uni-magdeburg.de) for */
   /* pointing out this more obvious way of doing the reset, and providing */
   /* a patch.								   */

   xobjs.pagelist[rpage]->wirewidth = xobjs.pagelist[0]->wirewidth;
   xobjs.pagelist[rpage]->orient = xobjs.pagelist[0]->orient;
   xobjs.pagelist[rpage]->pmode = xobjs.pagelist[0]->pmode;
   xobjs.pagelist[rpage]->outscale = xobjs.pagelist[0]->outscale;
   xobjs.pagelist[rpage]->filename = (char *)malloc
		((1 + strlen(xobjs.pagelist[rpage]->pageinst->thisobject->name))
		* sizeof(char));
   xobjs.pagelist[rpage]->background.name = (char *)NULL;

   strcpy(xobjs.pagelist[rpage]->filename,
		xobjs.pagelist[rpage]->pageinst->thisobject->name);
   xobjs.pagelist[rpage]->drawingscale.x = xobjs.pagelist[0]->drawingscale.x;
   xobjs.pagelist[rpage]->drawingscale.y = xobjs.pagelist[0]->drawingscale.y;
   xobjs.pagelist[rpage]->gridspace = xobjs.pagelist[0]->gridspace;
   xobjs.pagelist[rpage]->snapspace = xobjs.pagelist[0]->snapspace;

   if (xobjs.pagelist[0]->coordstyle == CM) {
      xobjs.pagelist[rpage]->coordstyle = CM;
      xobjs.pagelist[rpage]->pagesize.x = 595;
      xobjs.pagelist[rpage]->pagesize.y = 842;  /* A4 */
   }
   else {
      xobjs.pagelist[rpage]->coordstyle = FRAC_INCH;
      xobjs.pagelist[rpage]->pagesize.x = 612;	/* letter */
      xobjs.pagelist[rpage]->pagesize.y = 792;
   }
}

/*---------------------------------------------------------*/

void initmem(objectptr localdata)
{
   localdata->parts = 0;
   localdata->plist = (genericptr *)malloc(sizeof(genericptr));
   localdata->hidden = False;
   localdata->changes = 0;
   localdata->params = NULL;

   localdata->viewscale = 0.5;
   localdata->pcorner.x = -areastruct.width;
   localdata->pcorner.y = -areastruct.height; 
   localdata->bbox.width = 0;
   localdata->bbox.height = 0;
   localdata->bbox.lowerleft.x = 0;
   localdata->bbox.lowerleft.y = 0;

   localdata->highlight.netlist = NULL;
   localdata->highlight.thisinst = NULL;
   localdata->schemtype = PRIMARY;
   localdata->symschem = NULL;
   localdata->netnames = NULL;
   localdata->polygons = NULL;
   localdata->labels = NULL;
   localdata->ports = NULL;
   localdata->calls = NULL;
   localdata->valid = False;
   localdata->traversed = False;
   localdata->devname = NULL;
}

/*--------------------------------------------------------------*/
/* Exhaustively compare the contents of two objects and return  */
/* true if equivalent, false if not.				*/
/*--------------------------------------------------------------*/

Boolean elemcompare(genericptr *compgen, genericptr *gchk)
{
   Boolean bres;
   switch(ELEMENTTYPE(*compgen)) {
      case(ARC):
	 bres = (TOARC(compgen)->position.x == TOARC(gchk)->position.x &&
            TOARC(compgen)->position.y == TOARC(gchk)->position.y &&
	    TOARC(compgen)->style == TOARC(gchk)->style &&
	    TOARC(compgen)->width == TOARC(gchk)->width &&
	    abs(TOARC(compgen)->radius) == abs(TOARC(gchk)->radius) &&
	    TOARC(compgen)->yaxis  == TOARC(gchk)->yaxis &&
	    TOARC(compgen)->angle1 == TOARC(gchk)->angle1 &&
	    TOARC(compgen)->angle2 == TOARC(gchk)->angle2);
	 break;
      case(SPLINE):
	 bres = (TOSPLINE(compgen)->style == TOSPLINE(gchk)->style &&
	    TOSPLINE(compgen)->width == TOSPLINE(gchk)->width &&
	    TOSPLINE(compgen)->ctrl[0].x == TOSPLINE(gchk)->ctrl[0].x &&
	    TOSPLINE(compgen)->ctrl[0].y == TOSPLINE(gchk)->ctrl[0].y &&
	    TOSPLINE(compgen)->ctrl[1].x == TOSPLINE(gchk)->ctrl[1].x &&
	    TOSPLINE(compgen)->ctrl[1].y == TOSPLINE(gchk)->ctrl[1].y &&
	    TOSPLINE(compgen)->ctrl[2].x == TOSPLINE(gchk)->ctrl[2].x &&
	    TOSPLINE(compgen)->ctrl[2].y == TOSPLINE(gchk)->ctrl[2].y &&
	    TOSPLINE(compgen)->ctrl[3].x == TOSPLINE(gchk)->ctrl[3].x &&
	    TOSPLINE(compgen)->ctrl[3].y == TOSPLINE(gchk)->ctrl[3].y);
	 break;
      case(POLYGON): {
	 int i;
	 if (TOPOLY(compgen)->style == TOPOLY(gchk)->style &&
	       TOPOLY(compgen)->width == TOPOLY(gchk)->width &&
	       TOPOLY(compgen)->number == TOPOLY(gchk)->number) {
	    for (i = 0; i < TOPOLY(compgen)->number; i++) {
	       if (TOPOLY(compgen)->points[i].x != TOPOLY(gchk)->points[i].x
		     || TOPOLY(compgen)->points[i].y != TOPOLY(gchk)->points[i].y)
		  break;
	    }
	    bres = (i == TOPOLY(compgen)->number);
	 }
	 else bres = False;
	 }break;
   }
   return bres;
}

/*--------------------------------------------------------------*/
/* Compare any element with any other element.			*/
/*--------------------------------------------------------------*/

Boolean compare_single(genericptr *compgen, genericptr *gchk)
{
   Boolean bres = False;

   if ((*gchk)->type == (*compgen)->type) {
      switch(ELEMENTTYPE(*compgen)) {
	 case(OBJINST):{
	    objinst *newobj = TOOBJINST(compgen);
	    objinst *oldobj = TOOBJINST(gchk);
	    bres = (newobj->position.x == oldobj->position.x &&
	    		newobj->position.y == oldobj->position.y &&
	     		newobj->rotation == oldobj->rotation &&
	     		newobj->scale == oldobj->scale &&
	     		newobj->thisobject == oldobj->thisobject);
	    } break;
	 case(LABEL):
	    bres = (TOLABEL(compgen)->position.x == TOLABEL(gchk)->position.x &&
	    		TOLABEL(compgen)->position.y == TOLABEL(gchk)->position.y &&
	     		TOLABEL(compgen)->rotation == TOLABEL(gchk)->rotation &&
	     		TOLABEL(compgen)->scale == TOLABEL(gchk)->scale &&
	     		TOLABEL(compgen)->justify == TOLABEL(gchk)->justify &&
			((TOLABEL(compgen)->pin == TOLABEL(gchk)->pin) ||
				!areastruct.schemon) &&
	     		!stringcomp(TOLABEL(compgen)->string, TOLABEL(gchk)->string));
	    break;
	 case(PATH): /* elements *must* be in same order for a path */
	    bres = (TOPATH(compgen)->parts == TOPATH(gchk)->parts &&
			TOPATH(compgen)->style == TOPATH(gchk)->style &&
			TOPATH(compgen)->width == TOPATH(gchk)->width);
	    if (bres) {
	       genericptr *pathchk, *gpath;
	       for (pathchk = TOPATH(compgen)->plist, gpath =
			   TOPATH(gchk)->plist; pathchk < TOPATH(compgen)->plist
			   + TOPATH(compgen)->parts; pathchk++, gpath++) {
		  if (!elemcompare(pathchk, gpath)) bres = False;
	       }
	    }
	    break;
	 case(ARC): case(SPLINE): case(POLYGON):
	    bres = elemcompare(compgen, gchk);
	    break;
      }
   }
   return bres;
}

/*--------------------------------------------------------------------*/

short objcompare(objectptr obja, objectptr objb)
{
   genericptr *compgen, *glist, *gchk, *remg;
   short      csize;
   Boolean    bres;

   /* quick check on equivalence of number of objects */

   if (obja->parts != objb->parts) return False;

   /* check equivalence of parameters.  Parameters need not be in any	*/
   /* order; they must only match by key and value.			*/

   if (obja->params == NULL && objb->params != NULL) return False;
   else if (obja->params != NULL && objb->params == NULL) return False;
   else if (obja->params != NULL || objb->params != NULL) {
     oparamptr opsa, opsb;
     for (opsa = obja->params; opsa != NULL; opsa = opsa->next) {
	 opsb = match_param(objb, opsa->key);
	 if (opsb == NULL) return False;
	 else if (opsa->type != opsb->type) return False;
	 switch (opsa->type) {
	    case XC_STRING:
	       if (stringcomp(opsa->parameter.string, opsb->parameter.string))
		  return False;
	       break;
	    case XC_EXPR:
	       if (strcmp(opsa->parameter.expr, opsb->parameter.expr))
		  return False;
	       break;
	    case XC_INT: case XC_FLOAT:
	       if (opsa->parameter.ivalue != opsb->parameter.ivalue)
		  return False;
	       break;
	 }
      }
   }

   /* For the exhaustive check we must match component for component. */
   /* Best not to assume that elements are in same order for both.    */

   csize = obja->parts;

   glist = (genericptr *)malloc(csize * sizeof(genericptr));
   for (compgen = objb->plist; compgen < objb->plist + csize; compgen++)
      (*(glist + (int)(compgen - objb->plist))) = *compgen;
   for (compgen = obja->plist; compgen < obja->plist + obja->parts;
	 compgen++) {
      bres = False;
      for (gchk = glist; gchk < glist + csize; gchk++) {
	 if ((*compgen)->color == (*gchk)->color)
	    bres = compare_single(compgen, gchk);
	 if (bres) {
	   csize--;
	   for (remg = gchk; remg < glist + csize; remg++)
               *remg = *(remg + 1);
           break;
	 }
      }
   }
   free(glist);
   if (csize != 0) return False;

   /* Both objects cannot attempt to set an associated schematic/symbol to  */
   /* separate objects, although it is okay for one to make the association */
   /* and the other not to.						    */

   if (obja->symschem != NULL && objb->symschem != NULL)
      if (obja->symschem != objb->symschem)
	 return False;

   return(True);
}

/*------------------------*/
/* scale renormalization  */
/*------------------------*/

float getpsscale(float value, short page)
{
   if (xobjs.pagelist[page]->coordstyle == FRAC_INCH ||
 	 xobjs.pagelist[page]->coordstyle == DEC_INCH)
      return (value * INCHSCALE);
   else
      return (value * CMSCALE);
}

/*---------------------------------------------------------------*/
/* Keep track of columns of output and split lines when too long */
/*---------------------------------------------------------------*/

void dostcount(FILE *ps, short *count, short addlength)
{
   *count += addlength;
   if (*count > OUTPUTWIDTH) {
      *count = addlength;
      fprintf(ps, "\n");
   }
}

/*----------------------------------------------------------------------*/
/* Write a numerical value as a string to _STR, making a parameter	*/
/* substitution if appropriate.						*/
/*----------------------------------------------------------------------*/

void varpcheck(FILE *ps, short value, objectptr localdata, int pointno,
	short *stptr, genericptr thiselem, u_char which)
{
   oparamptr ops;
   eparamptr epp;
   Boolean done = False;

   for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
      if (epp->pdata.pointno != pointno) continue;
      ops = match_param(localdata, epp->key);
      if (ops != NULL && (ops->which == which)) {
	 sprintf(_STR, "%s ", epp->key);
	 done = True;
	 break;
      }
   }
      
   if (!done)
      sprintf(_STR, "%d ", (int)value);

   dostcount (ps, stptr, strlen(_STR));
   fputs(_STR, ps);
}

/*----------------------------------------------------------------------*/
/* like varpcheck(), but without pointnumber				*/
/*----------------------------------------------------------------------*/

void varcheck(FILE *ps, short value, objectptr localdata,
	short *stptr, genericptr thiselem, u_char which)
{
   varpcheck(ps, value, localdata, 0, stptr, thiselem, which);
}

/*----------------------------------------------------------------------*/
/* like varcheck(), but for floating-point values			*/
/*----------------------------------------------------------------------*/

void varfcheck(FILE *ps, float value, objectptr localdata, short *stptr,
	genericptr thiselem, u_char which)
{
   oparamptr ops;
   eparamptr epp;
   Boolean done = False;

   for (epp = thiselem->passed; epp != NULL; epp = epp->next) {
      ops = match_param(localdata, epp->key);
      if (ops != NULL && (ops->which == which)) {
	 sprintf(_STR, "%s ", epp->key);
	 done = True;
	 break;
      }
   }
   
   if (!done)
      sprintf(_STR, "%3.2f ", value);

   dostcount (ps, stptr, strlen(_STR));
   fputs(_STR, ps);
}

/* Structure used to hold data specific to each load mode.  See	*/
/* xcircuit.h for the list of load modes (enum loadmodes)	*/

typedef struct _loaddata {
    void (*func)();		/* Routine to run to load the file */
    char *prompt;		/* Substring name of action, for prompting */
    char *filext;		/* Default extention of file to load */
} loaddata;

/*-------------------------------------------------------*/
/* Load a PostScript or Python (interpreter script) file */
/*-------------------------------------------------------*/

void getfile(xcWidget button, pointertype mode, caddr_t nulldata)
{
   static loaddata loadmodes[LOAD_MODES] = {
	{startloadfile,  "load",    "ps"},	/* mode NORMAL */
	{importfile,     "import",  "ps"},	/* mode IMPORT */
	{loadbackground, "render",  "ps"},	/* mode PSBKGROUND */
#ifdef HAVE_PYTHON
	{execscript,	"execute",  "py"},
#else
	{execscript,	"execute",    ""},	/* mode SCRIPT */
#endif
	{crashrecover,	"recover",  "ps"},	/* mode RECOVER */
#ifdef ASG
	{importspice,	"import", "spice"},	/* mode IMPORTSPICE */
#endif
   };

   buttonsave *savebutton;
   char *promptstr = NULL;
   char strext[10];
   int idx = (int)mode;

   if (is_page(topobject) == -1) {
      Wprintf("Can only read file into top-level page!");
      return;
   }
   else if (idx >= LOAD_MODES) {
      Wprintf("Unknown mode passed to routine getfile()\n");
      return;
   }
   savebutton = (buttonsave *)malloc(sizeof(buttonsave));
   getgeneric(savebutton, button, getfile, (void *)mode);
   if (idx == RECOVER) {
      char *cfile = getcrashfilename();
      promptstr = (char *)malloc(18 + ((cfile == NULL) ? 9 : strlen(cfile)));
      sprintf(promptstr, "Recover file \'%s\'?", (cfile == NULL) ? "(unknown)" : cfile);
      popupprompt(button, promptstr, NULL, loadmodes[idx].func, savebutton, NULL);
      if (cfile) free(cfile);
   }
   else {
      promptstr = (char *)malloc(18 + strlen(loadmodes[idx].prompt));
      sprintf(promptstr, "Select file to %s:", loadmodes[idx].prompt);
      popupprompt(button, promptstr, "\0", loadmodes[idx].func,
		savebutton, loadmodes[idx].filext);
   }
   free(promptstr);
}

/*--------------------------------------------------------------*/
/* Tilde ('~') expansion in file name (stored in _STR)		*/
/*--------------------------------------------------------------*/

Boolean xc_tilde_expand(char *filename)
{
   struct passwd *passwd;
   char *username = NULL, *expanded, *sptr;

   if (*filename == '~') {
      sptr = filename + 1;
      if (*sptr == '/' || *sptr == ' ' || *sptr == '\0')
	 username = getenv("HOME");
      else {
	 for (; *sptr != '/' && *sptr != '\0'; sptr++);
	 if (*sptr == '\0') *(sptr + 1) = '\0';
	 *sptr = '\0';

	 passwd = getpwnam(filename + 1);
	 if (passwd != NULL)
	    username = passwd->pw_dir;

	 *sptr = '/';
      }
      if (username != NULL) {
	 expanded = (char *)malloc(strlen(username) +
		strlen(filename));
	 strcpy(expanded, username);
         strcat(expanded, sptr);
	 strcpy(filename, expanded);
         free(expanded);
      }
      return True;
   }
   return False;
} 

/*---------------------------------------------------------*/

Boolean nextfilename()	/* extract next filename from _STR2 into _STR */
{
   char *cptr, *slptr;

   sprintf(_STR, "%.149s", _STR2);
   if ((cptr = strrchr(_STR2, ',')) != NULL) {
      slptr = strrchr(_STR, '/');
      if (slptr == NULL || ((slptr - _STR) > (cptr - _STR2))) slptr = _STR - 1;
      sprintf(slptr + 1, "%s", cptr + 1); 
      *cptr = '\0';
      return True;
   }
   else return False;
}

/*---------------------------------------------------------*/

void loadfontlib()
{
   loadlibrary(FONTLIB);
}

/*------------------------------------------------------*/
/* Handle library loading and refresh current page if 	*/
/* it is a library page that just changed.		*/
/*------------------------------------------------------*/

void loadglib(Boolean lflag, short ilib, short tlib)
{
   while (nextfilename()) {
      if (lflag)
	 lflag = False;
      else
         ilib = createlibrary();
      loadlibrary(ilib);
      /* if (ilib == tlib) zoomview(NULL, NULL, NULL); */
   }
   if (lflag)
      lflag = False;
   else
      ilib = createlibrary();
   loadlibrary(ilib);
   /* if (ilib == tlib) zoomview(NULL, NULL, NULL); */
}

/*------------------------------------------------------*/
/* Load new library:  Create new library page and load	*/
/* to it.						*/
/*------------------------------------------------------*/

void loadulib()
{
   loadglib(False, (short)0, (short)is_library(topobject) + LIBRARY);
}

/*-----------------------------------------------------------*/
/* Add to library:  If on library page, add to that library. */
/* Otherwise, create a new library page and load to it.	     */
/*-----------------------------------------------------------*/

void loadblib()
{
   short ilib, tlib;
   Boolean lflag = True;

   /* Flag whether current page is a library page or not */

   if ((tlib = is_library(topobject)) < 0) {
      ilib = LIBRARY;
      lflag = False;
   }
   else
      ilib = tlib + LIBRARY;

   loadglib(lflag, ilib, tlib + LIBRARY);
}

/*---------------------------------------------------------*/

void getlib(xcWidget button, caddr_t clientdata, caddr_t nulldata)
{
   buttonsave *savebutton = (buttonsave *)malloc(sizeof(buttonsave));
   getgeneric(savebutton, button, getlib, NULL);
   popupprompt(button, "Enter library to load:", "\0", loadblib, savebutton,
	"lps");
}

/*---------------------------------------------------------*/

void getuserlib(xcWidget button, caddr_t clientdata, caddr_t nulldata)
{
   buttonsave *savebutton = (buttonsave *)malloc(sizeof(buttonsave));
   getgeneric(savebutton, button, getuserlib, NULL);
   popupprompt(button, "Enter library to load:", "\0", loadulib, savebutton,
	"lps");
}

/*------------------------------------------------------*/
/* Add a new name to the list of aliases for an object	*/
/*------------------------------------------------------*/

Boolean addalias(objectptr thisobj, char *newname)
{
   aliasptr aref;
   slistptr sref;
   Boolean retval = False;
   char *origname = thisobj->name;

   for (aref = aliastop; aref != NULL; aref = aref->next)
      if (aref->baseobj == thisobj)
	 break;

   /* An equivalence, not an alias */
   if (!strcmp(origname, newname)) return True;

   if (aref == NULL) {	/* entry does not exist;  add new baseobj */
      aref = (aliasptr)malloc(sizeof(alias));
      aref->baseobj = thisobj;
      aref->aliases = NULL;
      aref->next = aliastop;
      aliastop = aref;
   }

   for (sref = aref->aliases; sref != NULL; sref = sref->next)
      if (!strcmp(sref->alias, newname))
	 break;

   if (sref == NULL) {		/* needs new entry */
      sref = (slistptr)malloc(sizeof(stringlist));
      sref->alias = strdup(newname);
      sref->next = aref->aliases;
      aref->aliases = sref;
      return False;
   }
   else return True;		/* alias already exists! */
}

/*------------------------------------------------------*/
/* Remove all object name aliases			*/
/*------------------------------------------------------*/

void cleanupaliases(short mode)
{
   aliasptr aref;
   slistptr sref;
   objectptr baseobj;
   char *basename, *sptr;
   int i, j;

   if (aliastop == NULL) return;

   for (aref = aliastop; aref != NULL; aref = aref->next) {
      baseobj = aref->baseobj;
      for (sref = aref->aliases; sref != NULL; sref = sref->next)
	 free(sref->alias);
   }

   for (; (aref = aliastop->next); aliastop = aref)
      free(aliastop);
   free(aliastop);
   aliastop = NULL;

   /* Get rid of propagating underscores in names */

   for (i = 0; i < ((mode == FONTLIB) ? 1 : xobjs.numlibs); i++) {
      for (j = 0; j < ((mode == FONTLIB) ? xobjs.fontlib.number :
		xobjs.userlibs[i].number); j++) {
	 baseobj = (mode == FONTLIB) ? *(xobjs.fontlib.library + j) :
		*(xobjs.userlibs[i].library + j);

	 sptr = baseobj->name;
	 while (*sptr == '_') sptr++;
	 /* need memmove to avoid overwriting? */
	 memmove((void *)baseobj->name, (const void *)sptr, strlen(sptr) + 1);
	 checkname(baseobj);
      }
   }
}

/*------------------------------------------------------*/
/* Open a library file by name and return a pointer to	*/
/* the file structure, or NULL if an error occurred.	*/
/*------------------------------------------------------*/

FILE *libopen(char *libname, short mode, char name_return[])
{
   FILE *ps;
   char inname[150], expname[150];

   sscanf(libname, "%149s", expname);
   xc_tilde_expand(expname);
   strcpy(inname, expname);

   ps = fopen(inname, "r");
   if (ps == NULL) {
      char *inptr;

      /* try adding a .lps */
      
      if ((inptr = strrchr(inname, '/')) == NULL) inptr = inname;
      if (strchr(inptr, '.') == NULL) {
	 sprintf(inname, "%s.lps", expname);
         ps = fopen(inname, "r");
      }
      if (ps == NULL) {

         /* if not found in cwd, look for environment variable  */
	 /* "XCIRCUIT_LIB_DIR" defined			 	*/
	 /* (Thanks to Ali Moini, U. Adelaide, S. Australia)	*/

	 char *tmp_s = getenv((const char *)"XCIRCUIT_LIB_DIR");

	 if (tmp_s != NULL) {
	    sprintf(inname, "%s/%s", tmp_s, expname);
      	    ps = fopen(inname, "r");
      	    if (ps == NULL) {
	       sprintf(inname, "%s/%s.lps", tmp_s, expname);
	       ps = fopen(inname, "r");
	    }
            if (ps == NULL && mode == FONTLIB) {
	       /* Fprintf(stdout, "looking in %s/fonts\n", tmp_s); */
	       sprintf(inname, "%s/fonts/%s", tmp_s, expname);
	       ps = fopen(inname, "r");
	       if (ps == NULL) {
	          sprintf(inname, "%s/fonts/%s.lps", tmp_s, expname);
	          ps = fopen(inname, "r");
	       }
	       if (ps == NULL)
		  Fprintf(stdout, "%s not found, still trying. . .\n", inname);
            }
	 }

	 /* last resort:  hard-coded directory BUILTINS_DIR */

	 if (ps == NULL) {
	    sprintf(inname, "%s/%s", BUILTINS_DIR, expname);
      	    ps = fopen(inname, "r");
      	    if (ps == NULL) {
	       sprintf(inname, "%s/%s.lps", BUILTINS_DIR, expname);
	       ps = fopen(inname, "r");
	    }
            if (ps == NULL && mode == FONTLIB) {
	       /* Fprintf(stdout, "looking in %s/fonts\n", BUILTINS_DIR); */
	       sprintf(inname, "%s/fonts/%s", BUILTINS_DIR, expname);
	       ps = fopen(inname, "r");
	       if (ps == NULL) {
	          sprintf(inname, "%s/fonts/%s.lps", BUILTINS_DIR, expname);
	          ps = fopen(inname, "r");
	       }
	       if (ps == NULL) Fprintf(stdout, "%s not found.\n", inname);
	    }
	    if (ps == NULL) {
               Wprintf("No library file found.");
	    }
	 }
      }
   }
   
   if (name_return) strcpy(name_return, inname);
   return ps;
}

/*--------------------------------------------------------------*/
/* Add a record to the instlist list of the library indexed by  */
/* mode, create a new instance, and add it to the record.	*/
/*								*/
/* objname is the name of the library object to be instanced,	*/
/* and buffer is the line containing the instance parameters	*/
/* of the new instance, optionally preceded by scale and	*/
/* rotation values.						*/
/*								*/
/*--------------------------------------------------------------*/

objinstptr new_library_instance(short mode, char *objname, char *buffer)
{
   char *lineptr;
   objectptr libobj, localdata;
   objinstptr newobjinst;
   int j, libno;

   localdata = xobjs.libtop[mode + LIBRARY]->thisobject;

   for (j = 0; j < xobjs.userlibs[mode].number; j++) {
      libobj = *(xobjs.userlibs[mode].library + j);
      if (!strcmp(objname, libobj->name)) {
	 newobjinst = addtoinstlist(mode, libobj, TRUE);

	 lineptr = buffer;
	 while (isspace(*lineptr)) lineptr++;
	 if (*lineptr != '<') {
	    /* May declare instanced scale and rotation first */
	    lineptr = varfscan(localdata, lineptr, &newobjinst->scale,
			(genericptr)newobjinst, P_SCALE);
	    lineptr = varscan(localdata, lineptr, &newobjinst->rotation,
			(genericptr)newobjinst, P_ROTATION);
	 }
	 readparams(NULL, newobjinst, libobj, lineptr);
	 return newobjinst;
      }
   }
   return NULL;		/* error finding the library object */
}

/*------------------------------------------------------*/
/* Import a single object from a library file.  "mode"	*/
/* is the number of the library into which the object	*/
/* will be placed.  This function allows new libraries	*/
/* to be generated by "cutting and pasting" from	*/
/* existing libraries.  It also allows better library	*/
/* management when the number of objects becomes very	*/
/* large, such as when using "diplib" (7400 series	*/
/* chips, created from the PCB library).		*/ 	
/*------------------------------------------------------*/

void importfromlibrary(short mode, char *libname, char *objname)
{
   FILE *ps;
   char temp[150], keyword[100], *tptr;
   objectptr *newobject;
   objlistptr redef;
   float saveversion;
   Boolean dependencies = FALSE;
   
   ps = libopen(libname, mode, NULL);
   if (ps == NULL) return;

   version = 2.0;   /* Assume version is 2.0 unless found in header */

   for(;;) {
      if (fgets(temp, 149, ps) == NULL) {
         Wprintf("Error in library.");
	 goto endload;
      }
      else if (temp[0] == '/') {
	 sscanf(&temp[1], "%s", keyword);
	 if (!strcmp(keyword, objname))
	    break;
      }
      else if (*temp == '%') {
	 char *tptr = temp + 1;
	 while (isspace(*tptr)) tptr++;
	 if (!strncmp(tptr, "Version:", 8)) {
	    float tmpv;
	    tptr += 9;
	    if (sscanf(tptr, "%f", &tmpv) > 0) version = tmpv;
	 }
         else if (!strncmp(tptr, "Depend", 6)) {
	    dependencies = TRUE;
	    tptr += 7;
	    sscanf(tptr, "%s", keyword);
	    if (!strcmp(keyword, objname)) {
	       /* Load dependencies */
	       while (1) {
	          tptr += strlen(keyword) + 1;
	          if (sscanf(tptr, "%s", keyword) != 1) break;
	          if (keyword[0] == '\n' || keyword[0] == '\0') break;
	          /* Recursive import */
	          saveversion = version;
	          importfromlibrary(mode, libname, keyword);
	          version = saveversion;
	       }
	    }
	 }
      }
   }

   if ((version < 3.2) && (!dependencies)) {
      Fprintf(stderr, "Library does not have dependency list and cannot "
		"be trusted.\nLoad and rewrite library to update.\n");
      goto endload;
   }

   newobject = new_library_object(mode, keyword, &redef);

   if (objectread(ps, *newobject, 0, 0, mode, temp, DEFAULTCOLOR) == False) {

      if (library_object_unique(mode, *newobject, redef)) {
	 add_object_to_library(mode, *newobject);
	 cleanupaliases(mode);

	 /* pull in any instances of this object that	*/
	 /* are defined in the library			*/

	 for(;;) {
	    if (fgets(temp, 149, ps) == NULL) {
	       Wprintf("Error in library.");
	       goto endload;
	    }
	    else if (!strncmp(temp, "% EndLib", 8))
	       break;
	    else if (strstr(temp, "libinst") != NULL) {
	       if ((tptr = strstr(temp, objname)) != NULL) {
	          if (*(tptr - 1) == '/') {
		     char *eptr = tptr;
		     while (!isspace(*++eptr));
		     *eptr = '\0';
		     new_library_instance(mode - LIBRARY, tptr, temp);
		  }
	       }
	    }
	 }

	 if (mode != FONTLIB) composelib(mode);
      }
   }

endload:
   fclose(ps);
   version = PROG_VERSION;
}

/*------------------------------------------------------*/
/* Load a library page (given in parameter "mode") and	*/
/* rename the library page to match the library name as */
/* found in the file header.				*/
/*------------------------------------------------------*/

void loadlibrary(short mode)
{
   FILE *ps;
   char temp[150], keyword[30], percentc, inname[150];

   ps = libopen(_STR, mode, inname);
   if (ps == NULL) return;

   /* current version is PROG_VERSION;  however, all libraries newer than */
   /* version 2.0 require "Version" in the header.  So unnumbered	  */
   /* libraries may be assumed to be version 1.9 or earlier.		  */

   version = 1.9;
   for(;;) {
      if (fgets(temp, 149, ps) == NULL) {
         Wprintf("Error in library.");
	 fclose(ps);
         return;
      }
      sscanf(temp, "%c %29s", &percentc, keyword);

      /* Commands in header are PostScript comments (%) */
      if (percentc == '%') {

         /* Rename the library page if the library name is found in the header 	*/
	 /* and this library is not a font description.				*/
         if ((mode != FONTLIB) && !strcmp(keyword, "Library")) {
	    char *cptr, *nptr;
	    cptr = strchr(temp, ':');
	    if (cptr != NULL) {
	       /* Don't write terminating newline to the object's name string */
	       if ((nptr = strchr(cptr + 2, '\n')) != NULL) *nptr = '\0';
	       if (xobjs.userlibs[mode - LIBRARY].number == 0) {
	          sprintf(xobjs.libtop[mode]->thisobject->name,
				"Library: %.79s", cptr + 2);
		  renamelib(mode);
	       }
	    }
         }

         /* This comment gives the Xcircuit version number */
	 else if (!strcmp(keyword, "Version:")) {
	    float tmpv;
	    if (sscanf(temp, "%*c %*s %f", &tmpv) > 0) version = tmpv;
	 }

         /* This PostScript comment marks the end of the file header */
         else if (!strcmp(keyword, "XCircuitLib")) break;
      }
   }
   objectread(ps, topobject, 0, 0, mode, temp, DEFAULTCOLOR);
   cleanupaliases(mode);

   if (mode != FONTLIB) composelib(mode);

   /* Fprintf(stdout, "Loaded library %s\n", inname); */
   sprintf(_STR, "Loaded library %s", inname);
   Wprintf(_STR);

   version = PROG_VERSION;
   fclose(ps);
}

/*---------------------------------------------------------*/

void startloadfile()
{
   short firstpage = areastruct.page;

   while (nextfilename()) {
      loadfile(0);

      /* find next undefined page */

      while(areastruct.page < xobjs.pages &&
	   xobjs.pagelist[areastruct.page]->pageinst != NULL) areastruct.page++;
      changepage(areastruct.page);
   }
   loadfile(0);

   /* Display the first page loaded */

   newpage(firstpage);

   setsymschem();
}

/*------------------------------------------------------*/
/* Import an xcircuit file onto the current page	*/
/*------------------------------------------------------*/

void importfile()
{
   while (nextfilename()) loadfile(1);
   loadfile(1);
}

/*--------------------------------------------------------------*/
/* Skip forward in the input file to the next comment line	*/
/*--------------------------------------------------------------*/

void skiptocomment(char *temp, int length, FILE *ps)
{
   int pch;

   do {
      pch = getc(ps);
   } while (pch == '\n');

   ungetc(pch, ps);
   if (pch == '%') fgets(temp, length, ps);
}

/*--------------------------------------------------------------*/
/* ASG file import functions:					*/
/* This function loads a SPICE deck (hspice format)		*/
/*--------------------------------------------------------------*/

#ifdef ASG

void importspice()
{
   char inname[250];
   FILE *spcfile;

   if (eventmode == CATALOG_MODE) {
      Wprintf("Cannot import a netlist while in the library window.");
      return;
   }

   if (!nextfilename()) {
      xc_tilde_expand(_STR);
      sscanf(_STR, "%149s", inname);
      spcfile = fopen(inname, "r");
      ReadHSpice(inname);
      Route(&areastruct, FALSE);
      fclose(spcfile);
   }
   else {
      Wprintf("Error:  No spice file to read.");
      return;
   }
}

#endif

/*--------------------------------------------------------------*/
/* Load an xcircuit file into xcircuit				*/
/*								*/
/*    mode = 0 is a "load" function.  Behavior:  load on 	*/
/*	current page if empty.  Otherwise, load on first empty	*/
/*	page found.  If no empty pages exist, create a new page	*/
/*	and load there.						*/
/*    mode = 1 is an "import" function.  Behavior:  add		*/
/*	contents of file to the current page.			*/
/*--------------------------------------------------------------*/

void loadfile(short mode)
{
   FILE *ps;
   char inname[250], temp[150], keyword[30], percentc, *pdchar;
   char teststr[50], teststr2[20], pagestr[100];
   short offx, offy, multipage, page, temppmode = 0;
   float tmpfl;
   XPoint pagesize;

   /* First, if we're in catalog mode, return with error */

   if (eventmode == CATALOG_MODE) {
      Wprintf("Cannot load file from library window");
      return;
   }

   /* Do tilde expansion on filename */

   xc_tilde_expand(_STR);

#ifdef LGF
   /* quick check for .lgf or .lfo file */
   if ((pdchar = strchr(_STR, '.')) != NULL)
      if (!strcmp(pdchar + 1, "lgf") || !strcmp(pdchar + 1, "lfo")) {
	 loadlgf(mode);
         return;
      }
#endif

   sscanf(_STR, "%149s", inname);
   ps = fopen(inname, "r");
   if (ps == NULL) {
      sprintf(inname, "%s.ps", _STR);
      ps = fopen(inname, "r");
      if (ps == NULL) {

	 /* Could possibly be a library file? */

         if ((pdchar = strchr(_STR, '.')) == NULL) {
	    sprintf(inname, "%s.lps", _STR);
	    if ((ps = fopen(inname, "r")) != NULL) {
	       fclose(ps);
	       loadlibrary(USERLIB);
	       return;
	    }

#ifdef LGF
	    sprintf(inname, "%s.lgf", _STR);
	    if ((ps = fopen(inname, "r")) != NULL) {
	       fclose(ps);
	       loadlgf(mode);
	       return;
	    }

	    sprintf(inname, "%s.lfo", _STR);
	    if ((ps = fopen(inname, "r")) != NULL) {
	       fclose(ps);
	       loadlgf(mode);
	       return;
	    }
#endif
	 }

	 /* What to do if no file was found:				  */
	 /* If extension was .lgf or .lfo, just flag an error.  	  */
         /* Otherwise, if we're on an empty page, go ahead and rename it. */

#ifdef LGF

         else {
	    if (!strcmp(pdchar + 1, "lgf") || !strcmp(pdchar + 1, "lfo")) {
	       sprintf(temp, "Error: Can't find lgf-format file %s", _STR);
               Wprintf(temp);
	       return;
	    }
	 }
#endif

         if (topobject->parts == 0 && (mode == 0)) {

	    /* Check for file extension, and remove if "ps". */

            if ((pdchar = strchr(_STR, '.')) != NULL)
		if (!strcmp(pdchar + 1, "ps")) *pdchar = '\0';

	    free(xobjs.pagelist[areastruct.page]->filename);
	    xobjs.pagelist[areastruct.page]->filename = strdup(_STR);

	    /* If the name has a path component, use only the root 	*/
	    /* for the object name, but the full path for the filename. */

	    if ((pdchar = strrchr(_STR, '/')) != NULL)
               sprintf(topobject->name, "%s", pdchar + 1);
            else
	       sprintf(topobject->name, "%s", _STR);

	    renamepage(areastruct.page);
	    printname(topobject);
            Wprintf("Starting new drawing");
         }
         else {
	    sprintf(temp, "Can't find file %s, won't overwrite current page",
			_STR);
            Wprintf(temp);
         }
	 return;
      }
   }

   version = 1.0;
   multipage = 1;
   pagesize.x = 612;
   pagesize.y = 792;
   for(;;) {
      if (fgets(temp, 149, ps) == NULL) {
	 Wprintf("Error: EOF in or before prolog.");
	 return;
      }
      sscanf(temp, "%c%29s", &percentc, keyword);
      for (pdchar = keyword; isspace(*pdchar); pdchar++);
      if (percentc == '%') {
	 if (!strcmp(pdchar, "XCircuit")) break;
	 if (!strcmp(pdchar, "XCircuitLib")) {
	    /* version control in libraries is post version 1.9 */
	    if (version == 1.0) version = 1.9;
	    break;
	 }
	 if (!strcmp(pdchar, "%Page:")) break;
	 if (strstr(pdchar, "PS-Adobe") != NULL)
	    temppmode = (strstr(temp, "EPSF") != NULL) ? 0 : 1;
         else if (!strcmp(pdchar, "Version:"))
	    sscanf(temp, "%*c%*s %f", &version);
	 else if (!strcmp(pdchar, "%Pages:")) {
	    pdchar = advancetoken(temp);
	    multipage = atoi(pdchar);
	 }
	 /* Crash files get renamed back to their original filename */
	 else if (!strcmp(pdchar, "%Title:")) {
	    if (xobjs.tempfile != NULL)
	       if (!strcmp(inname, xobjs.tempfile))
	          sscanf(temp, "%*c%*s %s", inname);
	 }
	 else if ((temppmode == 1) && !strcmp(pdchar, "%BoundingBox:")) {
	    short botx, boty;
	    sscanf(temp, "%*s %hd %hd %hd %hd", &botx, &boty,
		&(pagesize.x), &(pagesize.y));
	    pagesize.x += botx;
	    pagesize.y += boty;
	 }
      }
#ifdef LGF
      else if (percentc == '-' && !strcmp(keyword, "5")) {
	 fclose(ps);
	 loadlgf(mode);
	 return;
      }
#endif
   }

   /* Look for old-style files (no %%Page; maximum one page in file) */

   if (!strcmp(pdchar, "XCircuit"))
      skiptocomment(temp, 149, ps);

   for (page = 0; page < multipage; page++) {
      sprintf(pagestr, "%d", page + 1);

      /* read out-of-page library definitions */

      if (strstr(temp, "%%Page:") == NULL && strstr(temp, "offsets") == NULL)
	 objectread(ps, topobject, 0, 0, USERLIB, temp, DEFAULTCOLOR);

      if (strstr(temp, "%%Page:") != NULL) {
	 sscanf(temp + 8, "%99s", pagestr);

	 /* Read the next line so any keywords in the Page name don't	*/
	 /* confuse the parser.						*/
	 if (fgets(temp, 149, ps) == NULL) {
            Wprintf("Error: bad page header.");
            return;
	 }
      }

      /* go to new page if necessary */

      if (page > 0) {

	 /* find next undefined page */

	 while(areastruct.page < xobjs.pages &&
		xobjs.pagelist[areastruct.page]->pageinst != NULL) areastruct.page++;
	 changepage(areastruct.page);
      }

      /* If this file was a library file then there is no page to load */

      if (strstr(temp, "EndLib") != NULL) {
   	 composelib(USERLIB);
	 Wprintf("Loaded library.");
	 return;
      }

      /* good so far;  let's clear out the old data structure */

      if (mode == 0) {
         reset(topobject, NORMAL);
         pagereset(areastruct.page);
	 xobjs.pagelist[areastruct.page]->pmode = temppmode;
	 if (temppmode == 1) {
	    xobjs.pagelist[areastruct.page]->pagesize.x = pagesize.x;
	    xobjs.pagelist[areastruct.page]->pagesize.y = pagesize.y;
	 }
      }
      else {
	 invalidate_netlist(topobject);
	 /* ensure that the netlist for topobject is destroyed */
	 freenetlist(topobject);
      }

      /* clear the undo record */
      flush_undo_stack();

      /* read to the "scale" line, picking up inch/cm type, drawing */
      /* scale, and grid/snapspace along the way		    */

      offx = offy = 0;
      for(;;) {
         if (strstr(temp, "offsets") != NULL) {
	    /* Prior to version 3.1.28 only. . . */
            sscanf(temp, "%c %hd %hd %*s", &percentc, &offx, &offy);
            if(percentc != '%') {
               Wprintf("Something wrong in offsets line.");
               offx = offy = 0;
            }
	 }

	 if ((temppmode == 1) && strstr(temp, "%%PageBoundingBox:") != NULL) {
	    /* Recast the individual page size if specified per page */
	    sscanf(temp, "%*s %*d %*d %hd %hd",
		&xobjs.pagelist[areastruct.page]->pagesize.x,
		&xobjs.pagelist[areastruct.page]->pagesize.y);
	 }
	 else if (strstr(temp, "drawingscale") != NULL)
	    sscanf(temp, "%*c %hd:%hd %*s",
		&xobjs.pagelist[areastruct.page]->drawingscale.x,
		&xobjs.pagelist[areastruct.page]->drawingscale.y);

	 else if (strstr(temp, "is_symbol") != NULL) {
	    sscanf(temp, "%*c %49s", teststr);
	    checkschem(topobject, teststr);
	 }

	 else if (strstr(temp, "gridspace"))
	    sscanf(temp, "%*c %f %f %*s", &xobjs.pagelist[areastruct.page]->gridspace,
		 &xobjs.pagelist[areastruct.page]->snapspace);
         else if (strstr(temp, "scale") != NULL || strstr(temp, "rotate") != NULL) {
            /* rotation (landscape mode) is optional; parse accordingly */

            sscanf(temp, "%f %49s", &tmpfl, teststr);
            if (strstr(teststr, "scale") != NULL) {
	       setgridtype(teststr);
               xobjs.pagelist[areastruct.page]->outscale = tmpfl;
            }
            else if (!strcmp(teststr, "rotate")) {
               xobjs.pagelist[areastruct.page]->orient = (short)tmpfl;
               fgets(temp, 149, ps);
               sscanf(temp, "%f %19s", &tmpfl, teststr2);
	       if (strstr(teststr2, "scale") != NULL) {
	          setgridtype(teststr2);
	          xobjs.pagelist[areastruct.page]->outscale = tmpfl;
	       }
	       else {
	          sscanf(temp, "%*f %*f %19s", teststr2);
	          if (!strcmp(teststr2, "scale"))
	             xobjs.pagelist[areastruct.page]->outscale = tmpfl /
		          getpsscale(1.0, areastruct.page);
	          else {
	             Wprintf("Error in scale/rotate constructs.");
	             return;
	          }
	       }
            }
            else {     /* old style scale? */
               sscanf(temp, "%*f %*s %19s", teststr2);
	       if ((teststr2 != NULL) && (!strcmp(teststr2, "scale")))
                  xobjs.pagelist[areastruct.page]->outscale = tmpfl /
		        getpsscale(1.0, areastruct.page);
	       else {
                  Wprintf("Error in scale/rotate constructs.");
                  return;
	       }
            }
	 }
         else if (strstr(temp, "setlinewidth") != NULL) {
            sscanf(temp, "%f %*s", &xobjs.pagelist[areastruct.page]->wirewidth);
            xobjs.pagelist[areastruct.page]->wirewidth /= 1.3;
	    break;
	 }
	 else if (strstr(temp, "insertion") != NULL) {
	    /* read in an included background image */
	    readbackground(ps);
	 }
	 else if (strstr(temp, "<<") != NULL) {
	    char *buffer = temp, *endptr;
	    /* Make sure we have the whole dictionary before calling */
	    while (strstr(buffer, ">>") == NULL) {
	       if (buffer == temp) {
		  buffer = (char *)malloc(strlen(buffer) + 150);
		  strcpy(buffer, temp);
	       }
	       else
	          buffer = (char *)realloc(buffer, strlen(buffer) + 150);
	       endptr = ridnewline(buffer);
	       *endptr++ = ' ';
               fgets(endptr, 149, ps);
	    }
	    /* read top-level parameter dictionary */
	    readparams(NULL, NULL, topobject, buffer);
	    if (buffer != temp) free(buffer);
	 }

	 if (fgets(temp, 149, ps) == NULL) {
            Wprintf("Error: Problems encountered in page header.");
            return;
         }
      }

      objectread(ps, topobject, offx, offy, LIBRARY, temp, DEFAULTCOLOR);

      /* skip to next page boundary or file trailer */

      if (strstr(temp, "showpage") != NULL && multipage != 1) {

	 skiptocomment(temp, 149, ps);

	 /* check for new filename if this is a crash recovery file */
         if (strstr(temp, "is_filename") != NULL) {
	    sscanf(temp + 2, "%s", inname);
	    fgets(temp, 149, ps);
	    skiptocomment(temp, 149, ps);
         }
      }

      /* Finally: set the filename and pagename for this page */

      if (mode == 0) {
	 char tpstr[6], *rootptr;

	 /* Set filename and page title.	      */

	 if (xobjs.pagelist[areastruct.page]->filename != NULL)
	    free(xobjs.pagelist[areastruct.page]->filename);
	 xobjs.pagelist[areastruct.page]->filename = strdup(inname);

         /* Delete the filename suffix if it is ".ps" */

         if ((pdchar = strchr(xobjs.pagelist[areastruct.page]->filename, '.')) != NULL) 
	       if (!strcmp(pdchar + 1, "ps")) *pdchar = '\0';

	 /* Get the root name (after all path components) */

	 rootptr = strrchr(xobjs.pagelist[areastruct.page]->filename, '/');
	 if (rootptr == NULL) rootptr = xobjs.pagelist[areastruct.page]->filename;
	 else rootptr++;

	 /* If we didn't read in a page name from the %%Page: header line, then */
	 /* set the page name to the root name of the file.			*/

	 sprintf(tpstr, "%d", page + 1);
	 if (!strcmp(pagestr, tpstr))
	    sprintf(topobject->name, "%.79s", rootptr);
	 else
	    sprintf(topobject->name, "%.79s", pagestr);

         renamepage(areastruct.page);
      }

      /* set object position to fit to window separately for each page */
      calcbbox(areastruct.topinstance);
      centerview(areastruct.topinstance);
   }

   /* Crash file recovery: read any out-of-page library definitions tacked */
   /* onto the end of the file into the user library.			   */
   objectread(ps, topobject, 0, 0, USERLIB, temp, DEFAULTCOLOR);
   cleanupaliases(USERLIB);

   sprintf(_STR, "Loaded file: %s (%d page%s)", inname, multipage,
	(multipage > 1 ? "s" : ""));
   Wprintf(_STR);

   composelib(USERLIB);
   composelib(PAGELIB);

   if (version > PROG_VERSION + VEPS) {
      sprintf(_STR, "WARNING: file %s is version %2.1f vs. executable version %2.1f",
		inname, version, PROG_VERSION);
      Wprintf(_STR);
   }

   version = PROG_VERSION;
   fclose(ps);
}

/*------------------------------------------------------*/
/* Object name comparison:  True if names are equal,    */
/* not counting leading underscores.                    */
/*------------------------------------------------------*/

int objnamecmp(char *name1, char *name2)
{
   char *n1ptr = name1;
   char *n2ptr = name2;

   while (*n1ptr == '_') n1ptr++;                           
   while (*n2ptr == '_') n2ptr++;

   return (strcmp(n1ptr, n2ptr));
}

/*------------------------------------------------------*/
/* Standard delimiter matching character.		*/
/*------------------------------------------------------*/

char standard_delimiter_end(char source)
{
   char target;
   switch(source) {
      case '(':  target = ')'; break;
      case '[':  target = ']'; break;
      case '{':  target = '}'; break;
      case '<':  target = '>'; break;
      default:   target = source;
   }
   return target;
}

/*------------------------------------------------------*/
/* Find matching parenthesis, bracket, brace, or tag	*/
/* Don't count when backslash character '\' is in front */
/*------------------------------------------------------*/

u_char *find_delimiter(u_char *fstring)
{
   int count = 1;

   u_char *search = fstring; 
   u_char source = *fstring;
   u_char target;

   target = (u_char)standard_delimiter_end((char)source);
   while (*++search != '\0') {
      if (*search == source && *(search - 1) != '\\') count++;
      else if (*search == target && *(search - 1) != '\\') count--;
      if (count == 0) break;
   }
   return search;
}

/*----------------------------------------------------------------------*/
/* Remove unnecessary font change information from a label		*/
/*----------------------------------------------------------------------*/

void cleanuplabel(stringpart **strhead)
{
   stringpart *curpart = *strhead;
   int oldfont, curfont;

   oldfont = curfont = -1;

   while (curpart != NULL) {
      switch (curpart->type) {
	 case FONT_NAME:
	    if (curfont == curpart->data.font) {
	       /* Font change is redundant:  remove it */
	       curpart = deletestring(curpart, strhead, areastruct.topinstance);
	    }
	    else {
	       curfont = curpart->data.font;
	    }
	    break;

	 case FONT_SCALE:
	    /* Old style font scale is always written absolute, not relative.	*/
	    /* Changes in scale were not allowed, so just get rid of them.	*/
	    if (version < 2.25)
	       curpart = deletestring(curpart, strhead, areastruct.topinstance);
	    break;

	 /* A font change may occur inside a parameter, so any font	*/
	 /* declaration after a parameter must be considered to be	*/
	 /* intentional.						*/

	 case PARAM_END:
	    curfont = oldfont = -1;
	    break;

	 case NORMALSCRIPT: case RETURN:
	    if (oldfont != -1) {
	       curfont = oldfont;
	       oldfont = -1;
	    }
	    break;

	 case SUBSCRIPT: case SUPERSCRIPT:
	    if (oldfont == -1)
	       oldfont = curfont;
	    break;
      }
      if (curpart != NULL)
	 curpart = curpart->nextpart;
   }
}

/*----------------------------------------------------------------------*/
/* Read label segments							*/
/*----------------------------------------------------------------------*/

void readlabel(objectptr localdata, char *lineptr, stringpart **strhead)
{
   Boolean fline = False;
   char *sptr;
   short j;
   char *endptr, *segptr = lineptr;
   char key[100];
   stringpart *newpart;
   oparamptr ops;

   while (*segptr != '\0') {	/* Look through all segments */

      while (isspace(*segptr) && (*segptr != '\0')) segptr++;

      if (*segptr == '(' || *segptr == '{') {
         endptr = find_delimiter(segptr);
         *endptr++ = '\0';
	 /* null string (e.g., null parameter substitution) */
	 if ((*segptr == '(') && (*(segptr + 1) == '\0')) {
	    segptr = endptr;
	    continue;
	 }
      }
      else if (*segptr == '\0' || *segptr == '}') break;

      makesegment(strhead, *strhead);
      newpart = *strhead;

      /* Embedded command is in braces: {} */

      if (*segptr == '{') {	

         /* Find the command for this PostScript procedure */
	 char *cmdptr = endptr - 2;
         while (isspace(*cmdptr)) cmdptr--;
         while (!isspace(*cmdptr) && (cmdptr > segptr)) cmdptr--;
	 cmdptr++;
	 segptr++;

         if (!strncmp(cmdptr, "Ss", 2))
	    newpart->type = SUPERSCRIPT;
         else if (!strncmp(cmdptr, "ss", 2))
            newpart->type = SUBSCRIPT;
         else if (!strncmp(cmdptr, "ns", 2))
            newpart->type = NORMALSCRIPT;
         else if (!strncmp(cmdptr, "hS", 2))
            newpart->type = HALFSPACE;
         else if (!strncmp(cmdptr, "qS", 2))
            newpart->type = QTRSPACE;
         else if (!strncmp(cmdptr, "CR", 2))
	    newpart->type = RETURN;
         else if (!strcmp(cmdptr, "Ts")) /* "Tab set" command */
	    newpart->type = TABSTOP;
         else if (!strcmp(cmdptr, "Tf")) /* "Tab forward" command */
	    newpart->type = TABFORWARD;
         else if (!strcmp(cmdptr, "Tb")) /* "Tab backward" command */
	    newpart->type = TABBACKWARD;
         else if (!strncmp(cmdptr, "ol", 2)) {
            newpart->type = OVERLINE;
            fline = True;
         }
         else if (!strncmp(cmdptr, "ul", 2)) {
            newpart->type = UNDERLINE;
            fline = True;
         }
         else if (cmdptr == segptr) {   /* cancel over- or underline */
            newpart->type = NOLINE;
            fline = False;
         }
	 /* To-do:  replace old-style backspace with tab stop */
         else if (!strcmp(cmdptr, "bs")) {
	    Wprintf("Warning:  Obsolete backspace command ommitted in text");
         }
         else if (!strcmp(cmdptr, "Kn")) {  /* "Kern" command */
	    int kx, ky;
	    sscanf(segptr, "%d %d", &kx, &ky);  
	    newpart->type = KERN;
	    newpart->data.kern[0] = kx;
	    newpart->data.kern[1] = ky;
         }
         else if (!strcmp(cmdptr, "scb")) {  /* change color command */
	    float cr, cg, cb;
	    int cval, cindex;
	    sscanf(segptr, "%f %f %f", &cr, &cg, &cb);  
            newpart->type = FONT_COLOR;
	    cval = rgb_alloccolor((int)(cr * 65535), (int)(cg * 65535),
		   (int)(cb * 65535));
	    for (cindex = 0; cindex < number_colors; cindex++)
               if (colorlist[cindex].color.pixel == cval)
                  break;
	    if (cindex == number_colors) {
	       Wprintf("Error:  Cannot allocate another color");
	       cindex = DEFAULTCOLOR;
	    }
	    newpart->data.color = cindex;
         }
         else if (!strcmp(cmdptr, "sce")) {  /* revert to default color */
            newpart->type = FONT_COLOR;
	    newpart->data.color = DEFAULTCOLOR;
         }
         else if (!strcmp(cmdptr, "cf")) {  /* change font or scale command */
	    char *nextptr, *newptr = segptr;

	    /* Set newptr to the fontname and nextptr to the next token. */
	    while (*newptr != '/' && *newptr != '\0') newptr++;
	    if (*newptr++ == '\0') {
	       Wprintf("Error:  Bad change-font command");
	       return;
	    }
	    for (nextptr = newptr; !isspace(*nextptr); nextptr++);
	    *(nextptr++) = '\0';
	    while (isspace(*nextptr)) nextptr++;

            for (j = 0; j < fontcount; j++)
               if (!strcmp(newptr, fonts[j].psname))
	          break;

            if (j == fontcount) 		/* this is a non-loaded font */
               loadfontfile(newptr);

            if (isdigit(*nextptr)) { /* second form of "cf" command---includes scale */
	       float locscale;
	       sscanf(nextptr, "%f", &locscale);
	       if (locscale != 1.0) {
	          newpart->type = FONT_SCALE;
	          newpart->data.scale = locscale;
	          makesegment(strhead, *strhead);
	          newpart = *strhead;
	       }
            }
	    newpart->type = FONT_NAME;
	    newpart->data.font = j;
         }
         else {   /* This exec isn't a known label function */
	    Wprintf("Error:  unknown substring function");
	    newpart->type = NOLINE;	/* This is fairly benign */
         }
      }

      /* Text substring is in parentheses: () */

      else if (*segptr == '(') {
	 u_char *tmpptr;
         if (fline == True) {
	    newpart->type = NOLINE;
            makesegment(strhead, *strhead);
	    newpart = *strhead;
	    fline = False;
         }
         newpart->type = TEXT_STRING;
         newpart->data.string = (u_char *)malloc(1 + strlen(++segptr));

         /* Copy string, translating octal codes into 8-bit characters */

         tmpptr = newpart->data.string;
         for (sptr = segptr; *sptr != '\0'; sptr++) {
            if (*sptr == '\\') {
               sptr++;
	       if (*sptr >= '0' && *sptr < '8') {
	          int tmpdig;
	          sscanf(sptr, "%3o", &tmpdig);
	          *tmpptr++ = (u_char)tmpdig;
	          sptr += 2;
	       }
               else *tmpptr++ = (u_char) *sptr;
	    }
            else *tmpptr++ = (u_char) *sptr;
	 }
 	 *tmpptr = '\0';
      }

      /* Parameterized substrings are denoted by parameter key.	*/
      /* The parameter (default and/or substitution value) is 	*/
      /* assumed to exist.					*/

      else {

         if (sscanf(segptr, "%99s", key) == 1) {
	    newpart->type = PARAM_START;
	    newpart->data.string = (char *)malloc(1 + strlen(key));
	    strcpy(newpart->data.string, key);

	    /* check for compatibility between the parameter value and	*/
	    /* the number of parameters and parameter type.		*/

	    ops = match_param(localdata, key);
	    if (ops == NULL) {
	       Fprintf(stderr, "readlabel() error:  No such parameter %s!\n", key);
	       deletestring(newpart, strhead, areastruct.topinstance);
	    }

            /* Fprintf(stdout, "Parameter %s called from object %s\n",	*/
	    /* key,	localdata->name);				*/
         }
	 endptr = segptr + 1;
	 while (!isspace(*endptr) && (*endptr != '\0')) endptr++;
      }
      segptr = endptr;
   }
}

/*--------------------------------------*/
/* skip over whitespace in string input */
/*--------------------------------------*/

char *skipwhitespace(char *lineptr)
{
   char *locptr = lineptr;

   while (isspace(*locptr) && (*locptr != '\n') && (*locptr != '\0')) locptr++;
   return locptr;
}

/*-------------------------------------------*/
/* advance to the next token in string input */
/*-------------------------------------------*/

char *advancetoken(char *lineptr)
{
   char *locptr = lineptr;

   while (!isspace(*locptr) && (*locptr != '\n') && (*locptr != '\0')) locptr++;
   while (isspace(*locptr) && (*locptr != '\n') && (*locptr != '\0')) locptr++;
   return locptr;
}

/*------------------------------------------------------*/
/* Read a parameter list for an object instance call	*/
/* This uses the "old style" in which each parameter	*/
/* was automatically assigned the key v1, v2, etc.	*/
/*------------------------------------------------------*/

void readparams(objectptr localdata, objinstptr newinst, objectptr libobj,
		char *buffer)
{
   oparamptr newops, objops, fops;
   char *arrayptr, *endptr, *arraynext;
   int paramno = 0;
   char paramkey[100];

   if ((arrayptr = strstr(buffer, "<<")) == NULL)
      if ((arrayptr = strchr(buffer, '[')) == NULL)
	 return;

   endptr = find_delimiter(arrayptr);
   if (*arrayptr == '<') {
      arrayptr++;	/* move to second '<' in "<<"	*/
      endptr--;		/* back up to first '>' in ">>"	*/
   }

   /* move to next non-space token after opening bracket */
   arrayptr++;
   while (isspace(*arrayptr) && *arrayptr != '\0') arrayptr++;

   while ((*arrayptr != '\0') && (arrayptr < endptr)) {

      newops = (oparamptr)malloc(sizeof(oparam));

      /* Arrays contain values only.  Dictionaries contain key:value pairs */
      if (*endptr == '>') {	/* dictionary type */
	 if (*arrayptr != '/') {
	    Fprintf(stdout, "Error: Dictionary key is a literal, not a name\n");
	 }
	 else arrayptr++;	/* Skip PostScript name delimiter */
	 sscanf(arrayptr, "%99s", paramkey);
	 newops->key = (char *)malloc(1 + strlen(paramkey));
	 strcpy(newops->key, paramkey);
	 arrayptr = advancetoken(arrayptr);
      }
      else {		/* array type; keys are "v1", "v2", etc. */
	 paramno++;
	 newops->key = (char *)malloc(6);
	 sprintf(newops->key, "v%d", paramno);
      }

      /* Add to instance's parameter list */
      /* If newinst is null, then the parameters are added to libobj */
      newops->next = NULL;
      if (newinst) {
	 if (newinst->params == NULL)
	    newinst->params = newops;
	 else {
	    for (fops = newinst->params; fops->next != NULL; fops = fops->next);
	    fops->next = newops;
	 }
      }
      else {
	 if (libobj->params == NULL)
	    libobj->params = newops;
	 else {
	    for (fops = libobj->params; fops->next != NULL; fops = fops->next);
	    fops->next = newops;
	 }
      }

      /* Find matching parameter in object definition */
      if (newinst) {
	 objops = match_param(libobj, newops->key);
	 if (objops == NULL) {
	    Fprintf(stdout, "Error: parameter does not exist!\n");
	    return;
	 }
      }

      /* Fill in "which" entry from the object default */
      newops->which = (newinst) ? objops->which : 0;

      /* Check next token.  If not either end-of-dictionary or	*/
      /* the next parameter key, then value is an expression.	*/
      /* Expressions are written as two strings, the first the	*/
      /* result of evaluting the expression, and the second the	*/
      /* expression itself, followed by "pop" to prevent the	*/
      /* PostScript interpreter from trying to evaluate the	*/
      /* expression (which is not in PostScript).		*/

      if (*arrayptr == '(' || *arrayptr == '{')
	 arraynext = find_delimiter(arrayptr);
      else
	 arraynext = arrayptr;
      arraynext = advancetoken(arraynext);

      if ((*endptr == '>') && (arraynext < endptr) && (*arraynext != '/')) {
	 char *substrend, *arraysave;

	 if (*arraynext == '(' || *arraynext == '{') {

	    substrend = find_delimiter(arraynext);
	    arraysave = arraynext + 1;
	    arraynext = advancetoken(substrend);

	    newops->type = (u_char)XC_EXPR;
	    newops->which = P_EXPRESSION;
	 }

	 if (strncmp(arraynext, "pop ", 4)) {
	    Wprintf("Error:  bad expression parameter!\n");
#ifdef TCL_WRAPPER
	    newops->parameter.expr = strdup("expr 0");
#else
	    newops->parameter.expr = strdup("0");
#endif
	    arrayptr = advancetoken(arrayptr);
	 } else {
	    *substrend = '\0';
	    newops->parameter.expr = strdup(arraysave);
	    arrayptr = advancetoken(arraynext);
	 }
      }

      else if (*arrayptr == '(' || *arrayptr == '{') { 
	 float r, g, b;
	 char *substrend, csave;
	 stringpart *endpart;

	 /* type XC_STRING */

	 substrend = find_delimiter(arrayptr);
	 csave = *(++substrend);
	 *substrend = '\0';
	 if (*arrayptr == '{') arrayptr++;

	 /* A numerical value immediately following the opening */
	 /* brace indicates a color parameter.			*/
	 if (sscanf(arrayptr, "%f %f %f", &r, &g, &b) == 3) {
	    newops->type = (u_char)XC_INT;
	    newops->which = P_COLOR;
	    newops->parameter.ivalue = rgb_alloccolor((int)(r * 65535),
		(int)(g * 65535), (int)(b * 65535)); 
	    addnewcolorentry(newops->parameter.ivalue);
	 }
	 else {
	    newops->type = (u_char)XC_STRING;
	    newops->which = P_SUBSTRING;
	    newops->parameter.string = NULL;
	    readlabel(libobj, arrayptr, &(newops->parameter.string));

	    *substrend = csave;

	    /* Append a PARAM_END to the parameter string */

	    endpart = makesegment(&(newops->parameter.string), NULL);
	    endpart->type = PARAM_END;
	    endpart->data.string = (u_char *)NULL;
	 }
	 arrayptr = substrend;
	 while (isspace(*arrayptr) && *arrayptr != '\0')
	    arrayptr++;
      }
      else {
	 char *token;
	 int scanned = 0;

	 /* type XC_FLOAT or XC_INT, or an indirect reference */

	 newops->type = (newinst) ? objops->type : (u_char)XC_FLOAT;

	 if (newops->type == XC_FLOAT) {
	    scanned = sscanf(arrayptr, "%f", &(newops->parameter.fvalue));
	    /* Fprintf(stdout, "Object %s called with parameter "
			"%s value %g\n", libobj->name,
			newops->key, newops->parameter.fvalue); */
	 }
	 else if (newops->type == XC_INT) {
	    scanned = sscanf(arrayptr, "%d", &(newops->parameter.ivalue));
	    /* Fprintf(stdout, "Object %s called with parameter "
			"%s value %d\n", libobj->name,
			newops->key, newops->parameter.ivalue); */
	 }
	 else if (newops->type != XC_STRING)
	    Fprintf(stderr, "Error: unknown parameter type!\n");

	 if (scanned == 0) {
	    /* Indirect reference --- create an eparam in the instance */
	    sscanf(arrayptr, "%99s", paramkey);

	    if (!newinst || !localdata) {
		/* Only object instances can use indirect references */
		Fprintf(stderr, "Error: parameter default %s cannot "
			"be parsed!\n", paramkey);
	    }
	    else if (match_param(localdata, paramkey) == NULL) {
		/* Reference key must exist in the calling object */
		Fprintf(stderr, "Error: parameter value %s cannot be parsed!\n",
			paramkey);
	    }
	    else {
	       /* Create an eparam record in the instance */
	       eparamptr newepp = make_new_eparam(paramkey);
	       newepp->pdata.refkey = strdup(newops->key);
	       newepp->next = newinst->passed;
	       newinst->passed = newepp;
	    }

	 }
	 arrayptr = advancetoken(arrayptr);
      }
   }

   /* Calculate the unique bounding box for the instance */

   if (newinst && (newinst->params != NULL)) {
      opsubstitute(libobj, newinst);
      calcbboxinst(newinst);
   }
}

/*--------------------------------------------------------------*/
/* Read a value which might be a short integer or a parameter.	*/
/* If the value is a parameter, check the parameter list to see */
/* if it needs to be re-typecast.  Return the position to the	*/
/* next token in "lineptr".					*/
/*--------------------------------------------------------------*/

char *varpscan(objectptr localdata, char *lineptr, short *hvalue,
	genericptr thiselem, int pointno, int offset, u_char which)
{
   oparamptr ops;
   char key[100];
   eparamptr newepp;

   if (sscanf(lineptr, "%hd", hvalue) != 1) {
      sscanf(lineptr, "%99s", key);
      ops = match_param(localdata, key);
      newepp = make_new_eparam(key);

      /* Add parameter to the linked list */
      newepp->next = thiselem->passed;
      thiselem->passed = newepp;
      newepp->pdata.pointno = pointno;

      if (ops != NULL) {

	 /* It cannot be known whether a parameter value is a float or int */
	 /* until we see how the parameter is used.  So we always read the */
	 /* parameter default as a float, and re-typecast it if necessary. */

	 if (ops->type == XC_FLOAT) {
	    ops->type = XC_INT;
	    /* (add 0.1 to avoid roundoff error in conversion to integer) */
	    ops->parameter.ivalue = (int)(ops->parameter.fvalue +
			((ops->parameter.fvalue < 0) ? -0.1 : 0.1));
	 }
	 ops->which = which;
	 *hvalue = (short)ops->parameter.ivalue;
      }
      else {
	 *hvalue = 0; /* okay; will get filled in later */
	 Fprintf(stderr, "Error:  parameter %s was used but not defined!\n", key);
      }
   }

   *hvalue -= (short)offset;
  
   return advancetoken(skipwhitespace(lineptr));
}

/*--------------------------------------------------------------*/
/* Read a value which might be a short integer or a parameter,	*/
/* but which is not a point in a pointlist.			*/
/*--------------------------------------------------------------*/

char *varscan(objectptr localdata, char *lineptr, short *hvalue,
	 genericptr thiselem, u_char which)
{
   return varpscan(localdata, lineptr, hvalue, thiselem, 0, 0, which);
}

/*--------------------------------------------------------------*/
/* Read a value which might be a float or a parameter.		*/
/* Return the position to the next token in "lineptr".		*/
/*--------------------------------------------------------------*/

char *varfscan(objectptr localdata, char *lineptr, float *fvalue,
	genericptr thiselem, u_char which)
{
   oparamptr ops;
   eparamptr newepp;
   char key[100];

   if (sscanf(lineptr, "%f", fvalue) != 1) {
      sscanf(lineptr, "%99s", key);
      ops = match_param(localdata, key);
      newepp = make_new_eparam(key);

      /* Add parameter to the linked list */
      newepp->next = thiselem->passed;
      thiselem->passed = newepp;

      if (ops != NULL) {
	 ops->which = which;
	 *fvalue = ops->parameter.fvalue;
      }
      else
	 Fprintf(stderr, "Error: no parameter defined!\n");
   }

   /* advance to next token */
   return advancetoken(skipwhitespace(lineptr));
}

/*--------------------------------------------------------------*/
/* Create a new instance of an object in the library's list of	*/
/* instances.  This instance will be used on the library page	*/
/* when doing "composelib()".					*/
/*--------------------------------------------------------------*/

objinstptr addtoinstlist(int libnum, objectptr libobj, Boolean virtual)
{
   objinstptr newinst = (objinstptr) malloc(sizeof(objinst));
   liblistptr spec = (liblistptr) malloc(sizeof(liblist));
   liblistptr srch;

   newinst->type = OBJINST;
   instancedefaults(newinst, libobj, 0, 0);

   spec->virtual = (u_char)virtual;
   spec->thisinst = newinst;
   spec->next = NULL;

   /* Add to end, so that duplicate, parameterized instances	*/
   /* always come after the original instance with the default	*/
   /* parameters.						*/

   if ((srch = xobjs.userlibs[libnum].instlist) == NULL)
      xobjs.userlibs[libnum].instlist = spec;
   else {
      while (srch->next != NULL) srch = srch->next;
      srch->next = spec;
   }

   /* Calculate the instance-specific bounding box */
   calcbboxinst(newinst);

   return newinst;
}

/*--------------------------------------------------------------*/
/* Deal with object reads:  Create a new object and prepare for	*/
/* reading.  The library number is passed as "mode".		*/
/*--------------------------------------------------------------*/

objectptr *new_library_object(short mode, char *name, objlistptr *retlist)
{
   objlistptr newdef, redef = NULL;
   objectptr *newobject, *libobj;
   objectptr *curlib = (mode == FONTLIB) ?
		xobjs.fontlib.library : xobjs.userlibs[mode - LIBRARY].library;
   short *libobjects = (mode == FONTLIB) ?
		&xobjs.fontlib.number : &xobjs.userlibs[mode - LIBRARY].number;
   int i, j;

   curlib = (objectptr *) realloc(curlib, (*libobjects + 1)
		      * sizeof(objectptr));
   if (mode == FONTLIB) xobjs.fontlib.library = curlib;
   else xobjs.userlibs[mode - LIBRARY].library = curlib;

   /* initial 1-pointer allocations */

   newobject = curlib + (*libobjects);
   *newobject = (objectptr) malloc(sizeof(object));
   initmem(*newobject);

   /* check that this object is not already in list of objects */

   if (mode == FONTLIB) {
      for (libobj = xobjs.fontlib.library; libobj != xobjs.fontlib.library +
            xobjs.fontlib.number; libobj++) {
	 /* This font character may be a redefinition of another */
	 if (!objnamecmp(name, (*libobj)->name)) {
	    newdef = (objlistptr) malloc(sizeof(objlist));
	    newdef->thisobject = *libobj;
	    newdef->next = redef;
	    redef = newdef;
	 }
      }
   }
   else {
      for (i = 0; i < xobjs.numlibs; i++) {
	 for (j = 0; j < xobjs.userlibs[i].number; j++) {
	    libobj = xobjs.userlibs[i].library + j;
	    /* This object may be a redefinition of another object */
	    if (!objnamecmp(name, (*libobj)->name)) {
	       newdef = (objlistptr) malloc(sizeof(objlist));
	       newdef->thisobject = *libobj;
	       newdef->next = redef;
	       redef = newdef;
	    }
	 }
      }
   }

   (*libobjects)++;
   sprintf((*newobject)->name, "%s", name);

   /* initmem() initialized schemtype to PRIMARY;  change it. */
   (*newobject)->schemtype = SYMBOL;

   *retlist = redef;
   return newobject;
}

/*--------------------------------------------------------------*/
/* do an exhaustive comparison between a new object and any	*/
/* object having the same name. If they are the same, destroy	*/
/* the duplicate.  If different, rename the original one.	*/
/*--------------------------------------------------------------*/

Boolean library_object_unique(short mode, objectptr newobject, objlistptr redef)
{
   Boolean is_unique = True;
   objlistptr newdef;
   short *libobjects = (mode == FONTLIB) ?
		&xobjs.fontlib.number : &xobjs.userlibs[mode - LIBRARY].number;

   if (redef == NULL)
      return is_unique;	/* No name conflicts; object is okay as-is */

   for (newdef = redef; newdef != NULL; newdef = newdef->next) {

      /* Must make sure that default parameter values are */
      /* plugged into the original object!		   */
      opsubstitute(newdef->thisobject, NULL);

      if (objcompare(newobject, newdef->thisobject) == True) {
	  addalias(newdef->thisobject, newobject->name);

	  /* If the new object has declared an association to a */
	  /* schematic, transfer it to the original, and make   */
	  /* sure that the page points to the object which will */
	  /* be left, not the one which will be destroyed.	*/

	  if (newobject->symschem != NULL) {
	     newdef->thisobject->symschem = newobject->symschem;
	     newdef->thisobject->symschem->symschem = newdef->thisobject;
	  }

	  reset(newobject, DESTROY);
	  (*libobjects)--;
	  is_unique = False;
	  break;
       }

       /* Not the same object, but has the same name.  This can't	*/
       /* happen within the same input file, so the name of the		*/
       /* original object can safely be altered.			*/

       else if (!strcmp(newobject->name, newdef->thisobject->name)) {
	  checkname(newdef->thisobject);
	  break;
       }
    }
    for (; (newdef = redef->next); redef = newdef)
       free(redef);
    free(redef);

    return is_unique;
}

/*--------------------------------------------------------------*/
/* Add an instance of the object to the library's instance list */
/*--------------------------------------------------------------*/

void add_object_to_library(short mode, objectptr newobject)
{
   objinstptr libinst;

   if (mode == FONTLIB) return;

   libinst = addtoinstlist(mode - LIBRARY, newobject, FALSE);
   calcbboxvalues(libinst, (genericptr *)NULL);

   /* Center the view of the object in its instance */
   centerview(libinst);
}

/*--------------------------------------------------------------*/
/* Continuation Line --- add memory to "buffer" as necessary.	*/
/* Add a space character to the current text in "buffer" and	*/
/* return a pointer to the new end-of-text.			*/
/*--------------------------------------------------------------*/

char *continueline(char **buffer)
{
   char *lineptr;
   int bufsize;

   for (lineptr = *buffer; (*lineptr != '\n') && (*lineptr != '\0'); lineptr++);
   if (*lineptr == '\n') *lineptr++ = ' ';

   bufsize = (int)(lineptr - (*buffer)) + 256;
   *buffer = (char *)realloc((*buffer), bufsize * sizeof(char));

   return ((*buffer) + (bufsize - 256));
}

/*--------------------------------------------------------------*/
/* Read an object (page) from a file into xcircuit		*/
/*--------------------------------------------------------------*/

Boolean objectread(FILE *ps, objectptr localdata, short offx, short offy,
	short mode, char *retstr, int ccolor)
{
   char *temp, *buffer, keyword[80];
   short tmpfont = -1;
   float tmpscale = 0.0;
   objectptr	*libobj;
   int curcolor = ccolor;
   char *colorkey = NULL;
   int i, j, k;
   objinstptr *newinst, newobjinst;

   /* path-handling variables */
   pathptr *newpath;
   XPoint startpoint;

   keyword[0] = '\0';

   buffer = (char *)malloc(256 * sizeof(char));
   temp = buffer;

   for(;;) {
      char *lineptr, *keyptr;

      if (fgets(temp, 255, ps) == NULL) {
	 if (strcmp(keyword, "restore")) {
            Wprintf("Error: end of file.");
	    *retstr = '\0';
	 }
	 break;
      }
      temp = buffer;

      /* because PostScript is a stack language, we will scan from the end */
      for (lineptr = buffer; (*lineptr != '\n') && (*lineptr != '\0'); lineptr++);
      if (lineptr != buffer) {  /* ignore any blank lines */
         for (keyptr = lineptr - 1; isspace(*keyptr) && keyptr != buffer; keyptr--);
         for (; !isspace(*keyptr) && keyptr != buffer; keyptr--);
         sscanf(keyptr, "%79s", keyword);

         if (!strcmp(keyword, "showpage")) {
            strncpy(retstr, buffer, 150);
            retstr[149] = '\0';
	    free(buffer);
	    return False;  /* end of page */
	 }

	 /* make a color change, adding the color if necessary */

	 else if (!strcmp(keyword, "scb")) {
	    float red, green, blue;
	    if (sscanf(buffer, "%f %f %f", &red, &green, &blue) == 3) {
	       curcolor = rgb_alloccolor((int)(red * 65535), (int)(green * 65535),
		   	(int)(blue * 65535)); 
	       addnewcolorentry(curcolor);
	       colorkey = NULL;
	    }
	    else {
	       char tmpkey[30];
	       oparamptr ops;

	       sscanf(buffer, "%29s", tmpkey);
	       ops = match_param(localdata, tmpkey);
	       if ((ops != NULL) && (ops->which == P_COLOR)) {
		  colorkey = ops->key;
		  curcolor = ops->parameter.ivalue;
	       }
	    }
	 }

	 /* end the color change, returning to default */

	 else if (!strcmp(keyword, "sce")) {
	    curcolor = ccolor;
	    colorkey = NULL;
	 }

	 /* begin a path constructor */

	 else if (!strcmp(keyword, "beginpath")) {
	    NEW_PATH(newpath, localdata);
	    (*newpath)->plist = (genericptr *)malloc(sizeof(genericptr));
	    (*newpath)->parts = 0;
	    (*newpath)->color = curcolor;
	    (*newpath)->passed = NULL;

	    lineptr = varpscan(localdata, buffer, &startpoint.x,
			(genericptr)*newpath, 0, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &startpoint.y,
			(genericptr)*newpath, 0, offy, P_POSITION_Y);

	    std_eparam((genericptr)(*newpath), colorkey);
	 }

	 /* end the path constructor */

	 else if (!strcmp(keyword, "endpath")) {

	    lineptr = varscan(localdata, buffer, &(*newpath)->style,
			(genericptr)*newpath, P_STYLE);
	    lineptr = varfscan(localdata, lineptr, &(*newpath)->width,
			(genericptr)*newpath, P_LINEWIDTH);

	    if ((*newpath)->parts > 0) {
	       localdata->parts++;
	    }
	    else {	/* in case of an empty path */
	       free((*newpath)->plist);
	       free(*newpath);
	    }
	    newpath = NULL;
	 }

	 /* read path parts */

	 else if (!strcmp(keyword, "polyc")) {
	    polyptr *newpoly;
	    pointlist newpoints;
	    short tmpnum;

	    NEW_POLY(newpoly, (*newpath));
	    (*newpath)->parts++;

	    for (--keyptr; *keyptr == ' '; keyptr--);
	    for (; *keyptr != ' '; keyptr--);
	    sscanf(keyptr, "%hd", &tmpnum);
	    (*newpoly)->number = tmpnum + 1;
	    (*newpoly)->width = 1.0;
	    (*newpoly)->style = UNCLOSED;
	    (*newpoly)->color = curcolor;
	    (*newpoly)->passed = NULL;

            (*newpoly)->points = (pointlist) malloc((*newpoly)->number * 
		   sizeof(XPoint));

	    lineptr = buffer;
            for (newpoints = (*newpoly)->points + (*newpoly)->number - 1;
		   newpoints > (*newpoly)->points; newpoints--) {

	       lineptr = varpscan(localdata, lineptr, &newpoints->x,
			(genericptr)*newpoly, newpoints - (*newpoly)->points,
			 offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &newpoints->y,
			(genericptr)*newpoly, newpoints - (*newpoly)->points,
			offy, P_POSITION_Y);
	    }
	    newpoints->x = startpoint.x;
	    newpoints->y = startpoint.y;
	    startpoint.x = (newpoints + (*newpoly)->number - 1)->x;
	    startpoint.y = (newpoints + (*newpoly)->number - 1)->y;
	 }

	 else if (!strcmp(keyword, "arc") || !strcmp(keyword, "arcn")) {
	    arcptr *newarc;
	    NEW_ARC(newarc, (*newpath));
	    (*newpath)->parts++;
	    (*newarc)->width = 1.0;
	    (*newarc)->style = UNCLOSED;
	    (*newarc)->color = curcolor;
	    (*newarc)->passed = NULL;

	    lineptr = varpscan(localdata, buffer, &(*newarc)->position.x,
			(genericptr)*newarc, 0, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newarc)->position.y,
			(genericptr)*newarc, 0, offy, P_POSITION_Y);
	    lineptr = varscan(localdata, lineptr, &(*newarc)->radius,
			(genericptr)*newarc, P_RADIUS);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle1,
			(genericptr)*newarc, P_ANGLE1);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle2,
			(genericptr)*newarc, P_ANGLE2);

	    (*newarc)->yaxis = (*newarc)->radius;
	    if (!strcmp(keyword, "arcn")) {
	       float tmpang = (*newarc)->angle1;
	       (*newarc)->radius = -((*newarc)->radius);
	       (*newarc)->angle1 = (*newarc)->angle2;
	       (*newarc)->angle2 = tmpang;
	    }
		
	    calcarc(*newarc);
	    startpoint.x = (short)(*newarc)->points[(*newarc)->number - 1].x;
	    startpoint.y = (short)(*newarc)->points[(*newarc)->number - 1].y;
	 }

	 else if (!strcmp(keyword, "pellip") || !strcmp(keyword, "nellip")) {
	    arcptr *newarc;
	    NEW_ARC(newarc, (*newpath));
	    (*newpath)->parts++;
	    (*newarc)->width = 1.0;
	    (*newarc)->style = UNCLOSED;
	    (*newarc)->color = curcolor;
	    (*newarc)->passed = NULL;

	    lineptr = varpscan(localdata, buffer, &(*newarc)->position.x,
			(genericptr)*newarc, 0, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newarc)->position.y,
			(genericptr)*newarc, 0, offy, P_POSITION_Y);
	    lineptr = varscan(localdata, lineptr, &(*newarc)->radius,
			(genericptr)*newarc, P_RADIUS);
	    lineptr = varscan(localdata, lineptr, &(*newarc)->yaxis,
			(genericptr)*newarc, P_MINOR_AXIS);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle1,
			(genericptr)*newarc, P_ANGLE1);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle2,
			(genericptr)*newarc, P_ANGLE2);

	    if (!strcmp(keyword, "nellip")) {
	       float tmpang = (*newarc)->angle1;
	       (*newarc)->radius = -((*newarc)->radius);
	       (*newarc)->angle1 = (*newarc)->angle2;
	       (*newarc)->angle2 = tmpang;
		
	    }
	    calcarc(*newarc);
	    startpoint.x = (short)(*newarc)->points[(*newarc)->number - 1].x;
	    startpoint.y = (short)(*newarc)->points[(*newarc)->number - 1].y;
	 }

	 else if (!strcmp(keyword, "curveto")) {
	    splineptr *newspline;
	    NEW_SPLINE(newspline, (*newpath));
	    (*newpath)->parts++;

	    (*newspline)->passed = NULL;
	    (*newspline)->width = 1.0;
	    (*newspline)->style = UNCLOSED;
	    (*newspline)->color = curcolor;

	    lineptr = varpscan(localdata, buffer, &(*newspline)->ctrl[1].x,
			(genericptr)*newspline, 1, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[1].y,
			(genericptr)*newspline, 1, offy, P_POSITION_Y);
	    lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[2].x,
			(genericptr)*newspline, 2, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[2].y,
			(genericptr)*newspline, 2, offy, P_POSITION_Y);
	    lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[3].x,
			(genericptr)*newspline, 3, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[3].y,
			(genericptr)*newspline, 3, offy, P_POSITION_Y);

	    (*newspline)->ctrl[0].x = startpoint.x;
	    (*newspline)->ctrl[0].y = startpoint.y;

	    calcspline(*newspline);
	    startpoint.x = (*newspline)->ctrl[3].x;
	    startpoint.y = (*newspline)->ctrl[3].y;
	 }

         /* read arcs */

         else if (!strcmp(keyword, "xcarc")) {
	    arcptr *newarc;
	 
	    NEW_ARC(newarc, localdata);
	    (*newarc)->color = curcolor;
	    (*newarc)->passed = NULL;

	    /* backward compatibility */
	    if (version < 1.5) {
	       sscanf(buffer, "%hd %hd %hd %f %f %f %hd", &(*newarc)->position.x,
	          &(*newarc)->position.y, &(*newarc)->radius, &(*newarc)->angle1,
	          &(*newarc)->angle2, &(*newarc)->width, &(*newarc)->style);
	       (*newarc)->position.x -= offx;
	       (*newarc)->position.y -= offy;
	    }
	    else {
	       lineptr = varscan(localdata, buffer, &(*newarc)->style,
				(genericptr)*newarc, P_STYLE);
	       lineptr = varfscan(localdata, lineptr, &(*newarc)->width,
				(genericptr)*newarc, P_LINEWIDTH);
	       lineptr = varpscan(localdata, lineptr, &(*newarc)->position.x,
				(genericptr)*newarc, 0, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newarc)->position.y,
				(genericptr)*newarc, 0, offy, P_POSITION_Y);
	       lineptr = varscan(localdata, lineptr, &(*newarc)->radius,
				(genericptr)*newarc, P_RADIUS);
	       lineptr = varfscan(localdata, lineptr, &(*newarc)->angle1,
				(genericptr)*newarc, P_ANGLE1);
	       lineptr = varfscan(localdata, lineptr, &(*newarc)->angle2,
				(genericptr)*newarc, P_ANGLE2);
	    }

	    (*newarc)->yaxis = (*newarc)->radius;
	    calcarc(*newarc);
	    localdata->parts++;
	    std_eparam((genericptr)(*newarc), colorkey);
         }

	 /* read ellipses */

         else if (!strcmp(keyword, "ellipse")) {
	    arcptr *newarc;
	 
	    NEW_ARC(newarc, localdata);

	    (*newarc)->color = curcolor;
	    (*newarc)->passed = NULL;

	    lineptr = varscan(localdata, buffer, &(*newarc)->style,
			(genericptr)*newarc, P_STYLE);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->width,
			(genericptr)*newarc, P_LINEWIDTH);
	    lineptr = varpscan(localdata, lineptr, &(*newarc)->position.x,
			(genericptr)*newarc, 0, offx, P_POSITION_X);
	    lineptr = varpscan(localdata, lineptr, &(*newarc)->position.y,
			(genericptr)*newarc, 0, offy, P_POSITION_Y);
	    lineptr = varscan(localdata, lineptr, &(*newarc)->radius,
			(genericptr)*newarc, P_RADIUS);
	    lineptr = varscan(localdata, lineptr, &(*newarc)->yaxis,
			(genericptr)*newarc, P_MINOR_AXIS);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle1,
			(genericptr)*newarc, P_ANGLE1);
	    lineptr = varfscan(localdata, lineptr, &(*newarc)->angle2,
			(genericptr)*newarc, P_ANGLE2);

	    calcarc(*newarc);
	    localdata->parts++;
	    std_eparam((genericptr)(*newarc), colorkey);
         }

         /* read polygons */
	 /* (and wires---backward compatibility for v1.5 and earlier) */

         else if (!strcmp(keyword, "polygon") || !strcmp(keyword, "wire")) {
	    polyptr *newpoly;
	    pointlist newpoints;

	    NEW_POLY(newpoly, localdata);
	    lineptr = buffer;

	    (*newpoly)->passed = NULL;

	    if (!strcmp(keyword, "wire")) {
	       (*newpoly)->number = 2;
	       (*newpoly)->width = 1.0;
	       (*newpoly)->style = UNCLOSED;
	    }
	    else {
	       /* backward compatibility */
	       if (version < 1.5) {
	          for (--keyptr; *keyptr == ' '; keyptr--);
	          for (; *keyptr != ' '; keyptr--);
	          sscanf(keyptr, "%hd", &(*newpoly)->style);
	          for (--keyptr; *keyptr == ' '; keyptr--);
	          for (; *keyptr != ' '; keyptr--);
	          sscanf(keyptr, "%f", &(*newpoly)->width);
	       }
	       for (--keyptr; *keyptr == ' '; keyptr--);
	       for (; *keyptr != ' '; keyptr--);
	       sscanf(keyptr, "%hd", &(*newpoly)->number);

	       if (version >= 1.5) {
	          lineptr = varscan(localdata, lineptr, &(*newpoly)->style,
				(genericptr)*newpoly, P_STYLE);
	          lineptr = varfscan(localdata, lineptr, &(*newpoly)->width,
				(genericptr)*newpoly, P_LINEWIDTH);
	       }
	    }

	    if ((*newpoly)->style & BBOX)
	       (*newpoly)->color = BBOXCOLOR;
	    else
	       (*newpoly)->color = curcolor;
            (*newpoly)->points = (pointlist) malloc((*newpoly)->number * 
		   sizeof(XPoint));

            for (newpoints = (*newpoly)->points; newpoints < (*newpoly)->points
		+ (*newpoly)->number; newpoints++) {
	       lineptr = varpscan(localdata, lineptr, &newpoints->x,
			(genericptr)*newpoly, newpoints - (*newpoly)->points,
			offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &newpoints->y,
			(genericptr)*newpoly, newpoints - (*newpoly)->points,
			offy, P_POSITION_Y);
	    }
	    localdata->parts++;
	    std_eparam((genericptr)(*newpoly), colorkey);
         }

	 /* read spline curves */

         else if (!strcmp(keyword, "spline")) {
            splineptr *newspline;

	    NEW_SPLINE(newspline, localdata);
	    (*newspline)->color = curcolor;
	    (*newspline)->passed = NULL;

	    /* backward compatibility */
	    if (version < 1.5) {
               sscanf(buffer, "%f %hd %hd %hd %hd %hd %hd %hd %hd %hd", 
	          &(*newspline)->width, &(*newspline)->ctrl[1].x,
	          &(*newspline)->ctrl[1].y, &(*newspline)->ctrl[2].x,
	          &(*newspline)->ctrl[2].y, &(*newspline)->ctrl[3].x,
	          &(*newspline)->ctrl[3].y, &(*newspline)->ctrl[0].x,
	          &(*newspline)->ctrl[0].y, &(*newspline)->style);
	       (*newspline)->ctrl[1].x -= offx; (*newspline)->ctrl[2].x -= offx;
	       (*newspline)->ctrl[0].x -= offx;
	       (*newspline)->ctrl[3].x -= offx;
	       (*newspline)->ctrl[1].y -= offy; (*newspline)->ctrl[2].y -= offy;
	       (*newspline)->ctrl[3].y -= offy;
	       (*newspline)->ctrl[0].y -= offy;
	    }
	    else {

	       lineptr = varscan(localdata, buffer, &(*newspline)->style,
				(genericptr)*newspline, P_STYLE);
	       lineptr = varfscan(localdata, lineptr, &(*newspline)->width,
				(genericptr)*newspline, P_LINEWIDTH);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[1].x,
				(genericptr)*newspline, 1, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[1].y,
				(genericptr)*newspline, 1, offy, P_POSITION_Y);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[2].x,
				(genericptr)*newspline, 2, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[2].y,
				(genericptr)*newspline, 2, offy, P_POSITION_Y);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[3].x,
				(genericptr)*newspline, 3, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[3].y,
				(genericptr)*newspline, 3, offy, P_POSITION_Y);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[0].x,
				(genericptr)*newspline, 0, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newspline)->ctrl[0].y,
				(genericptr)*newspline, 0, offy, P_POSITION_Y);
	    }

            localdata->parts++;
	    calcspline(*newspline);
	    std_eparam((genericptr)(*newspline), colorkey);
         }

         /* read labels */

         else if (!strcmp(keyword, "fontset")) { 	/* old style */
            char tmpstring[100];
            int i;
            sscanf(buffer, "%f %*c%99s", &tmpscale, tmpstring);
            for (i = 0; i < fontcount; i++)
               if (!strcmp(tmpstring, fonts[i].psname)) {
		  tmpfont = i;
		  break;
	       }
	    if (i == fontcount) i = 0;	/* Why bother with anything fancy? */
         }

         else if (!strcmp(keyword, "label") || !strcmp(keyword, "pinlabel")
		|| !strcmp(keyword, "pinglobal") || !strcmp(keyword, "infolabel")) {

	    labelptr *newlabel;
	    stringpart *firstscale, *firstfont;

	    NEW_LABEL(newlabel, localdata);
	    (*newlabel)->color = curcolor;
	    (*newlabel)->string = NULL;
	    (*newlabel)->passed = NULL;

	    /* scan backwards to get the number of substrings */
	    lineptr = keyptr - 1;
	    for (i = 0; i < ((version < 2.25) ? 5 : 6); i++) {
	       for (; *lineptr == ' '; lineptr--);
	       for (; *lineptr != ' '; lineptr--);
	    }
	    if ((strchr(lineptr, '.') != NULL) && (version < 2.25)) {
	       Fprintf(stderr, "Error:  File version claims to be %2.1f,"
			" but has version %2.1f labels\n", version, PROG_VERSION);
	       Fprintf(stderr, "Attempting to resolve problem by updating version.\n");
	       version = PROG_VERSION;
	       for (; *lineptr == ' '; lineptr--);
	       for (; *lineptr != ' '; lineptr--);
	    }
	    /* no. segments is ignored---may be a derived quantity, anyway */
	    if (version < 2.25) {
	       sscanf(lineptr, "%*s %hd %hd %hd %hd", &(*newlabel)->justify,
		   &(*newlabel)->rotation, &(*newlabel)->position.x,
		   &(*newlabel)->position.y);
	       (*newlabel)->position.x -= offx; (*newlabel)->position.y -= offy;
	       *lineptr = '\0';
	    }
	    else {
	       *lineptr++ = '\0';
	       lineptr = advancetoken(lineptr);  /* skip string token */
	       lineptr = varscan(localdata, lineptr, &(*newlabel)->justify,
				(genericptr)*newlabel, P_JUSTIFY);
	       lineptr = varscan(localdata, lineptr, &(*newlabel)->rotation,
				(genericptr)*newlabel, P_ROTATION);
	       lineptr = varfscan(localdata, lineptr, &(*newlabel)->scale,
				(genericptr)*newlabel, P_SCALE);
	       lineptr = varpscan(localdata, lineptr, &(*newlabel)->position.x,
				(genericptr)*newlabel, 0, offx, P_POSITION_X);
	       lineptr = varpscan(localdata, lineptr, &(*newlabel)->position.y,
				(genericptr)*newlabel, 0, offy, P_POSITION_Y);
	    }
	    if (version < 2.4)
	       (*newlabel)->rotation = -(*newlabel)->rotation;
	    while ((*newlabel)->rotation < 0) (*newlabel)->rotation += 360;

	    (*newlabel)->pin = False;
	    if (strcmp(keyword, "label")) {	/* all the schematic types */
	       /* enable schematic capture if it is not already on. */
	       if (!areastruct.schemon) doxschema(NULL, 0, NULL);
	       if (!strcmp(keyword, "pinlabel"))
		  (*newlabel)->pin = LOCAL;
	       else if (!strcmp(keyword, "pinglobal"))
		  (*newlabel)->pin = GLOBAL;
	       else if (!strcmp(keyword, "infolabel")) {
		  localdata->schemtype = FUNDAMENTAL;
		  (*newlabel)->pin = INFO;
		  if (curcolor == DEFAULTCOLOR)
		     (*newlabel)->color = INFOLABELCOLOR;
	       }
	    }

	    lineptr = buffer;	/* back to beginning of string */
	    if (!strncmp(lineptr, "mark", 4)) lineptr += 4;

	    readlabel(localdata, lineptr, &(*newlabel)->string);

	    if (version < 2.25) {
	       /* Switch 1st scale designator to overall font scale */

	       firstscale = (*newlabel)->string->nextpart;
	       if (firstscale->type != FONT_SCALE) {
	          if (tmpscale != 0.0)
	             (*newlabel)->scale = 0.0;
	          else
	             (*newlabel)->scale = 1.0;
	       }
	       else {
	          (*newlabel)->scale = firstscale->data.scale;
	          deletestring(firstscale, &((*newlabel)->string),
				areastruct.topinstance);
	       }
	    }

	    firstfont = (*newlabel)->string;
	    if (firstfont->type != FONT_NAME) {
	       if (tmpfont == -1) {
	          Fprintf(stderr, "Error:  Label with no font designator?\n");
		  tmpfont = 0;
	       }
	       firstfont = makesegment(&((*newlabel)->string), (*newlabel)->string);  
	       firstfont->type = FONT_NAME;
	       firstfont->data.font = tmpfont;
	    }
	    cleanuplabel(&(*newlabel)->string);

	    /* link string to the one that corresponds to this one */
	    /* in the symbol, if it exists.			   */

	    if (areastruct.schemon && localdata->symschem != NULL &&
		  (*newlabel)->pin && ((*newlabel)->pin != INFO)) {
	       genericptr *pgen;
	       for (pgen = localdata->symschem->plist; pgen < localdata->
			symschem->plist + localdata->symschem->parts; pgen++) {
		  if (IS_LABEL(*pgen))
		     if (!stringcomp(TOLABEL(pgen)->string, (*newlabel)->string))
		 	break;
	       }
	       if (pgen == localdata->symschem->plist + localdata->symschem->parts)
		  Wprintf("Error:  Unattached pin");
	    }

	    localdata->parts++;
	    std_eparam((genericptr)(*newlabel), colorkey);
         }

	 /* read symbol-to-schematic connection */

	 else if (!strcmp(keyword, "is_schematic")) {
	    char tempstr[50];
	    for (lineptr = buffer; *lineptr == ' '; lineptr++);
	    sscanf(++lineptr, "%49s", tempstr);
	    checksym(localdata, tempstr);
	 }

         /* read bounding box (font files only)	*/

         else if (!strcmp(keyword, "bbox")) {
	    for (lineptr = buffer; *lineptr == ' '; lineptr++);
            if (*lineptr != '%') {
	       Wprintf("Illegal bbox.");
	       free(buffer);
               *retstr = '\0';
	       return True;
	    }
	    sscanf(++lineptr, "%hd %hd %hd %hd",
		&localdata->bbox.lowerleft.x, &localdata->bbox.lowerleft.y,
		&localdata->bbox.width, &localdata->bbox.height);
         }

	 /* read "hidden" attribute */

	 else if (!strcmp(keyword, "hidden")) {
	    localdata->hidden = True;
	 }

	 /* read "libinst" special instance of a library part */

	 else if (!strcmp(keyword, "libinst")) {

	    /* Read backwards from keyword to find name of object instanced. */
	    for (lineptr = keyptr; *lineptr != '/' && lineptr > buffer;
			lineptr--);
	    sscanf(++lineptr, "%79s", keyword);
	    new_library_instance(mode - LIBRARY, keyword, buffer);
	 }

	 /* read objects */

         else if (!strcmp(keyword, "{")) {  /* This is an object definition */
	    objlistptr redef;
	    objectptr *newobject;

	    for (lineptr = buffer; *lineptr == ' '; lineptr++);
	    if (*lineptr++ != '/') {
	       /* This may be part of a label. . . treat as a continuation line */
	       temp = continueline(&buffer);
	       continue;
	    }
	    sscanf(lineptr, "%79s", keyword);

	    newobject = new_library_object(mode, keyword, &redef);

	    if (objectread(ps, *newobject, 0, 0, mode, retstr, curcolor) == True) {
               strncpy(retstr, buffer, 150);
               retstr[149] = '\0';
	       free(buffer);
	       return True;
            }
	    else {
	       if (library_object_unique(mode, *newobject, redef))
	          add_object_to_library(mode, *newobject);
	    }
         }
         else if (!strcmp(keyword, "def")) {
            strncpy(retstr, buffer, 150);
            retstr[149] = '\0';
	    free (buffer);
	    return False; /* end of object def or end of object library */
	 }

	 else if (!strcmp(keyword, "loadfontencoding")) {
	    /* Deprecated, but retained for backward compatibility. */
	    /* Load from script, .xcircuitrc, or command line instead. */
	    for (lineptr = buffer; *lineptr != '%'; lineptr++);
	    sscanf (lineptr + 1, "%149s", _STR);
	    if (*(lineptr + 1) != '%') loadfontfile(_STR);
	 }
	 else if (!strcmp(keyword, "loadlibrary")) {
	    /* Deprecated, but retained for backward compatibility */
	    /* Load from script, .xcircuitrc, or command line instead. */
	    int ilib, tlib;

	    for (lineptr = buffer; *lineptr != '%'; lineptr++);
	    sscanf (++lineptr, "%149s", _STR);
	    while (isspace(*lineptr)) lineptr++;
	    while (!isspace(*++lineptr));
	    while (isspace(*++lineptr));
	    if (sscanf (lineptr, "%d", &ilib) > 0) {
	       while ((ilib - 2 + LIBRARY) > xobjs.numlibs) {
		  tlib = createlibrary();
		  if (tlib != xobjs.numlibs - 2 + LIBRARY) {
		     ilib = tlib;
		     break;
		  }
	       }
	       mode = ilib - 1 + LIBRARY;
	    }
	    loadlibrary(mode);
	 }
	 else if (!strcmp(keyword, "beginparm")) { /* parameterized object */
	    short tmpnum, i;
	    for (--keyptr; *keyptr == ' '; keyptr--);
	    for (; isdigit(*keyptr) && (keyptr >= buffer); keyptr--);
	    sscanf(keyptr, "%hd", &tmpnum);
	    lineptr = buffer;
	    while (isspace(*lineptr)) lineptr++;

	    if (tmpnum < 256) {		/* read parameter defaults in order */
	       stringpart *newpart;
	       oparamptr newops;

	       for (i = 0; i < tmpnum; i++) {
		  oparamptr newops;

	          newops = (oparamptr)malloc(sizeof(oparam));
		  newops->next = localdata->params;
		  localdata->params = newops;
		  newops->key = (char *)malloc(6);
		  sprintf(newops->key, "v%d", i + 1);

		  if (*lineptr == '(' || *lineptr == '{') {  /* type is XC_STRING */
		     char *linetmp, csave;
		     
		     newops->parameter.string = NULL;

		     /* get simple substring or set of substrings and commands */
		     linetmp = find_delimiter(lineptr);
		     csave = *(++linetmp);
		     *linetmp = '\0';
		     if (*lineptr == '{') lineptr++;
		     readlabel(localdata, lineptr, &(newops->parameter.string));

		     /* Add the ending part to the parameter string */
		     newpart = makesegment(&(newops->parameter.string), NULL);  
		     newpart->type = PARAM_END;
		     newpart->data.string = (u_char *)NULL;

		     newops->type = (u_char)XC_STRING;
		     newops->which = P_SUBSTRING;
		     /* Fprintf(stdout, "Parameter %d to object %s defaults "
				"to string \"%s\"\n", i + 1, localdata->name,
				ops->parameter.string); */
		     *linetmp = csave;
		     lineptr = linetmp;
		     while (isspace(*lineptr)) lineptr++;
		  }
		  else {	/* type is assumed to be XC_FLOAT */
		     newops->type = (u_char)XC_FLOAT;
	             sscanf(lineptr, "%g", &newops->parameter.fvalue);
		     /* Fprintf(stdout, "Parameter %s to object %s defaults to "
				"value %g\n", newops->key, localdata->name,
				newops->parameter.fvalue); */
		     lineptr = advancetoken(lineptr);
		  }
	       }
	    }
	 }
	 else if (!strcmp(keyword, "trivial")) {
	    localdata->schemtype = TRIVIAL;
	    if (!areastruct.schemon) doxschema(NULL, 0, NULL);
	 }
         else if (!strcmp(keyword, "begingate")) {
	    localdata->params = NULL;
	    /* read dictionary of parameter key:value pairs */
	    readparams(NULL, NULL, localdata, buffer);
	 }

         else if (!strcmp(keyword, "%%Trailer")) break;
         else if (!strcmp(keyword, "EndLib")) break;
	 else if (!strcmp(keyword, "restore"));    /* handled at top */
	 else if (!strcmp(keyword, "grestore"));   /* ignore */
         else if (!strcmp(keyword, "endgate"));    /* also ignore */
	 else if (!strcmp(keyword, "xyarray"));	   /* ignore for now */
         else {
	    char *tmpptr;
	    Boolean found = False;

	    /* first, make sure this is not a general comment line */
	    /* and return if we have a page boundary	   	   */

	    for (tmpptr = buffer; isspace(*tmpptr); tmpptr++);
	    if (*tmpptr == '%') {
	       if (strstr(buffer, "%%Page:") == tmpptr) {
                  strncpy(retstr, buffer, 150);
                  retstr[149] = '\0';
		  free (buffer);
		  return True;
	       }
	       continue;
	    }

	    /* (Assume that this line calls an object instance) */
	    /* Double loop through user libraries 		*/

            for (k = 0; k < ((mode == FONTLIB) ? 1 : xobjs.numlibs); k++) {
	       for (j = 0; j < ((mode == FONTLIB) ? xobjs.fontlib.number :
			xobjs.userlibs[k].number); j++) {
		  libobj = (mode == FONTLIB) ? xobjs.fontlib.library + j :
			xobjs.userlibs[k].library + j;

	          if (!objnamecmp(keyword, (*libobj)->name)) {

		     /* If the name is not exactly the same (appended underscores) */
		     /* check if the name is on the list of aliases. */

		     if (strcmp(keyword, (*libobj)->name)) {
			Boolean is_alias = False;
			aliasptr ckalias = aliastop;
			slistptr sref;

			for (; ckalias != NULL; ckalias = ckalias->next) {
			   if (ckalias->baseobj == (*libobj)) {
			      sref = ckalias->aliases;
			      for (; sref != NULL; sref = sref->next) {
			         if (!strcmp(keyword, sref->alias)) {
				    is_alias = True;
				    break;
				 }
			      }
			      if (is_alias) break;
			   }
			}
			if (!is_alias) continue;
		     }

		     found = True;
		     NEW_OBJINST(newinst, localdata);
      	             localdata->parts++;
		     (*newinst)->thisobject = *libobj;
		     (*newinst)->color = curcolor;
		     (*newinst)->params = NULL;
		     (*newinst)->passed = NULL;
		     (*newinst)->bbox.lowerleft.x = (*libobj)->bbox.lowerleft.x;
		     (*newinst)->bbox.lowerleft.y = (*libobj)->bbox.lowerleft.y;
		     (*newinst)->bbox.width = (*libobj)->bbox.width;
		     (*newinst)->bbox.height = (*libobj)->bbox.height;
		     (*newinst)->schembbox = NULL;

	             lineptr = varfscan(localdata, buffer, &(*newinst)->scale,
				(genericptr)*newinst, P_SCALE);
	       	     lineptr = varscan(localdata, lineptr, &(*newinst)->rotation,
				(genericptr)*newinst, P_ROTATION);
	             lineptr = varpscan(localdata, lineptr, &(*newinst)->position.x,
				(genericptr)*newinst, 0, offx, P_POSITION_X);
	             lineptr = varpscan(localdata, lineptr, &(*newinst)->position.y,
				(genericptr)*newinst, 0, offy, P_POSITION_Y);

		     /* Negative rotations = flip in x in version 2.3.6 and    */
		     /* earlier.  Later versions don't allow negative rotation */

	    	     if (version < 2.4) {
                        if ((*newinst)->rotation < 0) {
			   (*newinst)->scale = -((*newinst)->scale);
			   (*newinst)->rotation++;
		        }
		        (*newinst)->rotation = -(*newinst)->rotation;
		     }

                     while ((*newinst)->rotation > 360) (*newinst)->rotation -= 360;
                     while ((*newinst)->rotation < 0) (*newinst)->rotation += 360;

		     calcbboxinst(*newinst);
		     std_eparam((genericptr)(*newinst), colorkey);

		     /* Does this instance contain parameters? */
		     readparams(localdata, *newinst, *libobj, buffer);
		     break;

	          } /* if !strcmp */
	       } /* for j loop */
	       if (found) break;
	    } /* for k loop */
	    if (!found)		/* will assume that we have a continuation line */
	       temp = continueline(&buffer);
         }
      }
   }
   strncpy(retstr, buffer, 150);
   retstr[149] = '\0';
   free(buffer);
   return True;
}

/*------------------------*/
/* Save a PostScript file */
/*------------------------*/

#ifdef TCL_WRAPPER

void setfile(char *filename, int mode)
{
   /* see if name has been changed in the buffer */

   if (strcmp(xobjs.pagelist[areastruct.page]->filename, filename)) {
      Wprintf("Changing name of edit file.");
      free(xobjs.pagelist[areastruct.page]->filename);
      xobjs.pagelist[areastruct.page]->filename = strdup(filename);
   }

   if (strstr(xobjs.pagelist[areastruct.page]->filename, "Page ") != NULL) {
      Wprintf("Warning: Enter a new name.");
      if (beeper) XBell(dpy, 100);
   }
   else {
      savefile(mode); 
      if (beeper) XBell(dpy, 100);
   }
}

#else  /* !TCL_WRAPPER */

void setfile(xcWidget button, xcWidget fnamewidget, caddr_t clientdata) 
{
   /* see if name has been changed in the buffer */

   sprintf(_STR2, "%.249s", (char *)XwTextCopyBuffer(fnamewidget));
   if (strcmp(xobjs.pagelist[areastruct.page]->filename, _STR2)) {
      Wprintf("Changing name of edit file.");
      free(xobjs.pagelist[areastruct.page]->filename);
      xobjs.pagelist[areastruct.page]->filename = strdup(_STR2);
   }
   if (strstr(xobjs.pagelist[areastruct.page]->filename, "Page ") != NULL) {
      Wprintf("Warning: Enter a new name.");
      if (beeper) XBell(dpy, 100);
   }
   else {

      Arg wargs[1];
      xcWidget db, di;

      savefile(CURRENT_PAGE); 

      /* Change "close" button to read "done" */

      di = xcParent(button);
      db = XtNameToWidget(di, "Close");
      XtSetArg(wargs[0], XtNlabel, "  Done  ");
      XtSetValues(db, wargs, 1);
      if (beeper) XBell(dpy, 100);
   }
}

#endif  /* TCL_WRAPPER */

/*--------------------------------------------------------------*/
/* Update number of changes for an object and initiate a temp	*/
/* file save if total number of unsaved changes exceeds 20.	*/
/*--------------------------------------------------------------*/

void incr_changes(objectptr thisobj)
{
   /* It is assumed that empty pages are meant to be that way */
   /* and are not to be saved, so changes are marked as zero. */

   if (thisobj->parts == 0) {
      thisobj->changes = 0;
      return;
   }

   thisobj->changes++;
   xobjs.new_changes++;

   if (xobjs.new_changes > MAXCHANGES) {
#ifdef TCL_WRAPPER
      savetemp(NULL);
#else
      savetemp(NULL, NULL);
#endif
      xobjs.new_changes = 0;	/* reset the count */
   }
}

/*--------------------------------------------------------------*/
/* tempfile save						*/
/*--------------------------------------------------------------*/

#ifdef TCL_WRAPPER

xcTimeOutProc savetemp(caddr_t clientdata)
{
   if (xobjs.timeout_id == NULL) { 	/* Not called by timeout callback */
      xcRemoveTimeOut(xobjs.timeout_id);
   }
   xobjs.timeout_id = NULL;

#else

xcTimeOutProc savetemp(caddr_t clientdata, xcIntervalId *id)
{
   if (id == NULL) 	/* Not called by timeout callback */
      xcRemoveTimeOut(xobjs.timeout_id);

#endif

   /* First see if there are any unsaved changes in the file.	*/
   /* If not, then just reset the counter and continue.  	*/

   if (countchanges(NULL) > 0) {
      if (xobjs.tempfile == NULL)
      {
         int fd;
         char *template = (char *)malloc(10 + strlen(xobjs.tempdir));

         sprintf(template, "%s/XCXXXXXX", xobjs.tempdir);

         fd = mkstemp(template);
         if (fd == -1) {
	    Fprintf(stderr, "Error generating file for savetemp\n");
	    free(template);
	    return NULL;
         } 
         close(fd);
         xobjs.tempfile = strdup(template);
         free(template);
      }
      savefile(ALL_PAGES);
   }

   xobjs.timeout_id = xcAddTimeOut(app, (u_long)60000 *
	xobjs.save_interval, (xcTimeOutProc)savetemp, NULL);
   return NULL;
}

/*----------------------------------------------------------------------*/
/* Set all objects in the list "wroteobjs" as having no unsaved changes */
/*----------------------------------------------------------------------*/

void setassaved(objectptr *wroteobjs, short written)
{
   int i;

   for (i = 0; i < written; i++)
      (*(wroteobjs + i))->changes = 0;
}

/*---------------------------------------------------------------*/
/* Save indicated library.  If libno is 0, save current page if	 */
/* the current page is a library.  If not, save the user library */
/*---------------------------------------------------------------*/

void savelibpopup(xcWidget button, pointertype libno, caddr_t nulldata)
{
   char *bconf;
   buttonsave *savebutton;
   int ilib = (int)libno;

   if (ilib == 0) ilib = is_library(topobject);
   if (ilib < 0) ilib = xobjs.numlibs - 1;
   if ((ilib = is_library(topobject)) < 0) ilib = xobjs.numlibs - 1;

   if (xobjs.userlibs[ilib].number == 0) {
      Wprintf("No objects in library to save.");
      return;
   }

#ifndef TCL_WRAPPER
   savebutton = (buttonsave *)malloc(sizeof(buttonsave));
   getgeneric(savebutton, button, savelibpopup, (void *)ilib);
   popupprompt(button, "Enter name for library:", "\0", savelibrary,
	savebutton, NULL);
#endif
}

/*---------------------------------------------------------*/

void savelibrary(xcWidget w, int ilib)
{
   FILE *ps;
   char outname[150], *outptr;
   objectptr *wroteobjs, *libptr, libobjptr, *optr, depobj;
   genericptr *gptr;
   liblistptr spec;
   short written;
   char *uname = NULL;
   char *hostname = NULL;
   struct passwd *mypwentry = NULL;
   int i;


   sscanf(_STR2, "%249s", outname);
   if ((outptr = strrchr(outname, '/')) == NULL) outptr = outname;
   if (strchr(outptr, '.') == NULL) sprintf(outname, "%s.lps", _STR2);
   xc_tilde_expand(outname);

   ps = fopen(outname, "w");
   if (ps == NULL) {
      Wprintf("Can't open PS file.");
      return;
   }

   fprintf(ps, "%%! PostScript set of library objects for XCircuit\n");
   fprintf(ps, "%%  Version: %2.1f\n", version);
   fprintf(ps, "%%  Library name is: %s\n", _STR2);
   uname = getenv((const char *)"USER");
   if (uname != NULL) mypwentry = getpwnam(uname);

   /* Check for both $HOST and $HOSTNAME environment variables.  Thanks */
   /* to frankie liu <frankliu@Stanford.EDU> for this fix.		*/
   
   if ((hostname = getenv((const char *)"HOSTNAME")) == NULL)
      if ((hostname = getenv((const char *)"HOST")) == NULL) {
	 if (gethostname(_STR, 149) != 0)
	    hostname = uname;
	 else
	    hostname = _STR;
      }

   if (mypwentry != NULL)
      fprintf(ps, "%%  Author: %s <%s@%s>\n", mypwentry->pw_gecos, uname,
		hostname);

   fprintf(ps, "%%\n\n");

   /* Print lists of object dependencies, where they exist. */
   /* Note that collectdepend() will always return the object itself as	*/
   /* the last entry in the list, so we should ignore that one.		*/

   wroteobjs = (objectptr *) malloc(sizeof(objectptr));
   for (spec = xobjs.userlibs[ilib].instlist; spec != NULL; spec = spec->next) {
      if (!spec->virtual) {
	 written = 0;
	 libobjptr = spec->thisinst->thisobject;

	 /* Search for all object definitions instantiated in this object, */
	 /* and add them to the dependency list (non-recursive).	   */

	 for (gptr = libobjptr->plist; gptr < libobjptr->plist
			+ libobjptr->parts; gptr++) {
	    if (IS_OBJINST(*gptr)) {
	       depobj = TOOBJINST(gptr)->thisobject;

	       /* Search among the list of objects already collected.	*/
	       /* If this object has been previously, then ignore it.	*/
	       /* Otherwise, update the list of object dependencies	*/

	       for (optr = wroteobjs; optr < wroteobjs + written; optr++)
		  if (*optr == depobj)
		     break;

	       if (optr == wroteobjs + written) {
		  wroteobjs = (objectptr *)realloc(wroteobjs, (written + 1) * 
				sizeof(objectptr));
		  *(wroteobjs + written) = depobj;
		  written++;
	       }
	    }
	 }
	 if (written > 0) {
	    fprintf(ps, "%% Depend %s", libobjptr->name);
	    for (i = 0; i < written; i++) {
	       depobj = *(wroteobjs + i);
	       fprintf(ps, " %s", depobj->name);
	    }
	    fprintf(ps, "\n");
	 }
      }
   }

   fprintf(ps, "\n%% XCircuitLib library objects\n");

   /* list of library objects already written */

   wroteobjs = (objectptr *)realloc(wroteobjs, sizeof(objectptr));
   written = 0;

   /* write all of the object definitions used, bottom up, with virtual instances */
   /* in the correct placement.							  */

   for (spec = xobjs.userlibs[ilib].instlist; spec != NULL; spec = spec->next) {
      if (!spec->virtual) {
         printobjects(ps, spec->thisinst->thisobject, &wroteobjs, &written,
		DEFAULTCOLOR);
      }
      else {
	 if ((spec->thisinst->scale != 1.0) || (spec->thisinst->rotation != 0)) {
	    fprintf(ps, "%3.2f %d ", spec->thisinst->scale,
				spec->thisinst->rotation);
	 }
         printparams(ps, spec->thisinst, 0);
         fprintf(ps, "/%s libinst\n", spec->thisinst->thisobject->name);
	 if ((spec->next != NULL) && (!(spec->next->virtual)))
	    fprintf(ps, "\n");
      }
   }

   setassaved(wroteobjs, written);
   xobjs.new_changes = countchanges(NULL);

   /* and the postlog */

   fprintf(ps, "\n%% EndLib\n");
   fclose(ps);
   sprintf(_STR, "Library %s saved.", outname);
   Wprintf(_STR);

   free(wroteobjs);
}

/*----------------------------------------------------------------------*/
/* Recursive routine to search the object hierarchy for fonts used	*/
/*----------------------------------------------------------------------*/

void findfonts(objectptr writepage, short *fontsused) {
   genericptr *dfp;
   stringpart *chp;
   int findex;

   for (dfp = writepage->plist; dfp < writepage->plist + writepage->parts; dfp++) {
      if (IS_LABEL(*dfp)) {
         for (chp = TOLABEL(dfp)->string; chp != NULL; chp = chp->nextpart) {
	    if (chp->type == FONT_NAME) {
	       findex = chp->data.font;
	       if (fontsused[findex] == 0) {
	          fontsused[findex] = 0x8000 | fonts[findex].flags;
	       }
	    }
	 }
      }
      else if (IS_OBJINST(*dfp)) {
	 findfonts(TOOBJINST(dfp)->thisobject, fontsused);
      }
   }
}

/*----------------------------------------------------------------------*/
/* Main file saving routine						*/
/*----------------------------------------------------------------------*/

void savefile(short mode) 
{
   FILE *ps, *pro;
   char outname[150], temp[150], prologue[150], *fname, *fptr;
   short written, fontsused[256], i, page, curpage, multipage;
   short savepage, stcount, *pagelist;
   objectptr *wroteobjs;
   objinstptr writepage;
   int findex;
   time_t tdate;
   char *tmp_s;

   if (mode != ALL_PAGES)
      fname = xobjs.pagelist[areastruct.page]->filename;
   else {
      /* doubly-protected backup: protect against errors during file write */
      sprintf(outname, "%sB", xobjs.tempfile);
      rename(xobjs.tempfile, outname);
      fname = xobjs.tempfile;
   }

   if ((fptr = strrchr(fname, '/')) == NULL) fptr = fname;
   if ((mode != ALL_PAGES) && (strchr(fptr, '.') == NULL))
      sprintf(outname, "%s.ps", fname);
   else sprintf(outname, "%s", fname);

   xc_tilde_expand(outname);

   ps = fopen(outname, "w");
   if (ps == NULL) {
      Wprintf("Can't open PS file.");
      return;
   }

   /* calculate the bounding box of the drawing */

   /* find all the schematics which are subcircuits of the top-level-page */
   /* circuit and give them the same filename, so that they will be saved */
   /* together as a set.						  */

   if ((areastruct.schemon) && (mode != NO_SUBCIRCUITS)) {
      Wprintf("Gathering all subcircuits. . .");
      collectsubschems(areastruct.page);
   }

   /* Check for multiple-page output: get the number of pages;	*/
   /* ignore empty pages.					*/

   multipage = 0;
   savepage = areastruct.page;

   if (mode == NO_SUBCIRCUITS)
      pagelist = pagetotals(areastruct.page, INDEPENDENT);
   else if (mode == ALL_PAGES)
      pagelist = pagetotals(areastruct.page, ALL_PAGES);
   else
      pagelist = pagetotals(areastruct.page, TOTAL_PAGES);

   for (page = 0; page < xobjs.pages; page++)
      if (pagelist[page] > 0)
	  multipage++;

   if (multipage == 0) {
      Wprintf("Panic:  could not find this page in page list!");
      free (pagelist);
      fclose(ps);
      return;
   }

   /* Print the PostScript DSC Document Header */

   fprintf(ps, "%%!PS-Adobe-3.0");
   if (multipage == 1 && !(xobjs.pagelist[areastruct.page]->pmode & 1))
      fprintf(ps, " EPSF-3.0\n");
   else
      fprintf(ps, "\n");
   fprintf(ps, "%%%%Title: %s\n", xobjs.pagelist[areastruct.page]->filename);
   fprintf(ps, "%%%%Creator: Xcircuit v%2.1f\n", PROG_VERSION);
   tdate = time(NULL);
   fprintf(ps, "%%%%CreationDate: %s", asctime(localtime(&tdate)));
   fprintf(ps, "%%%%Pages: %d\n", multipage);

   /* This is just a default value; each bounding box is declared per	*/
   /* page by the DSC "PageBoundingBox" keyword.			*/
   /* However, encapsulated files adjust the bounding box to center on	*/
   /* the object, instead of centering the object on the page.		*/

   if (multipage == 1 && !(xobjs.pagelist[areastruct.page]->pmode & 1)) {
      objectptr thisobj = xobjs.pagelist[areastruct.page]->pageinst->thisobject;
      float psscale = getpsscale(xobjs.pagelist[areastruct.page]->outscale,
			areastruct.page);

      /* The top-level bounding box determines the size of an encapsulated */
      /* drawing, regardless of the PageBoundingBox numbers.  Therefore,   */
      /* we size this to bound just the picture by closing up the 1" (72   */
      /* PostScript units) margins, except for a tiny sliver of a margin   */
      /* (4 PostScript units) which covers a bit of sloppiness in the font */
      /* measurements.							   */
	
      fprintf(ps, "%%%%BoundingBox: 68 68 %d %d\n",
	 (int)((float)thisobj->bbox.width * psscale) + 76,
	 (int)((float)thisobj->bbox.height * psscale) + 76);
   }
   else if (xobjs.pagelist[0]->coordstyle == CM)
      fprintf(ps, "%%%%BoundingBox: 0 0 595 812\n");	/* A4 default */
   else
      fprintf(ps, "%%%%BoundingBox: 0 0 612 792\n");	/* letter default */

   for (i = 0; i <= fontcount; i++) fontsused[i] = 0;
   fprintf(ps, "%%%%DocumentNeededResources: ");
   stcount = 27;

   /* find all of the fonts used in this document */
   /* log all fonts which are native PostScript   */

   for (curpage = 0; curpage < xobjs.pages; curpage++)
      if (pagelist[curpage] > 0) {
         writepage = xobjs.pagelist[curpage]->pageinst;
         findfonts(writepage->thisobject, fontsused);
      }
      
   for (i = 0; i <= fontcount; i++) {
      if (fontsused[i] & 0x8000)
	 if ((fonts[i].flags & 0x8018) == 0x0) {
	    stcount += strlen(fonts[i].psname) + 6;
	    if (stcount > OUTPUTWIDTH) {
	       stcount = strlen(fonts[i].psname) + 6;
	       fprintf(ps, "\n%%%%+ ");
	    }
	    fprintf(ps, "font %s ", fonts[i].psname);
	 }
   }

   fprintf(ps, "\n%%%%EndComments\n");

   tmp_s = getenv((const char *)"XCIRCUIT_LIB_DIR");
   if (tmp_s != NULL) {
      sprintf(prologue, "%s/%s", tmp_s, PROLOGUE_FILE);
      pro = fopen(prologue, "r");
   }
   else
      pro = NULL;

   if (pro == NULL) {
      sprintf(prologue, "%s/%s", PROLOGUE_DIR, PROLOGUE_FILE);
      pro = fopen(prologue, "r");
      if (pro == NULL) {
         sprintf(prologue, "%s", PROLOGUE_FILE);
         pro = fopen(prologue, "r");
         if (pro == NULL) {
            Wprintf("Can't open prolog.");
	    free(pagelist);
	    fclose(ps);
            return;
	 }
      }
   }

   /* write the prolog to the output */

   for(;;) {
      if (fgets(temp, 149, pro) == NULL) break;
      fputs(temp, ps);
   }
   fclose(pro);

   /* Special font handling */

   for (findex = 0; findex < fontcount; findex++) {

      /* Derived font slant */

      if ((fontsused[findex] & 0x032) == 0x032)
         fprintf(ps, "/%s /%s .167 fontslant\n\n",
		fonts[findex].psname, fonts[findex].family);

      /* Derived ISO-Latin1 encoding */

      if ((fontsused[findex] & 0xf80) == 0x100) {
	 char *fontorig = NULL;
	 short i;
	 /* find the original standard-encoded font (can be itself) */
	 for (i = 0; i < fontcount; i++) {
	    if (i == findex) continue;
	    if (!strcmp(fonts[i].family, fonts[findex].family) &&
		 ((fonts[i].flags & 0x03) == (fonts[findex].flags & 0x03)))
	       fontorig = fonts[i].psname;
	    if (fontorig == NULL) fontorig = fonts[findex].psname;
	 }
	 fprintf(ps, "/%s findfont dup length dict begin\n", fontorig);
	 fprintf(ps, "{1 index /FID ne {def} {pop pop} ifelse} forall\n");
	 fprintf(ps, "/Encoding ISOLatin1Encoding def currentdict end\n");
	 fprintf(ps, "/%s exch definefont pop\n\n", fonts[findex].psname);
      }

      /* To do:  ISO-Latin2 encoding */

      if ((fontsused[findex] & 0xf80) == 0x180) {
      }

      /* To do:  Special encoding */

      if ((fontsused[findex] & 0xf80) == 0x80) {
      }

      /* To do:  Vectored (drawn) font */

      if (fontsused[findex] & 0x8) {
      }
   }

   /* List of objects already written */
   wroteobjs = (objectptr *) malloc (sizeof(objectptr));
   written = 0;

   fprintf(ps, "%% XCircuit output starts here.\n\n");
   fprintf(ps, "%%%%BeginDocumentSetup\n\n");

   for (curpage = 0; curpage < xobjs.pages; curpage++) {
      if (pagelist[curpage] == 0) continue;

      /* Write all of the object definitions used, bottom up */
      printrefobjects(ps, xobjs.pagelist[curpage]->pageinst->thisobject,
		&wroteobjs, &written);
   }

   fprintf(ps, "\n%%%%EndDocumentSetup\n\n");

   page = 0;
   for (curpage = 0; curpage < xobjs.pages; curpage++) {
      if (pagelist[curpage] == 0) continue;

      /* Print the page header, all elements in the page, and page trailer */
      printpageobject(ps, xobjs.pagelist[curpage]->pageinst->thisobject,
		++page, curpage);

      /* For crash recovery, log the filename for each page */
      if (mode == ALL_PAGES)
	 fprintf(ps, "%% %s is_filename\n", xobjs.pagelist[curpage]->filename);

      fprintf(ps, "\n");
      fflush(ps);
   }

   /* For crash recovery, save all objects that have been edited but are */
   /* not in the list of objects already saved.				 */

   if (mode == ALL_PAGES)
   {
      int i, j, k;
      objectptr thisobj;

      for (i = 0; i < xobjs.numlibs; i++) {
	 for (j = 0; j < xobjs.userlibs[i].number; j++) {
	    thisobj = *(xobjs.userlibs[i].library + j);
	    if (thisobj->changes > 0 ) {
	       for (k = 0; k < written; k++)
	          if (thisobj == *(wroteobjs + k)) break;
	       if (k == written)
      	          printobjects(ps, thisobj, &wroteobjs, &written, DEFAULTCOLOR);
	    }
	 }
      }
   }
   else {	/* No unsaved changes in these objects */
      setassaved(wroteobjs, written);
      for (i = 0; i < xobjs.pages; i++)
	 if (pagelist[i] > 0)
	    xobjs.pagelist[i]->pageinst->thisobject->changes = 0;
      xobjs.new_changes = countchanges(NULL);
   }

   /* Free allocated memory */
   free((char *)pagelist);
   free((char *)wroteobjs);

   /* Done! */

   fprintf(ps, "%%%%Trailer\n");
   fprintf(ps, "XCIRCsave restore\n");
   fprintf(ps, "%%%%EOF\n");
   fclose(ps);

   sprintf(_STR, "File %s saved (%d page%s).", fname, multipage,
		(multipage > 1 ? "s" : ""));
   Wprintf(_STR);

   /* Remove the temporary redundant backup */
   if (mode == ALL_PAGES) {
      sprintf(outname, "%sB", xobjs.tempfile);
      unlink(outname);
   }

   /* Write LATEX strings, if any are present */
   TopDoLatex();
}

/*----------------------------------------------------------------------*/
/* Given a color index, print the R, G, B values			*/
/*----------------------------------------------------------------------*/

int printRGBvalues(char *tstr, int index, const char *postfix)
{
   int i;
   for (i = 0; i < number_colors; i++) {
      if (colorlist[i].color.pixel == index) {
	 sprintf(tstr, "%4.3f %4.3f %4.3f %s",
		   (float)colorlist[i].color.red   / 65535,
		   (float)colorlist[i].color.green / 65535,
		   (float)colorlist[i].color.blue  / 65535,
		   postfix);
	 return 0;
      }
   }

   /* The program can reach this point for any color which is	*/
   /* not listed in the table.  This should not happen.		*/

   return -1;
}

/*----------------------------------------------------*/
/* Write string to PostScript string, ignoring NO_OPs */
/*----------------------------------------------------*/

char *nosprint(char *sptr)
{
   int qtmp, slen = 100;
   u_char *pptr, *qptr, *bptr;

   bptr = (u_char *)malloc(slen);	/* initial length 100 */
   qptr = bptr;

   *qptr++ = '(';

   /* Includes extended character set (non-ASCII) */

   for (pptr = sptr; pptr && *pptr != '\0'; pptr++) {
      /* Ensure enough space for the string, including everything following the "for" loop */
      qtmp = qptr - bptr;
      if (qtmp + 7 >= slen) {
	 slen += 7;
	 bptr = (char *)realloc(bptr, slen);
	 qptr = bptr + qtmp;
      }

      /* Deal with non-printable characters and parentheses */
      if (*pptr > (char)126) {
	 sprintf(qptr, "\\%3o", (int)(*pptr));
	 qptr += 4; 
      }
      else {
         if ((*pptr == '(') || (*pptr == ')') || (*pptr == '\\'))
	    *qptr++ = '\\';
         *qptr++ = *pptr;
      }
   }
   if (qptr == bptr + 1) {	/* Empty string gets a NULL result, not "()" */
      qptr--;
   }
   else {
      *qptr++ = ')';
      *qptr++ = ' ';
   }
   *qptr++ = '\0';

   return (char *)bptr;
}

/*--------------------------------------------------------------*/
/* Write label segments to the output (in reverse order)	*/
/*--------------------------------------------------------------*/

short writelabel(FILE *ps, stringpart *chrtop, short *stcount)
{
   short i, segs = 0;
   stringpart *chrptr;
   char **ostr = (char **)malloc(sizeof(char *));
   char *tmpstr;
   float lastscale = 1.0;
   int lastfont = -1;

   /* Write segments into string array, in forward order */

   for (chrptr = chrtop; chrptr != NULL; chrptr = chrptr->nextpart) {
      ostr = (char **)realloc(ostr, (segs + 1) * sizeof(char *));
      if (chrtop->type == PARAM_END) {	/* NULL parameter is empty string */
	 ostr[segs] = (char *)malloc(4);
	 strcpy(ostr[segs], "() ");
      }
      else {
	 tmpstr = writesegment(chrptr, &lastscale, &lastfont);
	 if (tmpstr[0] != '\0')
            ostr[segs] = tmpstr;
	 else
	    segs--;
      }
      segs++;
   }

   /* Write string array to output in reverse order */
   for (i = segs - 1; i >= 0; i--) {
      dostcount(ps, stcount, strlen(ostr[i]));
      fputs(ostr[i], ps);
      free(ostr[i]);
   }
   free(ostr);	 

   return segs;
}

/*--------------------------------------------------------------*/
/* Write a single label segment to the output			*/
/* (Recursive, so we can write segments in the reverse order)	*/
/*--------------------------------------------------------------*/

char *writesegment(stringpart *chrptr, float *lastscale, int *lastfont)
{
   int type = chrptr->type;
   char *retstr;

   switch(type) {
      case PARAM_START:
	 sprintf(_STR, "%s ", chrptr->data.string);
	 break;
      case PARAM_END:
	 _STR[0] = '\0';
	 chrptr->nextpart = NULL;
	 break;
      case SUBSCRIPT:
	 sprintf(_STR, "{ss} ");
	 break;
      case SUPERSCRIPT:
	 sprintf(_STR, "{Ss} ");
	 break;
      case NORMALSCRIPT:
	 *lastscale = 1.0;
         sprintf(_STR, "{ns} ");
         break;
      case UNDERLINE:
         sprintf(_STR, "{ul} ");
         break;
      case OVERLINE:
         sprintf(_STR, "{ol} ");
         break;
      case NOLINE:
         sprintf(_STR, "{} ");
         break;
      case HALFSPACE:
         sprintf(_STR, "{hS} ");
         break;
      case QTRSPACE:
	 sprintf(_STR, "{qS} ");
	 break;
      case RETURN:
	 *lastscale = 1.0;
	 sprintf(_STR, "{CR} ");
	 break;
      case TABSTOP:
	 sprintf(_STR, "{Ts} ");
	 break;
      case TABFORWARD:
	 sprintf(_STR, "{Tf} ");
	 break;
      case TABBACKWARD:
	 sprintf(_STR, "{Tb} ");
	 break;
      case FONT_NAME:
	 if (*lastscale == 1.0)
	    sprintf(_STR, "{/%s cf} ", fonts[chrptr->data.font].psname);
	 else
	    sprintf(_STR, "{/%s %5.3f cf} ", fonts[chrptr->data.font].psname,
		*lastscale);
	 *lastfont = chrptr->data.font;
	 break;
      case FONT_SCALE:
	 if (*lastfont == -1) {
	    Fprintf(stderr, "Warning:  Font may not be the one that was intended.\n");
	    *lastfont = 0;
	 }
	 *lastscale = chrptr->data.scale;
	 sprintf(_STR, "{/%s %5.3f cf} ", fonts[*lastfont].psname, *lastscale);
	 break;
      case FONT_COLOR:
	 strcpy(_STR, "{");
	 if (chrptr->data.color == DEFAULTCOLOR)
	    strcat(_STR, "sce} ");
	 else
	    if (printRGBvalues(_STR + 1,
		    colorlist[chrptr->data.color].color.pixel, "scb} ") < 0)
	       strcat(_STR, "sce} ");
	 break;
      case KERN:
	 sprintf(_STR, "{%d %d Kn} ", chrptr->data.kern[0], chrptr->data.kern[1]);
	 break;
      case TEXT_STRING:
	 /* Everything except TEXT_STRING will always fit in the _STR fixed-length character array. */
	 return nosprint(chrptr->data.string);
   }

   retstr = (char *)malloc(1 + strlen(_STR));
   strcpy(retstr, _STR);
   return retstr;
}

/*--------------------------------------------------------------*/
/* Routine to write all the label segments as stored in _STR	*/
/*--------------------------------------------------------------*/

void writelabelsegs(FILE *ps, short *stcount, stringpart *chrptr)
{
   Boolean ismultipart = ((chrptr->nextpart != NULL) &&
	   (chrptr->nextpart->type != PARAM_END)) ? True : False;

   /* If there is only one part, but it is not a string, then set */
   /* multipart anyway so we get the double brace {{ }}.	  */

   if ((!ismultipart) && (chrptr->type != TEXT_STRING))
      ismultipart = True;

   /* nextpart is not NULL if there are multiple parts to the string */
   if (ismultipart) {
      fprintf(ps, "{");
      (*stcount)++;
   }
   writelabel(ps, chrptr, stcount);

   if (ismultipart) {
      fprintf(ps, "} ");
      (*stcount) += 2;
   }
}

/*--------------------------------------------------------------*/
/* Write the dictionary of parameters belonging to an object	*/
/*--------------------------------------------------------------*/

void printobjectparams(FILE *ps, objectptr localdata)
{
   short stcount;
   oparamptr ops;
   char *ps_expr;

   /* Check for parameters and default values */
   if (localdata->params == NULL) return;

   fprintf(ps, "<<");
   stcount = 2;

   for (ops = localdata->params; ops != NULL; ops = ops->next) {
      fprintf(ps, "/%s ", ops->key);
      dostcount (ps, &stcount, strlen(ops->key) + 2);

      switch (ops->type) {
	 case XC_EXPR:
	    ps_expr = evaluate_expr(ops);
	    dostcount(ps, &stcount, 3 + strlen(ps_expr));
	    fputs("(", ps);
	    fputs(ps_expr, ps);
	    fputs(") ", ps);
	    dostcount(ps, &stcount, 7 + strlen(ops->parameter.expr));
	    fputs("(", ps);
	    fputs(ops->parameter.expr, ps);
	    fputs(") pop ", ps);
	    free(ps_expr);
	    break;
	 case XC_STRING:
	    writelabelsegs(ps, &stcount, ops->parameter.string);
	    break;
	 case XC_INT:
	    sprintf(_STR, "%d ", ops->parameter.ivalue);
	    dostcount(ps, &stcount, strlen(_STR));
	    fputs(_STR, ps);
	    break;
	 case XC_FLOAT:
	    sprintf(_STR, "%g ", ops->parameter.fvalue);
	    dostcount(ps, &stcount, strlen(_STR));
	    fputs(_STR, ps);
	    break;
      }
   }

   fprintf(ps, ">> ");
   dostcount (ps, &stcount, 3);
}

/*--------------------------------------------------------------*/
/* Write the list of parameters belonging to an object instance */
/*--------------------------------------------------------------*/

short printparams(FILE *ps, objinstptr sinst, short stcount)
{
   short i, iend, loccount;
   oparamptr ops, objops;
   eparamptr epp;
   char *ps_expr;

   if (sinst->params == NULL) return stcount;

   fprintf(ps, "<<");		/* begin PostScript dictionary */
   loccount = stcount + 2;

   for (ops = sinst->params; ops != NULL; ops = ops->next) {
      fprintf(ps, "/%s ", ops->key);
      dostcount(ps, &loccount, strlen(ops->key) + 2);

      /* Check for indirect parameter references */
      for (epp = sinst->passed; epp != NULL; epp = epp->next) {
	 if (epp->pdata.refkey != NULL) {
	    if (!strcmp(epp->pdata.refkey, ops->key)) {
	       sprintf(_STR, "%s ", epp->pdata.refkey);
	       dostcount(ps, &loccount, strlen(_STR));
	       fputs(_STR, ps);
	       break;
	    }
	 }
      }
      if (epp == NULL) {	/* No indirection */
         switch (ops->type) {
	    case XC_STRING:
	       writelabelsegs(ps, &loccount, ops->parameter.string);
	       break;
	    case XC_EXPR:
	       ps_expr = evaluate_expr(ops);
	       dostcount(ps, &loccount, 3 + strlen(ps_expr));
	       fputs("(", ps);
	       fputs(ps_expr, ps);
	       fputs(") ", ps);
	       dostcount(ps, &loccount, 3 + strlen(ops->parameter.expr));
	       fputs("(", ps);
	       fputs(ops->parameter.expr, ps);
	       fputs(") pop ", ps);
	       free(ps_expr);
	       break;
	    case XC_INT:
	       sprintf(_STR, "%d ", ops->parameter.ivalue);
	       dostcount(ps, &loccount, strlen(_STR));
	       fputs(_STR, ps);
	       break;
	    case XC_FLOAT:
	       sprintf(_STR, "%g ", ops->parameter.fvalue);
	       dostcount(ps, &loccount, strlen(_STR));
	       fputs(_STR, ps);
	       break;
	 }
      }
   }
   fprintf(ps, ">> ");		/* end PostScript dictionary */
   return loccount + 3;
}

/*------------------------------------------------------------------*/
/* Macro for point output (calls varpcheck() on x and y components) */
/*------------------------------------------------------------------*/

#define xyvarcheck(z, n, t) \
    varpcheck(ps, z.x, localdata, n, &stcount, *t, P_POSITION_X); \
    varpcheck(ps, z.y, localdata, n, &stcount, *t, P_POSITION_Y)
  
/*--------------------------------------------------------------*/
/* Main routine for writing the contents of a single object to	*/
/* output file "ps".						*/
/*--------------------------------------------------------------*/

void printOneObject(FILE *ps, objectptr localdata, int ccolor)
{
   int curcolor = ccolor;
   genericptr *savegen, *pgen;
   objinstptr sobj;
   pointlist savept;
   short stcount;
   short segs;
   Boolean has_param;

   /* first, get a total count of all objects and give warning if large */

   if (localdata->parts > 255) {
      sprintf(_STR, "Warning: \"%s\" may exceed printer's PS limit for definitions",
	    localdata->name);
      Wprintf(_STR);
   }
	   
   for (savegen = localdata->plist; savegen < localdata->plist +
		localdata->parts; savegen++) {

      /* change current color if different */

      if ((*savegen)->color != curcolor) {
	 if ((curcolor = (*savegen)->color) == DEFAULTCOLOR)
	    fprintf(ps, "sce\n");
	 else {
	    if (printRGBvalues(_STR, (*savegen)->color, "scb\n") < 0) {
	       fprintf(ps, "sce\n");
	       curcolor = DEFAULTCOLOR;
	    }
	    else
	       fputs(_STR, ps); 
	 }
      }

      stcount = 0;
      switch(ELEMENTTYPE(*savegen)) {

	 case(POLYGON):
	    varcheck(ps, TOPOLY(savegen)->style, localdata, &stcount,
			*savegen, P_STYLE);
	    varfcheck(ps, TOPOLY(savegen)->width, localdata, &stcount,
			*savegen, P_LINEWIDTH);
            for (savept = TOPOLY(savegen)->points; savept < TOPOLY(savegen)->
		      points + TOPOLY(savegen)->number; savept++) {
	       varpcheck(ps, savept->x, localdata,
			savept - TOPOLY(savegen)->points, &stcount, *savegen,
			P_POSITION_X);
	       varpcheck(ps, savept->y, localdata,
			savept - TOPOLY(savegen)->points, &stcount, *savegen,
			P_POSITION_Y);
	    }
	    sprintf(_STR, "%hd polygon\n", TOPOLY(savegen)->number);
	    dostcount (ps, &stcount, strlen(_STR));
	    fputs(_STR, ps);
	    break;

	 case(PATH):
	    pgen = TOPATH(savegen)->plist;
	    switch(ELEMENTTYPE(*pgen)) {
	       case ARC:
		  varpcheck(ps, (short)TOARC(pgen)->points[0].x, localdata,
				0, &stcount, *pgen, P_POSITION_X);
		  varpcheck(ps, (short)TOARC(pgen)->points[0].y, localdata,
				0, &stcount, *pgen, P_POSITION_Y);
		  break;
	       case POLYGON:
		  xyvarcheck(TOPOLY(pgen)->points[0], 0, pgen);
		  break;
	       case SPLINE:
		  varpcheck(ps, (short)TOSPLINE(pgen)->ctrl[0].x, localdata,
				0, &stcount, *pgen, P_POSITION_X);
		  varpcheck(ps, (short)TOSPLINE(pgen)->ctrl[0].y, localdata,
				0, &stcount, *pgen, P_POSITION_Y);
		  break;
	    }
	    dostcount(ps, &stcount, 9);
	    fprintf(ps, "beginpath\n");
	    for (pgen = TOPATH(savegen)->plist; pgen < TOPATH(savegen)->plist
			+ TOPATH(savegen)->parts; pgen++) {
	       switch(ELEMENTTYPE(*pgen)) {
		  case ARC:
		     if (abs(TOARC(pgen)->radius) == TOARC(pgen)->yaxis) {
			xyvarcheck(TOARC(pgen)->position, 0, pgen);
	       		varcheck(ps, abs(TOARC(pgen)->radius), localdata,
				&stcount, *pgen, P_RADIUS);
			if (TOARC(pgen)->radius < 0) {
	       		   varfcheck(ps, TOARC(pgen)->angle2, localdata,
				&stcount, *pgen, P_ANGLE2);
	       		   varfcheck(ps, TOARC(pgen)->angle1, localdata,
				&stcount, *pgen, P_ANGLE1);
			   fprintf(ps, " arcn\n");
			}
			else {
	       		   varfcheck(ps, TOARC(pgen)->angle1, localdata,
				&stcount, *pgen, P_ANGLE1);
	       		   varfcheck(ps, TOARC(pgen)->angle2, localdata,
				&stcount, *pgen, P_ANGLE2);
			   fprintf(ps, " arc\n");
			}
		     }
		     else {
			xyvarcheck(TOARC(pgen)->position, 0, pgen);
	       		varcheck(ps, abs(TOARC(pgen)->radius), localdata,
				&stcount, *pgen, P_RADIUS);
	       		varcheck(ps, TOARC(pgen)->yaxis, localdata,
				&stcount, *pgen, P_MINOR_AXIS);

			if (TOARC(pgen)->radius < 0) {
	       		   varfcheck(ps, TOARC(pgen)->angle2, localdata,
				&stcount, *pgen, P_ANGLE2);
	       		   varfcheck(ps, TOARC(pgen)->angle1, localdata,
				&stcount, *pgen, P_ANGLE1);
			   fprintf(ps, " nellip\n");
			}
			else {
	       		   varfcheck(ps, TOARC(pgen)->angle1, localdata,
				&stcount, *pgen, P_ANGLE1);
	       		   varfcheck(ps, TOARC(pgen)->angle2, localdata,
				&stcount, *pgen, P_ANGLE2);
			   fprintf(ps, " pellip\n");
			}
		     }
		     break;
		  case POLYGON:
               	     for (savept = TOPOLY(pgen)->points + TOPOLY(pgen)->number
				- 1; savept > TOPOLY(pgen)->points; savept--) {
    			varpcheck(ps, savept->x, localdata,
				savept - TOPOLY(pgen)->points, &stcount, *pgen,
				P_POSITION_X);
			varpcheck(ps, savept->y, localdata,
				savept - TOPOLY(pgen)->points, &stcount, *pgen,
				P_POSITION_Y);
	       	     }
	       	     sprintf(_STR, "%hd polyc\n", TOPOLY(pgen)->number - 1);
	       	     dostcount (ps, &stcount, strlen(_STR));
	       	     fputs(_STR, ps);
	       	     break;
		  case SPLINE:
		     xyvarcheck(TOSPLINE(pgen)->ctrl[1], 1, pgen);
		     xyvarcheck(TOSPLINE(pgen)->ctrl[2], 2, pgen);
		     xyvarcheck(TOSPLINE(pgen)->ctrl[3], 3, pgen);
		     fprintf(ps, "curveto\n");
		     break;
	       }
	    }
	    varcheck(ps, TOPATH(savegen)->style, localdata, &stcount,
			*savegen, P_STYLE);
	    varfcheck(ps, TOPATH(savegen)->width, localdata, &stcount,
			*savegen, P_LINEWIDTH);
	    fprintf(ps, "endpath\n");
	    break;

	 case(SPLINE):
	    varcheck(ps, TOSPLINE(savegen)->style, localdata, &stcount,
			*savegen, P_STYLE);
	    varfcheck(ps, TOSPLINE(savegen)->width, localdata, &stcount,
			*savegen, P_LINEWIDTH);
	    xyvarcheck(TOSPLINE(savegen)->ctrl[1], 1, savegen);
	    xyvarcheck(TOSPLINE(savegen)->ctrl[2], 2, savegen);
	    xyvarcheck(TOSPLINE(savegen)->ctrl[3], 3, savegen);
	    xyvarcheck(TOSPLINE(savegen)->ctrl[0], 0, savegen);
	    fprintf(ps, "spline\n");
	    break;

	 case(ARC):
	    varcheck(ps, TOARC(savegen)->style, localdata, &stcount,
			*savegen, P_STYLE);
	    varfcheck(ps, TOARC(savegen)->width, localdata, &stcount,
			*savegen, P_LINEWIDTH);
	    xyvarcheck(TOARC(savegen)->position, 0, savegen);
	    varcheck(ps, abs(TOARC(savegen)->radius), localdata, &stcount,
			*savegen, P_RADIUS);
	    if (abs(TOARC(savegen)->radius) == TOARC(savegen)->yaxis) {
	       varfcheck(ps, TOARC(savegen)->angle1, localdata, &stcount,
			*savegen, P_ANGLE1);
	       varfcheck(ps, TOARC(savegen)->angle2, localdata, &stcount,
			*savegen, P_ANGLE2);
	       fprintf(ps, "xcarc\n");
	    }
	    else {
	       varcheck(ps, abs(TOARC(savegen)->yaxis), localdata, &stcount,
			*savegen, P_MINOR_AXIS);
	       varfcheck(ps, TOARC(savegen)->angle1, localdata, &stcount,
			*savegen, P_ANGLE1);
	       varfcheck(ps, TOARC(savegen)->angle2, localdata, &stcount,
			*savegen, P_ANGLE2);
	       fprintf(ps, "ellipse\n");
	    }
	    break;

	 case(OBJINST):
	    sobj = TOOBJINST(savegen);
	    varfcheck(ps, sobj->scale, localdata, &stcount, *savegen, P_SCALE);
	    varcheck(ps, sobj->rotation, localdata, &stcount, *savegen, P_ROTATION);
	    xyvarcheck(sobj->position, 0, savegen);

	    stcount = printparams(ps, sobj, stcount);

	    fprintf(ps, "%s\n", TOOBJINST(savegen)->thisobject->name);
	    break;
            
	 case(LABEL):

	    /* strip schematic pins and labels if schematic capture is off */
	    if (TOLABEL(savegen)->pin && !areastruct.schemon) break;

	    /* Don't save temporary labels from schematic capture system */
	    else if (TOLABEL(savegen)->string->type != FONT_NAME) break;

	    /* Check for parameter --- must use "mark" to count # segments */
	    has_param = hasparameter(TOLABEL(savegen));

	    if (has_param) {
	       fprintf(ps, "mark ");
	       stcount += 5;
	    }

	    segs = writelabel(ps, TOLABEL(savegen)->string, &stcount);

	    if (segs > 0) {
	       if (has_param)
                  sprintf(_STR, "ctmk ");
	       else
                  sprintf(_STR, "%hd ", segs);
	       dostcount(ps, &stcount, strlen(_STR));
	       fputs(_STR, ps);
	       varcheck(ps, TOLABEL(savegen)->justify, localdata, &stcount,
			*savegen, P_JUSTIFY);
	       varcheck(ps, TOLABEL(savegen)->rotation, localdata, &stcount,
			*savegen, P_ROTATION);
	       varfcheck(ps, TOLABEL(savegen)->scale, localdata, &stcount,
			*savegen, P_SCALE);
	       xyvarcheck(TOLABEL(savegen)->position, 0, savegen);

	       if (areastruct.schemon) {
		  switch(TOLABEL(savegen)->pin) {
		     case LOCAL:
			strcpy(_STR, "pinlabel\n"); break;
		     case GLOBAL:
			strcpy(_STR, "pinglobal\n"); break;
		     case INFO:
			strcpy(_STR, "infolabel\n"); break;
		     default:
		     	strcpy(_STR, "label\n");
		  }
	       }
	       else strcpy(_STR, "label\n");
	       dostcount(ps, &stcount, strlen(_STR));
	       fputs(_STR, ps);
	    }
	    break;
      }
   }
}

/*----------------------------------------------------------------------*/
/* Recursive routine to print out the library objects used in this	*/
/* drawing, starting at the bottom of the object hierarchy so that each	*/
/* object is defined before it is called.  A list of objects already	*/
/* written is maintained so that no object is written twice. 		*/
/*									*/
/* When object "localdata" is not a top-level page, call this routine	*/
/* with mpage=-1 (simpler than checking whether localdata is a page).	*/
/*----------------------------------------------------------------------*/

void printobjects(FILE *ps, objectptr localdata, objectptr **wrotelist,
	short *written, int ccolor)
{
   genericptr *gptr;
   objectptr *optr;
   oparamptr ops;
   int curcolor = ccolor;

   /* Search among the list of objects already written to the output	*/
   /* If this object has been written previously, then we ignore it.	*/

   for (optr = *wrotelist; optr < *wrotelist + *written; optr++)
      if (*optr == localdata)
	  return;

   /* If this page is a schematic, write out the definiton of any symbol */
   /* attached to it, because that symbol may not be used anywhere else. */

   if (localdata->symschem && (localdata->schemtype == PRIMARY))
      printobjects(ps, localdata->symschem, wrotelist, written, curcolor);

   /* Search for all object definitions instantiated in this object,	*/
   /* and (recursively) print them to the output.			*/

   for (gptr = localdata->plist; gptr < localdata->plist + localdata->parts; gptr++)
      if (IS_OBJINST(*gptr))
         printobjects(ps, TOOBJINST(gptr)->thisobject, wrotelist, written, curcolor);

   /* Update the list of objects already written to the output */

   *wrotelist = (objectptr *)realloc(*wrotelist, (*written + 1) * 
		sizeof(objectptr));
   *(*wrotelist + *written) = localdata;
   (*written)++;

   fprintf(ps, "/%s {\n", localdata->name);
   /* No longer writes "bbox" record */
   if (localdata->hidden == True) fprintf(ps, "%% hidden\n");

   /* For symbols with schematics, and "trivial" schematics */
   if (areastruct.schemon) {
      if (localdata->symschem != NULL)
	 fprintf(ps, "%% %s is_schematic\n", localdata->symschem->name);
      else if (localdata->schemtype == TRIVIAL)
	 fprintf(ps, "%% trivial\n");
   }

   printobjectparams(ps, localdata);
   fprintf(ps, "begingate\n");

   /* Write all the elements in order */

   printOneObject(ps, localdata, curcolor);

   /* Write object (gate) trailer */

   fprintf(ps, "endgate\n} def\n\n");
}

/*--------------------------------------------------------------*/
/* Print a page header followed by everything in the page.	*/
/* this routine assumes that all objects used by the page have	*/
/* already been handled and written to the output.		*/
/*								*/
/* "page" is the page number, counting consecutively from one.	*/
/* "mpage" is the page number in xcircuit's pagelist structure.	*/
/*--------------------------------------------------------------*/

void printpageobject(FILE *ps, objectptr localdata, short page, short mpage)
{
   genericptr *gptr;
   XPoint origin, corner;
   objinstptr writepage;
   int width, height;
   float psnorm, psscale;
   float xmargin, ymargin;
   char *rootptr;
   polyptr framebox;

   /* Output page header information */

   rootptr = strrchr(xobjs.pagelist[mpage]->filename, '/');
   if (rootptr == NULL)
      rootptr = xobjs.pagelist[mpage]->filename;
   else rootptr++;

   writepage = xobjs.pagelist[mpage]->pageinst;

   psnorm = xobjs.pagelist[mpage]->outscale;
   psscale = getpsscale(psnorm, mpage);

   /* Determine the margins (offset of drawing from page corner)	*/
   /* If a bounding box has been declared in the drawing, it is		*/
   /* centered on the page.  Otherwise, the drawing itself is		*/
   /* centered on the page.  If encapsulated, the bounding box		*/
   /* encompasses only the object itself.				*/

   width = toplevelwidth(writepage, &origin.x);
   height = toplevelheight(writepage, &origin.y);

   corner.x = origin.x + width;
   corner.y = origin.y + height;

   if (xobjs.pagelist[mpage]->pmode & 1) {	/* full page */

      if (xobjs.pagelist[mpage]->orient == 90) {
	 xmargin = (xobjs.pagelist[mpage]->pagesize.x -
		((float)height * psscale)) / 2;
	 ymargin = (xobjs.pagelist[mpage]->pagesize.y -
		((float)width * psscale)) / 2;
      }
      else {
         xmargin = (xobjs.pagelist[mpage]->pagesize.x -
		((float)width * psscale)) / 2;
         ymargin = (xobjs.pagelist[mpage]->pagesize.y -
		((float)height * psscale)) / 2;
      }

      /* If a framebox is declared, then we adjust the page to be	*/
      /* centered on the framebox by translating through the		*/
      /* difference between the object center and the framebox	*/
      /* center.							*/

      if ((framebox = checkforbbox(localdata)) != NULL) {
 	 int i, fcentx = 0, fcenty = 0;

	 for (i = 0; i < framebox->number; i++) {
	    fcentx += framebox->points[i].x;
	    fcenty += framebox->points[i].y;
	 }
	 fcentx /= framebox->number;
	 fcenty /= framebox->number;

	 xmargin += psscale * (float)(origin.x + (width >> 1) - fcentx);
	 ymargin += psscale * (float)(origin.y + (height >> 1) - fcenty);
      }

   }
   else {	/* encapsulated --- should have 1" border so that any */
		/* drawing passed directly to a printer will not clip */
      xmargin = ymargin = 72;
   }

   /* If the page label is just the root name of the file, or has been left	*/
   /* as "Page n" or "Page_n", just do the normal page numbering.  Otherwise,	*/
   /* write out the page label explicitly.					*/

   if ((!strcmp(rootptr, localdata->name)) || (strchr(localdata->name, ' ')
		!= NULL) || (strstr(localdata->name, "Page_") != NULL))
      fprintf (ps, "%%%%Page: %d %d\n", page, page);
   else
      fprintf (ps, "%%%%Page: %s %d\n", localdata->name, page);

   if (xobjs.pagelist[mpage]->orient == 90)
      fprintf (ps, "%%%%PageOrientation: Landscape\n");
   else
      fprintf (ps, "%%%%PageOrientation: Portrait\n");

   if (xobjs.pagelist[mpage]->pmode & 1) 	/* full page */
      fprintf(ps, "%%%%PageBoundingBox: 0 0 %d %d\n",
		xobjs.pagelist[mpage]->pagesize.x,
		xobjs.pagelist[mpage]->pagesize.y);

   /* Encapsulated files do not get a PageBoundingBox line */
   /* fprintf(ps, "%%%%PageBoundingBox: 0 0 %d %d\n",
		(int)((float)width * psscale) + 114,
		(int)((float)height * psscale) + 114); */
     
   fprintf (ps, "/pgsave save def bop\n");

   /* Top-page definitions */
   if (localdata->params != NULL) {
      printobjectparams(ps, localdata);
      fprintf(ps, "begin\n");
   }

   if (localdata->symschem != NULL) {
      fprintf(ps, "%% %s is_symbol\n", localdata->symschem->name);
   }

   /* Extend bounding box around schematic pins */
   extendschembbox(xobjs.pagelist[mpage]->pageinst, &origin, &corner);

   if (xobjs.pagelist[mpage]->drawingscale.x != 1
		|| xobjs.pagelist[mpage]->drawingscale.y != 1)
      fprintf(ps, "%% %hd:%hd drawingscale\n", xobjs.pagelist[mpage]->drawingscale.x,
	 	xobjs.pagelist[mpage]->drawingscale.y);

   if (xobjs.pagelist[mpage]->gridspace != 32
		|| xobjs.pagelist[mpage]->snapspace != 16)
      fprintf(ps, "%% %4.2f %4.2f gridspace\n", xobjs.pagelist[mpage]->gridspace,
		xobjs.pagelist[mpage]->snapspace);

   if (xobjs.pagelist[mpage]->background.name != (char *)NULL) {
      float iscale = (xobjs.pagelist[mpage]->coordstyle == CM) ? CMSCALE : INCHSCALE;
      if (xobjs.pagelist[mpage]->orient == 90)
	 fprintf(ps, "%5.4f %d %d 90 psinsertion\n", psnorm,
		  (int)(ymargin - xmargin),
		  -((int)((float)(corner.y - origin.y) * psscale) +
		  (int)(xmargin + ymargin)));
      else
	 fprintf(ps, "%5.4f %d %d 0 psinsertion\n", psnorm,
		(int)(xmargin / psscale) - origin.x,
		(int)(ymargin / psscale) - origin.y);
      savebackground(ps, xobjs.pagelist[mpage]->background.name);
      fprintf(ps, "\nend_insert\n");
   }

   if (xobjs.pagelist[mpage]->orient == 90)
      fprintf(ps, "90 rotate %d %d translate\n", (int)(ymargin - xmargin),
	     -((int)((float)(corner.y - origin.y) * psscale) + 
	     (int)(xmargin + ymargin)));

   fprintf(ps, "%5.4f ", psnorm);
   switch(xobjs.pagelist[mpage]->coordstyle) {
      case FRAC_INCH: case DEC_INCH:
	 fprintf(ps, "inchscale\n");
	 break;
      case CM:
	 fprintf(ps, "cmscale\n");
	 break;
   }

   /* Final scale and translation */
   fprintf(ps, "%5.4f setlinewidth %d %d translate\n\n",
		1.3 * xobjs.pagelist[mpage]->wirewidth,
		(int)(xmargin / psscale) - origin.x,
		(int)(ymargin / psscale) - origin.y);

   /* Output all the elements in the page */
   printOneObject(ps, localdata, DEFAULTCOLOR);

   /* Page trailer */
   if (localdata->params != NULL) fprintf(ps, "end ");
   fprintf(ps, "pgsave restore showpage\n");
}

/*--------------------------------------------------------------*/
/* Print objects referenced from a particular page.  These get	*/
/* bundled together at the beginning of the output file under	*/
/* the DSC "DocumentSetup" section, so that the PostScript	*/
/* interpreter knows that these definitions may be used by any	*/
/* page.  This prevents ghostscript from producing an error	*/
/* when backing up in a multi-page document.			*/
/*--------------------------------------------------------------*/

void printrefobjects(FILE *ps, objectptr localdata, objectptr **wrotelist,
	short *written)
{
   genericptr *gptr;

   /* If this page is a schematic, write out the definiton of any symbol */
   /* attached to it, because that symbol may not be used anywhere else. */

   if (localdata->symschem && (localdata->schemtype == PRIMARY))
      printobjects(ps, localdata->symschem, wrotelist, written, DEFAULTCOLOR);

   /* Search for all object definitions instantiated on the page and	*/
   /* write them to the output.						*/

   for (gptr = localdata->plist; gptr < localdata->plist + localdata->parts; gptr++)
      if (IS_OBJINST(*gptr))
         printobjects(ps, TOOBJINST(gptr)->thisobject, wrotelist, written,
		DEFAULTCOLOR);
}

/*----------------------------------------------------------------------*/
