/*-------------------------------------------------------------------------*/
/* functions.c 								   */
/* Copyright (c) 2002  Tim Edwards, Johns Hopkins University        	   */
/*-------------------------------------------------------------------------*/

/*-------------------------------------------------------------------------*/
/*      written by Tim Edwards, 8/13/93    				   */
/*-------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

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

#ifdef TCL_WRAPPER 
#include <tk.h>
#endif

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

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

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

extern Display *dpy;
extern Pixmap STIPPLE[8];
extern short eventmode;
extern Clientdata areastruct;
extern Globaldata xobjs;
extern int *appcolors;
extern short textpos, textend;

/*------------------------------------------------------------------------*/
/* find the squared length of a wire (or distance between two points in   */
/* user space).								  */
/*------------------------------------------------------------------------*/

long sqwirelen(XPoint *userpt1, XPoint *userpt2)
{
  long xdist, ydist;

  xdist = (long)userpt2->x - (long)userpt1->x;
  ydist = (long)userpt2->y - (long)userpt1->y;
  return (xdist * xdist + ydist * ydist);
}

/*------------------------------------------------------------------------*/
/* floating-point version of the above					  */
/*------------------------------------------------------------------------*/

float fsqwirelen(XfPoint *userpt1, XfPoint *userpt2)
{
  float xdist, ydist;

  xdist = userpt2->x - userpt1->x;
  ydist = userpt2->y - userpt1->y;
  return (xdist * xdist + ydist * ydist);
}

/*------------------------------------------------------------------------*/
/* Find absolute distance between two points in user space		  */
/*------------------------------------------------------------------------*/

int wirelength(XPoint *userpt1, XPoint *userpt2)
{
  u_long xdist, ydist;

  xdist = (long)(userpt2->x) - (long)(userpt1->x);
  ydist = (long)(userpt2->y) - (long)(userpt1->y);
  return (int)sqrt((double)(xdist * xdist + ydist * ydist));
}

/*------------------------------------------------------------------------*/
/* Find the closest (squared) distance from a point to a line		  */
/*------------------------------------------------------------------------*/

long finddist(XPoint *linept1, XPoint *linept2, XPoint *userpt)
{
   long a, b, c, frac;
   float protod;

   c = sqwirelen(linept1, linept2);
   a = sqwirelen(linept1, userpt);
   b = sqwirelen(linept2, userpt);
   frac = a - b;
   if (frac >= c) return b;	  /* "=" is important if c = 0 ! */
   else if (-frac >= c) return a;
   else {
      protod = (float)(c + a - b);
      return (a - (long)((protod * protod) / (float)(c << 2)));
   }
}

/*------------------------------------------------------------------------*/
/* Calculate points for an arc						  */
/*------------------------------------------------------------------------*/

void calcarc(arcptr thearc)
{
   short idx;
   int sarc;
   float theta, delta;

   /* assume that angle2 > angle1 always: must be guaranteed by other routines */

   sarc = (int)(thearc->angle2 - thearc->angle1) * RSTEPS;
   thearc->number = (sarc / 360) + 1;
   if (sarc % 360 != 0) thearc->number++;
	   
   delta = RADFAC * ((float)(thearc->angle2 - thearc->angle1) / (thearc->number - 1));
   theta = thearc->angle1 * RADFAC;

   for (idx = 0; idx < thearc->number - 1; idx++) {
      thearc->points[idx].x = (float)thearc->position.x + 
	   fabs((float)thearc->radius) * cos(theta);
      thearc->points[idx].y = (float)thearc->position.y +
	   (float)thearc->yaxis * sin(theta);
      theta += delta;
   }

   /* place last point exactly to avoid roundoff error */

   theta = thearc->angle2 * RADFAC;
   thearc->points[thearc->number - 1].x = (float)thearc->position.x + 
	   fabs((float)thearc->radius) * cos(theta);
   thearc->points[thearc->number - 1].y = (float)thearc->position.y +
	   (float)thearc->yaxis * sin(theta);

   if (thearc->radius < 0) reversefpoints(thearc->points, thearc->number);
}

/*------------------------------------------------------------------------*/
/* Create a Bezier curve approximation from control points		  */
/* (using PostScript formula for Bezier cubic curve)			  */
/*------------------------------------------------------------------------*/

float par[INTSEGS];
float parsq[INTSEGS];
float parcb[INTSEGS];

void initsplines()
{
   float t;
   short idx;

   for (idx = 0; idx < INTSEGS; idx++) {
      t = (float)(idx + 1) / (INTSEGS + 1);
      par[idx] = t;
      parsq[idx] = t * t;
      parcb[idx] = parsq[idx] * t;
   }
}

/*------------------------------------------------------------------------*/
/* Compute spline coefficients						  */
/*------------------------------------------------------------------------*/

void computecoeffs(splineptr thespline, float *ax, float *bx, float *cx,
	float *ay, float *by, float *cy)
{
   *cx = 3.0 * (float)(thespline->ctrl[1].x - thespline->ctrl[0].x);
   *bx = 3.0 * (float)(thespline->ctrl[2].x - thespline->ctrl[1].x) - *cx;
   *ax = (float)(thespline->ctrl[3].x - thespline->ctrl[0].x) - *cx - *bx;

   *cy = 3.0 * (float)(thespline->ctrl[1].y - thespline->ctrl[0].y);
   *by = 3.0 * (float)(thespline->ctrl[2].y - thespline->ctrl[1].y) - *cy;
   *ay = (float)(thespline->ctrl[3].y - thespline->ctrl[0].y) - *cy - *by;   
}

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

void calcspline(splineptr thespline)
{
   float ax, bx, cx, ay, by, cy;
   short idx;

   computecoeffs(thespline, &ax, &bx, &cx, &ay, &by, &cy);
   for (idx = 0; idx < INTSEGS; idx++) {
      thespline->points[idx].x = ax * parcb[idx] + bx * parsq[idx] +
	 cx * par[idx] + (float)thespline->ctrl[0].x;
      thespline->points[idx].y = ay * parcb[idx] + by * parsq[idx] +
	 cy * par[idx] + (float)thespline->ctrl[0].y;
   }
}

/*------------------------------------------------------------------------*/
/* Find the (x,y) position and tangent rotation of a point on a spline    */
/*------------------------------------------------------------------------*/

void findsplinepos(splineptr thespline, float t, XPoint *retpoint, int *retrot)
{
   float ax, bx, cx, ay, by, cy;
   float tsq = t * t;
   float tcb = tsq * t;
   double dxdt, dydt;

   computecoeffs(thespline, &ax, &bx, &cx, &ay, &by, &cy);
   retpoint->x = (short)(ax * tcb + bx * tsq + cx * t + (float)thespline->ctrl[0].x);
   retpoint->y = (short)(ay * tcb + by * tsq + cy * t + (float)thespline->ctrl[0].y);

   if (retrot != NULL) {
      dxdt = (double)(3 * ax * tsq + 2 * bx * t + cx);
      dydt = (double)(3 * ay * tsq + 2 * by * t + cy);
      *retrot = (int)(INVRFAC * atan2(dxdt, dydt));  /* reversed y, x */
      if (*retrot < 0) *retrot += 360;
   }
}

/*------------------------------------------------------------------------*/
/* floating-point version of the above					  */
/*------------------------------------------------------------------------*/

void ffindsplinepos(splineptr thespline, float t, XfPoint *retpoint)
{
   float ax, bx, cx, ay, by, cy;
   float tsq = t * t;
   float tcb = tsq * t;

   computecoeffs(thespline, &ax, &bx, &cx, &ay, &by, &cy);
   retpoint->x = ax * tcb + bx * tsq + cx * t + (float)thespline->ctrl[0].x;
   retpoint->y = ay * tcb + by * tsq + cy * t + (float)thespline->ctrl[0].y;
}

/*------------------------------------------------------------------------*/
/* Find the closest distance between a point and a spline and return the  */
/* fractional distance along the spline of this point.			  */
/*------------------------------------------------------------------------*/

float findsplinemin(splineptr thespline, XPoint *upoint)
{
   XfPoint 	*spt, flpt, newspt;
   float	minval = 1000000, tval, hval, ndist;
   short	j, ival;

   flpt.x = (float)(upoint->x);
   flpt.y = (float)(upoint->y);

   /* get estimate from precalculated spline points */

   for (spt = thespline->points; spt < thespline->points + INTSEGS;
	spt++) {
      ndist = fsqwirelen(spt, &flpt);
      if (ndist < minval) {
	 minval = ndist;
	 ival = (short)(spt - thespline->points);
      }
   }
   tval = (float)(ival + 1) / (INTSEGS + 1);
   hval = 0.5 / (INTSEGS + 1);

   /* short fixed iterative loop to converge on minimum t */

   for (j = 0; j < 5; j++) {
      tval += hval;
      ffindsplinepos(thespline, tval, &newspt);
      ndist = fsqwirelen(&newspt, &flpt);
      if (ndist < minval) minval = ndist;
      else {
         tval -= hval * 2;
         ffindsplinepos(thespline, tval, &newspt);
         ndist = fsqwirelen(&newspt, &flpt);
         if (ndist < minval) minval = ndist;
	 else tval += hval;
      }
      hval /= 2;
   }

   if (tval < 0.1) {
      if ((float)sqwirelen(&(thespline->ctrl[0]), upoint) < minval) tval = 0;
   }
   else if (tval > 0.9) {
      if ((float)sqwirelen(&(thespline->ctrl[3]), upoint) < minval) tval = 1;
   }
   return tval;
}

/*----------------------------------------------------------------------------*/
/* Find closest point of a polygon to the cursor			      */
/*----------------------------------------------------------------------------*/

short closepoint(polyptr curpoly, XPoint *cursloc)
{
   short curdist, mindist;
   XPoint *curpt, *savept; 

   curpt = savept = curpoly->points;
   mindist = wirelength(curpt, cursloc);
   while (++curpt < curpoly->points + curpoly->number) {
      curdist = wirelength(curpt, cursloc);
      if (curdist < mindist) {
         mindist = curdist;
         savept = curpt;
      }
   }
   return (short)(savept - curpoly->points);
}

/*----------------------------------------------------------------------------*/
/* Coordinate system transformations 					      */
/*----------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------*/
/*  Check screen bounds:  minimum, maximum scale and translation is determined	*/
/*  by values which fit in an X11 type XPoint (short int).  If the window	*/
/*  extremes exceed type short when mapped to user space, or if the page 	*/
/*  bounds exceed type short when mapped to X11 window space, return error.	*/ 
/*------------------------------------------------------------------------------*/

short checkbounds()
{
   XPoint testpt;
   long lval;

   /* check window-to-user space */

   lval = 2 * (long)((float) (areastruct.width) / (*areastruct.vscale)) +
	(long)areastruct.pcorner->x;
   if (lval != (long)((short)lval)) return -1;
   lval = 2 * (long)((float) (areastruct.height) / (*areastruct.vscale)) +
	(long)areastruct.pcorner->y;
   if (lval != (long)((short)lval)) return -1;

   /* check user-to-window space */

   lval = (long)((float)(topobject->bbox.lowerleft.x - areastruct.pcorner->x) *
	(*areastruct.vscale));
   if (lval != (long)((short)lval)) return -1;
   lval = (long)areastruct.height - (long)((float)(topobject->bbox.lowerleft.y -
	areastruct.pcorner->y) * (*areastruct.vscale)); 
   if (lval != (long)((short)lval)) return -1;
   UTransformbyCTM(DCTM, &(topobject->bbox.lowerleft), &testpt, 1);

   lval = (long)((float)(topobject->bbox.lowerleft.x + topobject->bbox.width -
	areastruct.pcorner->x) * (*areastruct.vscale));
   if (lval != (long)((short)lval)) return -1;
   lval = (long)areastruct.height - (long)((float)(topobject->bbox.lowerleft.y +
	topobject->bbox.height - areastruct.pcorner->y) * (*areastruct.vscale)); 
   if (lval != (long)((short)lval)) return -1;

   return 0;
}

/*------------------------------------------------------------------------*/
/* Transform X-window coordinate to xcircuit coordinate system		  */
/*------------------------------------------------------------------------*/

void window_to_user(short xw, short yw, XPoint *upt)
{
  float tmpx, tmpy;

  tmpx = (float)xw / (*areastruct.vscale) + (float)areastruct.pcorner->x;
  tmpy = (float)(areastruct.height - yw) / (*areastruct.vscale) + 
	(float)areastruct.pcorner->y;

  tmpx += (tmpx > 0) ? 0.5 : -0.5;
  tmpy += (tmpy > 0) ? 0.5 : -0.5;

  upt->x = (short)tmpx;
  upt->y = (short)tmpy;
}

/*------------------------------------------------------------------------*/
/* Transform xcircuit coordinate back to X-window coordinate system       */
/*------------------------------------------------------------------------*/

void user_to_window(XPoint upt, XPoint *wpt)
{
  float tmpx, tmpy;

  tmpx = (float)(upt.x - areastruct.pcorner->x) * (*areastruct.vscale);
  tmpy = (float)areastruct.height - (float)(upt.y - areastruct.pcorner->y)
	* (*areastruct.vscale); 

  tmpx += (tmpx > 0) ? 0.5 : -0.5;
  tmpy += (tmpy > 0) ? 0.5 : -0.5;

  wpt->x = (short)tmpx;
  wpt->y = (short)tmpy;
}

/*------------------------------------------------------------------------*/
/* Transformations in the object hierarchy				  */
/*------------------------------------------------------------------------*/

float UTopScale()
{
   Matrix *ctm = DCTM;
   return (float)(sqrt((double)(ctm->a * ctm->a + ctm->d * ctm->d)));
}

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

short UTopTransScale(float length)
{
   /* 0.5 gives nearest int but unfortunately gives odd results. . .*/
  
   return (short)(length * UTopScale() + 0.45);
}

/*----------------------------------------------------------------------*/
/* Get the cursor position						*/
/*----------------------------------------------------------------------*/

XPoint UGetCursor()
{
   Window nullwin;
   int    nullint, xpos, ypos;
   u_int   nullui;
   XPoint newpos;
 
   XQueryPointer(dpy, areastruct.areawin, &nullwin, &nullwin, &nullint,
	&nullint, &xpos, &ypos, &nullui);

   newpos.x = xpos;
   newpos.y = ypos;

   return newpos;
}

/*----------------------------------------------------------------------*/
/* Get the cursor position and translate to user coordinates		*/
/*----------------------------------------------------------------------*/

XPoint UGetCursorPos()
{
   XPoint winpos, userpos;
 
   winpos = UGetCursor();

   window_to_user(winpos.x, winpos.y, &userpos);  

   return userpos;
}

/*----------------------------------------------------------------------*/
/* Translate a point to the nearest snap-to grid point			*/
/*----------------------------------------------------------------------*/
/* user coordinates to user coordinates version 			*/

void u2u_snap(XPoint *uvalue)
{
   float tmpx, tmpy;
   float tmpix, tmpiy;

   if (areastruct.snapto) {
      tmpx = (float)uvalue->x / xobjs.pagelist[areastruct.page]->snapspace;
      if (tmpx > 0)
	 tmpix = (float)((int)(tmpx + 0.5));
      else
         tmpix = (float)((int)(tmpx - 0.5));

      tmpy = (float)uvalue->y / xobjs.pagelist[areastruct.page]->snapspace;
      if (tmpy > 0)
         tmpiy = (float)((int)(tmpy + 0.5));
      else
         tmpiy = (float)((int)(tmpy - 0.5));

      tmpix *= xobjs.pagelist[areastruct.page]->snapspace;
      tmpix += (tmpix > 0) ? 0.5 : -0.5;
      tmpiy *= xobjs.pagelist[areastruct.page]->snapspace;
      tmpiy += (tmpiy > 0) ? 0.5 : -0.5;

      uvalue->x = (int)tmpix;
      uvalue->y = (int)tmpiy;
   }
}

/*------------------------------------------------------------------------*/
/* window coordinates to user coordinates version 			  */
/*------------------------------------------------------------------------*/

void snap(short valuex, short valuey, XPoint *returnpt)
{
   window_to_user(valuex, valuey, returnpt);  
   u2u_snap(returnpt);
}

/*------------------------------------------------------------------------*/
/* Transform object coordinates through scale, translation, and rotation  */
/* This routine attempts to match the PostScript definition of trans-     */
/*    formation matrices.						  */
/*------------------------------------------------------------------------*/

/*------------------------------------------------------------------------*/
/* Current transformation matrix manipulation routines			  */
/*------------------------------------------------------------------------*/

void UResetCTM(Matrix *ctm)
{
   ctm->a = ctm->e = 1;
   ctm->b = ctm->d = 0;
   ctm->c = ctm->f = 0;  /* 0.5 for nearest-int real->int conversion? */
}

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

void InvertCTM(Matrix *ctm)
{
   float det = ctm->a * ctm->e - ctm->b * ctm->d;
   float tx = ctm->b * ctm->f - ctm->c * ctm->e;
   float ty = ctm->d * ctm->c - ctm->a * ctm->f;

   float tmpa = ctm->a;

   ctm->b = -ctm->b / det;
   ctm->d = -ctm->d / det;

   ctm->a = ctm->e / det;
   ctm->e = tmpa / det;
   ctm->c = tx / det;
   ctm->f = ty / det;
}

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

void UCopyCTM(fctm, tctm)
   Matrix *fctm, *tctm;
{
   tctm->a = fctm->a;
   tctm->b = fctm->b;
   tctm->c = fctm->c;
   tctm->d = fctm->d;
   tctm->e = fctm->e;
   tctm->f = fctm->f;
}

/*-------------------------------------------------------------------------*/
/* Multiply CTM by current screen position and scale to get transformation */
/* matrix from a user point to the X11 window				   */
/*-------------------------------------------------------------------------*/

void UMakeWCTM(Matrix *ctm)
{
   ctm->a *= (*areastruct.vscale);
   ctm->b *= (*areastruct.vscale);
   ctm->c = (ctm->c - (float)areastruct.pcorner->x) * (*areastruct.vscale);
   
   ctm->d *= -(*areastruct.vscale);
   ctm->e *= -(*areastruct.vscale);
   ctm->f = (float)areastruct.height + ((float)areastruct.pcorner->y - ctm->f) *
	    (*areastruct.vscale);
}

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

void UMultCTM(Matrix *ctm, XPoint position, float scale, short rotate)
{
   float tmpa, tmpb, tmpd, tmpe, yscale;
   float mata, matb, matc;
   double drot = (double)rotate * RADFAC;

   yscale = abs(scale);  /* -scale implies flip in x direction only */ 
   
   tmpa =  scale * cos(drot);
   tmpb = yscale * sin(drot);
   tmpd = -scale * sin(drot);
   tmpe = yscale * cos(drot);
   
   mata = ctm->a * tmpa + ctm->d * tmpb;
   matb = ctm->b * tmpa + ctm->e * tmpb;
   matc = ctm->c * tmpa + ctm->f * tmpb + position.x;

   ctm->d = ctm->d * tmpe + ctm->a * tmpd;
   ctm->e = ctm->e * tmpe + ctm->b * tmpd;
   ctm->f = ctm->f * tmpe + ctm->c * tmpd + position.y; 

   ctm->a = mata;
   ctm->b = matb;
   ctm->c = matc;
}

/*----------------------------------------------------------------------*/
/* Slanting function x' = x + beta * y, y' = y				*/
/*----------------------------------------------------------------------*/

void USlantCTM(Matrix *ctm, float beta)
{
   ctm->b += ctm->a * beta;
   ctm->e += ctm->d * beta;
}

#define EPS 1e-9
/*----------------------------------------------------------------------*/
/* Transform text to make it right-side up within 90 degrees of page	*/
/* NOTE:  This is not yet resolved, as xcircuit does not agree with	*/
/* PostScript in a few cases!						*/
/*----------------------------------------------------------------------*/

void UPreScaleCTM(Matrix *ctm)
{
   /* negative X scale (-1, +1) */
   if ((ctm->a < -EPS) || ((ctm->a < EPS) && (ctm->a > -EPS) &&
		((ctm->d * ctm->b) < 0))) {
      ctm->a = -ctm->a;
      ctm->d = -ctm->d;
   }

   /* negative Y scale (+1, -1) */
   if (ctm->e > EPS) {
      ctm->e = -ctm->e;
      ctm->b = -ctm->b;
   }

   /* At 90, 270 degrees need special attention to avoid discrepencies	*/
   /* with the PostScript output due to roundoff error.  This code	*/
   /* matches what PostScript produces.					*/

}

/*----------------------------------------------------------------------*/
/* Adjust justification and CTM as necessary for flip invariance	*/
/*----------------------------------------------------------------------*/

short flipadjust(short justify)
{
   short tmpjust = justify & (~FLIPINV);

   if (justify & FLIPINV) {
      if (((DCTM)->a < -EPS) || (((DCTM)->a < EPS) && ((DCTM)->a > -EPS) &&
		(((DCTM)->d * (DCTM)->b) < 0))) {
         if ((tmpjust & (RIGHT | NOTLEFT)) != NOTLEFT)
            tmpjust ^= (RIGHT | NOTLEFT);
      }   
      if ((DCTM)->e > EPS) {
         if ((tmpjust & (TOP | NOTBOTTOM)) != NOTBOTTOM) 
            tmpjust ^= (TOP | NOTBOTTOM);
      }
      UPreScaleCTM(DCTM);
   }      
   return tmpjust;
}

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

void UPreMultCTM(Matrix *ctm, XPoint position, float scale, short rotate)
{
   float tmpa, tmpb, tmpd, tmpe, yscale;
   float mata, matd;
   double drot = (double)rotate * RADFAC;

   yscale = abs(scale);		/* negative scale value implies flip in x only */
   
   tmpa =  scale * cos(drot);
   tmpb = yscale * sin(drot);
   tmpd = -scale * sin(drot);
   tmpe = yscale * cos(drot);
   
   ctm->c += ctm->a * position.x + ctm->b * position.y;
   ctm->f += ctm->d * position.x + ctm->e * position.y;

   mata = ctm->a * tmpa + ctm->b * tmpd;
   ctm->b = ctm->a * tmpb + ctm->b * tmpe;

   matd = ctm->d * tmpa + ctm->e * tmpd;
   ctm->e = ctm->d * tmpb + ctm->e * tmpe;

   ctm->a = mata;
   ctm->d = matd;
}

/*----------------------------------------------------------------------*/
/* Direct Matrix-Matrix multiplication					*/
/*----------------------------------------------------------------------*/

void UPreMultCTMbyMat(Matrix *ctm, Matrix *pre)
{
   float mata, matd;

   mata = pre->a * ctm->a + pre->d * ctm->b;
   ctm->c += pre->c * ctm->a + pre->f * ctm->b;
   ctm->b = pre->b * ctm->a + pre->e * ctm->b;
   ctm->a = mata;

   matd = pre->a * ctm->d + pre->d * ctm->e;
   ctm->f += pre->c * ctm->d + pre->f * ctm->e;
   ctm->e = pre->b * ctm->d + pre->e * ctm->e;
   ctm->d = matd;
}

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

void UTransformbyCTM(Matrix *ctm, XPoint *ipoints, XPoint *points, short number)
{
   pointlist current, ptptr = points;
   short tmpx;

   for (current = ipoints; current < ipoints + number; current++, ptptr++) {
      tmpx = (short)(ctm->a * (float)current->x + ctm->b * (float)current->y
		 + ctm->c);
      ptptr->y = (short)(ctm->d * (float)current->x + ctm->e * (float)current->y
		 + ctm->f);
      ptptr->x = tmpx;
   }
}
  
/*------------------------------------------------------------------------*/
/* (same as above routine but using type (float) for point values;  this  */
/* is for calculation of Bezier curve internal points.			  */
/*------------------------------------------------------------------------*/

void UfTransformbyCTM(Matrix *ctm, XfPoint *fpoints, XPoint *points, short number)
{
   fpointlist current;
   pointlist new = points;

   for (current = fpoints; current < fpoints + number; current++, new++) {
      new->x = (short)(ctm->a * current->x + ctm->b * current->y + ctm->c);
      new->y = (short)(ctm->d * current->x + ctm->e * current->y + ctm->f);
   }
}

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

void UPopCTM()
{
   Matrixptr lastmatrix;

   if (areastruct.MatStack == NULL) {
      Wprintf("Matrix stack pop error");
      return;
   }
   lastmatrix = areastruct.MatStack->nextmatrix;
   free(areastruct.MatStack);
   areastruct.MatStack = lastmatrix;
}

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

void UPushCTM()
{
   Matrixptr nmatrix;

   nmatrix = (Matrixptr)malloc(sizeof(Matrix));
   if (areastruct.MatStack == NULL)
      UResetCTM(nmatrix);
   else
      UCopyCTM(areastruct.MatStack, nmatrix); 
   nmatrix->nextmatrix = areastruct.MatStack;
   areastruct.MatStack = nmatrix;
}

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

void UTransformPoints(XPoint *points, XPoint *newpoints, short number,
	XPoint atpt, float scale, short rotate)
{
   Matrix LCTM;
 
   UResetCTM(&LCTM);
   UMultCTM(&LCTM, atpt, scale, rotate);
   UTransformbyCTM(&LCTM, points, newpoints, number);
}

/*----------------------------------------------------*/
/* Transform points inward to next hierarchical level */
/*----------------------------------------------------*/

void InvTransformPoints(XPoint *points, XPoint *newpoints, short number,
	XPoint atpt, float scale, short rotate)
{
   Matrix LCTM;
 
   UResetCTM(&LCTM);
   UPreMultCTM(&LCTM, atpt, scale, rotate);
   InvertCTM(&LCTM);
   UTransformbyCTM(&LCTM, points, newpoints, number);
}

/*------------------------------------------------------------------------*/
/* Translate wire coords to force wire to horizontal or vertical position */
/*------------------------------------------------------------------------*/

void manhattanize(XPoint *pospt, polyptr newwire)
{
   short deltax, deltay;
   XPoint *tpoint = newwire->points + newwire->number - 2;

   deltax = abs(tpoint->x - pospt->x);
   deltay = abs(tpoint->y - pospt->y);
   if (deltay > deltax) pospt->x = tpoint->x;
   else pospt->y = tpoint->y;
}

/*----------------------------------------------------------------------*/
/* Bounding box calculation routines					*/
/*----------------------------------------------------------------------*/

void bboxcalc(short testval, short *lowerval, short *upperval)
{
   if (testval < *lowerval) *lowerval = testval;
   if (testval > *upperval) *upperval = testval;
}

/*----------------------------------------------------------------------*/
/* Bounding box calculation for elements which can be part of a path	*/
/*----------------------------------------------------------------------*/

void calcextents(genericptr *bboxgen, short *llx, short *lly, 
	short *urx, short *ury)
{
   switch (ELEMENTTYPE(*bboxgen)) {
      case(POLYGON): {
         pointlist bboxpts;
         for (bboxpts = TOPOLY(bboxgen)->points; bboxpts < TOPOLY(bboxgen)->points
		 + TOPOLY(bboxgen)->number; bboxpts++) {
	    bboxcalc(bboxpts->x, llx, urx);
	    bboxcalc(bboxpts->y, lly, ury);
         }
         } break;

      case(SPLINE): {
         fpointlist bboxpts;
         bboxcalc(TOSPLINE(bboxgen)->ctrl[0].x, llx, urx);
         bboxcalc(TOSPLINE(bboxgen)->ctrl[0].y, lly, ury);
         bboxcalc(TOSPLINE(bboxgen)->ctrl[3].x, llx, urx);
         bboxcalc(TOSPLINE(bboxgen)->ctrl[3].y, lly, ury);
         for (bboxpts = TOSPLINE(bboxgen)->points; bboxpts < 
		 TOSPLINE(bboxgen)->points + INTSEGS; bboxpts++) {
	    bboxcalc((short)(bboxpts->x), llx, urx);
	    bboxcalc((short)(bboxpts->y), lly, ury);
         }
         } break;

      case (ARC): {
         fpointlist bboxpts;
         for (bboxpts = TOARC(bboxgen)->points; bboxpts < TOARC(bboxgen)->points +
	         TOARC(bboxgen)->number; bboxpts++) {
            bboxcalc((short)(bboxpts->x), llx, urx);
	    bboxcalc((short)(bboxpts->y), lly, ury);
         }
         } break;
   }
}

/*----------------------------------------------------------------------*/
/* Calculate the bounding box of an object instance			*/
/*----------------------------------------------------------------------*/

void objinstbbox(objinstptr obbox, XPoint *npoints)
{
   XPoint points[4];

   points[0].x = points[1].x = obbox->bbox.lowerleft.x;
   points[1].y = points[2].y = obbox->bbox.lowerleft.y + obbox->bbox.height;
   points[2].x = points[3].x = obbox->bbox.lowerleft.x + obbox->bbox.width;
   points[0].y = points[3].y = obbox->bbox.lowerleft.y;

   UTransformPoints(points, npoints, 4, obbox->position,
		 obbox->scale, obbox->rotation); 
}

/*----------------------------------------------------------------------*/
/* Calculate the bounding box of a label				*/
/*----------------------------------------------------------------------*/

void labelbbox(labelptr labox, XPoint *npoints, objinstptr callinst)
{
   XPoint points[4];
   TextExtents tmpext;
   short j;

   tmpext = ULength(labox->string, callinst, 0.0, 0, NULL);
   points[0].x = points[1].x = (labox->justify & NOTLEFT ? 
	       (labox->justify & RIGHT ? -tmpext.width :
		-tmpext.width / 2) : 0);
   points[2].x = points[3].x = points[0].x + tmpext.width;
   points[0].y = points[3].y = (labox->justify & NOTBOTTOM ?
	       (labox->justify & TOP ? -tmpext.ascent :
		-(tmpext.ascent + tmpext.base) / 2) : -tmpext.base)
		+ tmpext.descent;
   points[1].y = points[2].y = points[0].y + tmpext.ascent - tmpext.descent;

   /* separate bounding box for pinlabels and infolabels */

   if (labox->pin)
      for (j = 0; j < 4; j++)
	 pinadjust(labox->justify, &points[j].x, &points[j].y, 1);

   UTransformPoints(points, npoints, 4, labox->position,
		labox->scale, labox->rotation); 
}

/*--------------------------------------------------------------*/
/* Wrapper for single call to calcbboxsingle() in the netlister */
/*--------------------------------------------------------------*/

void calcinstbbox(genericptr *bboxgen, short *llx, short *lly, short *urx,
                short *ury)
{
   *llx = *lly = 32767;
   *urx = *ury = -32768;

   calcbboxsingle(bboxgen, areastruct.topinstance, llx, lly, urx, ury);
}

/*----------------------------------------------------------------------*/
/* Bounding box calculation for a single generic element		*/
/*----------------------------------------------------------------------*/

void calcbboxsingle(genericptr *bboxgen, objinstptr thisinst, 
		short *llx, short *lly, short *urx, short *ury)
{
   XPoint npoints[4];
   short j;

   /* For each screen element, compute the extents and revise bounding	*/
   /* box points, if necessary. 					*/

   switch(ELEMENTTYPE(*bboxgen)) {

      case(OBJINST):
	 objinstbbox(TOOBJINST(bboxgen), npoints);

         for (j = 0; j < 4; j++) {
            bboxcalc(npoints[j].x, llx, urx);
            bboxcalc(npoints[j].y, lly, ury);
         }
	 break;

      case(LABEL):
	 {
	    /* because a pin is offset from its position point, include */
	    /* that point in the bounding box.				*/

	    if (TOLABEL(bboxgen)->pin) {
               bboxcalc(TOLABEL(bboxgen)->position.x, llx, urx);
               bboxcalc(TOLABEL(bboxgen)->position.y, lly, ury);
	    }
	    labelbbox(TOLABEL(bboxgen), npoints, thisinst);

            for (j = 0; j < 4; j++) {
               bboxcalc(npoints[j].x, llx, urx);
               bboxcalc(npoints[j].y, lly, ury);
            }
         } break;

      case(PATH): {
	 genericptr *pathc;
	 for (pathc = TOPATH(bboxgen)->plist; pathc < TOPATH(bboxgen)->plist
		  + TOPATH(bboxgen)->parts; pathc++)
	       calcextents(pathc, llx, lly, urx, ury);
	 } break;

      default:
	 calcextents(bboxgen, llx, lly, urx, ury);
   }
}

/*------------------------------------------------------*/
/* Find if an object is in the specified library	*/
/*------------------------------------------------------*/

Boolean object_in_library(short libnum, objectptr thisobject)
{
   short i;

   for (i = 0; i < xobjs.userlibs[libnum].number; i++) {
      if (*(xobjs.userlibs[libnum].library + i) == thisobject)
	 return True;
   }
   return False;
}

/*-----------------------------------------------------------*/
/* Find if an object is in the hierarchy of the given object */
/* Returns the number (position in plist) or -1 if not found */
/*-----------------------------------------------------------*/

short find_object(objectptr pageobj, objectptr thisobject)
{
   short i, j;
   genericptr *pelem;

   for (i = 0; i < pageobj->parts; i++) {
      pelem = pageobj->plist + i;
      if (IS_OBJINST(*pelem)) {
	 if ((TOOBJINST(pelem))->thisobject == thisobject)
	    return i;
	 else if ((j = find_object((TOOBJINST(pelem))->thisobject, thisobject)) >= 0)
	    return i;  /* was j---is this the right fix? */
      }
   }
   return -1;
}

/*------------------------------------------------------*/
/* Find all pages and libraries containing this object	*/
/* and update accordingly.  If this object is a page,	*/
/* just update the page directory.			*/
/*------------------------------------------------------*/

void updatepagebounds(objectptr thisobject)
{
   short i, j;
   objectptr pageobj;

   if ((i = is_page(thisobject)) >= 0) {
      if (xobjs.pagelist[i]->background.name != (char *)NULL)
         backgroundbbox(i);
      updatepagelib(PAGELIB, i);
   }
   else {
      for (i = 0; i < xobjs.pages; i++) {
         if (xobjs.pagelist[i]->pageinst != NULL) {
	    pageobj = xobjs.pagelist[i]->pageinst->thisobject;
            if ((j = find_object(pageobj, thisobject)) >= 0) {
	       calcbboxvalues(xobjs.pagelist[i]->pageinst,
			(genericptr *)(pageobj->plist + j));
	       updatepagelib(PAGELIB, i);
	    }
         }
      }
      for (i = 0; i < xobjs.numlibs; i++)
         if (object_in_library(i, thisobject))
	    composelib(i + LIBRARY);
   }
}

/*--------------------------------------------------------------*/
/* Free memory for the schematic bounding box                   */
/*--------------------------------------------------------------*/

void invalidateschembbox(objinstptr thisinst)
{
   if (thisinst->schembbox != NULL) {
      free(thisinst->schembbox);
      thisinst->schembbox = NULL;
   }
}

/*--------------------------------------------------------------*/
/* Calculate the bounding box for an object instance.  Use the	*/
/* existing bbox and finish calculation on all the elements	*/
/* which have parameters not taking default values.		*/
/* This finishes the calculation partially done by		*/
/* calcbboxvalues().						*/
/*--------------------------------------------------------------*/

void calcbboxinst(objinstptr thisinst)
{
   objectptr thisobj;
   genericptr *gelem;
   short llx, lly, urx, ury;

   short pllx, plly, purx, pury;
   Boolean hasschembbox = FALSE;

   if (thisinst == NULL) return;

   thisobj = thisinst->thisobject;

   llx = thisobj->bbox.lowerleft.x;
   lly = thisobj->bbox.lowerleft.y;
   urx = llx + thisobj->bbox.width;
   ury = lly + thisobj->bbox.height;

   pllx = plly = 32767;
   purx = pury = -32768;

   for (gelem = thisobj->plist; gelem < thisobj->plist + thisobj->parts;
		gelem++) {
      /* pins which do not appear outside of the object	*/
      /* contribute to the objects "schembbox".		*/

      if (IS_LABEL(*gelem)) {
	 labelptr btext = TOLABEL(gelem);
	 if (btext->pin && !(btext->justify & PINVISIBLE)) {
	    hasschembbox = TRUE;
	    calcbboxsingle(gelem, thisinst, &pllx, &plly, &purx, &pury);
	    continue;
	 }
      }

      if (has_param(*gelem))
	 calcbboxsingle(gelem, thisinst, &llx, &lly, &urx, &ury);
   }

   thisinst->bbox.lowerleft.x = llx;
   thisinst->bbox.lowerleft.y = lly;
   thisinst->bbox.width = urx - llx;
   thisinst->bbox.height = ury - lly;

   if (hasschembbox) {
      if (thisinst->schembbox == NULL)
         thisinst->schembbox = (BBox *)malloc(sizeof(BBox));

      thisinst->schembbox->lowerleft.x = pllx;
      thisinst->schembbox->lowerleft.y = plly;
      thisinst->schembbox->width = purx - pllx;
      thisinst->schembbox->height = pury - plly;
   }
   else
      invalidateschembbox(thisinst);
}

/*--------------------------------------------------------------*/
/* Update things based on a changed instance bounding box.	*/
/* If the parameter was a single-instance			*/
/* substitution, only the page should be updated.  If the	*/
/* parameter was a default value, the library should be updated */
/* and any pages containing the object where the parameter	*/
/* takes the default value.					*/
/*--------------------------------------------------------------*/

void updateinstparam(objectptr bobj)
{
   short i, j;
   objectptr pageobj;

   /* change bounds on pagelib and all pages			*/
   /* containing this *object* if and only if the object	*/
   /* instance takes the default value.  Also update the	*/
   /* library page.						*/

   for (i = 0; i < xobjs.pages; i++)
      if (xobjs.pagelist[i]->pageinst != NULL) {
	 pageobj = xobjs.pagelist[i]->pageinst->thisobject;
         if ((j = find_object(pageobj, topobject)) >= 0) {

	    /* Really, we'd like to recalculate the bounding box only if the */
	    /* parameter value is the default value which was just changed.	*/
	    /* However, then any non-default values may contain the wrong	*/
	    /* substitutions.						*/

	    objinstptr cinst = TOOBJINST(pageobj->plist + j);
	    if (cinst->thisobject->params == NULL) {
	       calcbboxvalues(xobjs.pagelist[i]->pageinst, pageobj->plist + j);
	       updatepagelib(PAGELIB, i);
	    }
	 }
      }

   for (i = 0; i < xobjs.numlibs; i++)
      if (object_in_library(i, topobject))
	 composelib(i + LIBRARY);
}

/*--------------------------------------------------------------*/
/* Calculate bbox on all elements of the given object		*/
/*--------------------------------------------------------------*/

void calcbbox(objinstptr binst)
{
   calcbboxvalues(binst, (genericptr *)NULL);
   if (binst == areastruct.topinstance) {
      updatepagebounds(topobject);
   }
}

/*--------------------------------------------------------------*/
/* Calculate bbox on the given element of the specified object.	*/
/* This is a wrapper for calcbboxvalues() assuming that we're	*/
/* on the top-level, and that page bounds need to be updated.	*/
/*--------------------------------------------------------------*/

void singlebbox(genericptr *gelem)
{
   calcbboxvalues(areastruct.topinstance, (genericptr *)gelem);
   updatepagebounds(topobject);
}

/*----------------------------------------------------------------------*/
/* Extend bounding box based on selected elements only			*/
/*----------------------------------------------------------------------*/

void calcbboxselect()
{
   short *bsel;
   for (bsel = areastruct.selectlist; bsel < areastruct.selectlist +
		areastruct.selects; bsel++)
      calcbboxvalues(areastruct.topinstance, topobject->plist + *bsel);
  
   updatepagebounds(topobject);
}

/*--------------------------------------------------------------*/
/* Update Bounding box for an object.				*/
/* If newelement == NULL, calculate bounding box from scratch.	*/
/* Otherwise, expand bounding box to enclose newelement.	*/
/*--------------------------------------------------------------*/

void calcbboxvalues(objinstptr thisinst, genericptr *newelement)
{
   genericptr *bboxgen;
   short llx, lly, urx, ury;
   objectptr thisobj = thisinst->thisobject;

   /* no action if there are no elements */
   if (thisobj->parts == 0) return;

   /* If this object has parameters, then we will do a separate		*/
   /* bounding box calculation on parameterized parts.  This		*/
   /* calculation ignores them, and the result is a base that the	*/
   /* instance bounding-box computation can use as a starting point.	*/

   /* set starting bounds as maximum bounds of screen */
   llx = lly = 32767;
   urx = ury = -32768;

   for (bboxgen = thisobj->plist; bboxgen < thisobj->plist +
		thisobj->parts; bboxgen++) {

      /* override the "for" loop if we're doing a single element */
      if (newelement != NULL) bboxgen = newelement;

      if ((thisobj->params == NULL) || (!has_param(*bboxgen))) {
	 /* pins which do not appear outside of the object 	*/
	 /* are ignored now---will be computed per instance.	*/

	 if (IS_LABEL(*bboxgen)) {
	    labelptr btext = TOLABEL(bboxgen);
	    if (btext->pin && !(btext->justify & PINVISIBLE)) {
	       goto nextgen;
	    }
	 }
	 calcbboxsingle(bboxgen, thisinst, &llx, &lly, &urx, &ury);
      }
nextgen:
      if (newelement != NULL) break;
   }

   /* if this is a single-element calculation and its bounding box	*/
   /* turned out to be smaller than the object's, then we need to	*/
   /* recompute the entire object's bounding box in case it got		*/
   /* smaller.  This is not recursive, in spite of looks.		*/

   if (newelement != NULL) {
      if (llx > thisobj->bbox.lowerleft.x &&
		lly > thisobj->bbox.lowerleft.y &&
		urx < (thisobj->bbox.lowerleft.x + thisobj->bbox.width) &&
		ury < (thisobj->bbox.lowerleft.y + thisobj->bbox.height)) {
	 calcbboxvalues(thisinst, NULL);
	 return;
      }
      else {
	 bboxcalc(thisobj->bbox.lowerleft.x, &llx, &urx);
	 bboxcalc(thisobj->bbox.lowerleft.y, &lly, &ury);
	 bboxcalc(thisobj->bbox.lowerleft.x + thisobj->bbox.width, &llx, &urx);
	 bboxcalc(thisobj->bbox.lowerleft.y + thisobj->bbox.height, &lly, &ury);
      }
   }

   /* set the new bounding box */

   thisobj->bbox.lowerleft.x = llx;
   thisobj->bbox.lowerleft.y = lly;
   thisobj->bbox.width = urx - llx;
   thisobj->bbox.height = ury - lly;

   /* calculate instance-specific values */
   calcbboxinst(thisinst);
}

/*------------------------------------------------------*/
/* Center an object in the viewing window		*/
/*------------------------------------------------------*/

void centerview(objinstptr tinst)
{
   XPoint origin, corner;
   Dimension width, height;
   float fitwidth, fitheight;
   objectptr tobj = tinst->thisobject;

   origin = tinst->bbox.lowerleft;
   corner.x = origin.x + tinst->bbox.width;
   corner.y = origin.y + tinst->bbox.height;

   extendschembbox(tinst, &origin, &corner);

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

   fitwidth = (float)areastruct.width / ((float)width + 2 * DEFAULTGRIDSPACE);
   fitheight = (float)areastruct.height / ((float)height + 2 * DEFAULTGRIDSPACE);

   tobj->viewscale = (fitwidth < fitheight) ?
		 min(MINAUTOSCALE, fitwidth) : min(MINAUTOSCALE, fitheight);

   tobj->pcorner.x = origin.x - (areastruct.width
	       / tobj->viewscale - width) / 2;
   tobj->pcorner.y = origin.y - (areastruct.height
	       / tobj->viewscale - height) / 2;
}

/*-----------------------------------------------------------*/
/* Refresh the window and scrollbars and write the page name */
/*-----------------------------------------------------------*/

void refresh(xcWidget bw, caddr_t clientdata, caddr_t calldata)
{
   drawarea(NULL, NULL, NULL);
   if (areastruct.scrollbarh)
      drawhbar(areastruct.scrollbarh, NULL, NULL);
   if (areastruct.scrollbarv)
      drawvbar(areastruct.scrollbarv, NULL, NULL);
   printname(topobject);
}

/*------------------------------------------------------*/
/* Center the current page in the viewing window	*/
/*------------------------------------------------------*/

void zoomview(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   if (eventmode == NORMAL_MODE || eventmode == COPY2_MODE ||
	 eventmode == PUSH_MODE || eventmode == PRESS_MODE ||
	 eventmode == CATALOG_MODE || eventmode == FONTCAT_MODE ||
	 eventmode == FONTCAT2_MODE) {

      centerview(areastruct.topinstance);
      areastruct.lastbackground = NULL;
      renderbackground();
      refresh(NULL, NULL, NULL);
   }
}

/*---------------------------------------------------------*/
/* Basic X Graphics Routines in the User coordinate system */
/*---------------------------------------------------------*/

void UDrawSimpleLine(XPoint *pt1, XPoint *pt2)
{
   XPoint newpt1, newpt2;

   UTransformbyCTM(DCTM, pt1, &newpt1, 1);
   UTransformbyCTM(DCTM, pt2, &newpt2, 1);

   XDrawLine(dpy, areastruct.areawin, areastruct.gc,
		newpt1.x, newpt1.y, newpt2.x, newpt2.y); 
} 

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

void UDrawLine(XPoint *pt1, XPoint *pt2)
{
   short tmpwidth = UTopTransScale(xobjs.pagelist[areastruct.page]->wirewidth);

   XSetLineAttributes(dpy, areastruct.gc, tmpwidth >= 2.0 ? (int)tmpwidth : 
	0, LineSolid, CapRound, JoinBevel);
   UDrawSimpleLine(pt1, pt2); 
} 

/*----------------------------------------------------------------------*/
/* Add circle at given point to indicate that the point is a parameter.	*/
/* The circle is divided into quarters.  For parameterized y-coordinate	*/
/* the top and bottom quarters are drawn.  For parameterized x-		*/
/* coordinate, the left and right quarters are drawn.  A full circle	*/
/* indicates either both x- and y-coordinates are parameterized, or	*/
/* else any other kind of parameterization (presently, not used).	*/
/*									*/
/* (note that the two angles in XDrawArc() are 1) the start angle,	*/
/* measured in absolute 64th degrees from 0 (3 o'clock), and 2) the	*/
/* path length, in relative 64th degrees (positive = counterclockwise,	*/
/* negative = clockwise)).						*/
/*----------------------------------------------------------------------*/

void UDrawCircle(XPoint *upt, u_char which)
{
   XPoint wpt;

   user_to_window(*upt, &wpt);
   XSetLineAttributes(dpy, areastruct.gc, 0, LineSolid, CapButt, JoinMiter);

   switch(which) {
      case P_POSITION_X:
         XDrawArc(dpy, areastruct.areawin, areastruct.gc, wpt.x - 4,
		wpt.y - 4, 8, 8, -(45 * 64), (90 * 64));
         XDrawArc(dpy, areastruct.areawin, areastruct.gc, wpt.x - 4,
		wpt.y - 4, 8, 8, (135 * 64), (90 * 64));
	 break;
      case P_POSITION_Y:
         XDrawArc(dpy, areastruct.areawin, areastruct.gc, wpt.x - 4,
		wpt.y - 4, 8, 8, (45 * 64), (90 * 64));
         XDrawArc(dpy, areastruct.areawin, areastruct.gc, wpt.x - 4,
		wpt.y - 4, 8, 8, (225 * 64), (90 * 64));
	 break;
      default:
         XDrawArc(dpy, areastruct.areawin, areastruct.gc, wpt.x - 4,
		wpt.y - 4, 8, 8, 0, (360 * 64));
	 break;
   }
}

/*----------------------------------------------------------------------*/
/* Add "X" at string origin						*/
/*----------------------------------------------------------------------*/

void UDrawXAt(XPoint *wpt)
{
   XSetLineAttributes(dpy, areastruct.gc, 0, LineSolid, CapButt, JoinMiter);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wpt->x - 3,
		wpt->y - 3, wpt->x + 3, wpt->y + 3);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wpt->x + 3,
		wpt->y - 3, wpt->x - 3, wpt->y + 3);
}

/*----------------------------------------------------------------------*/
/* Draw "X" on current level						*/
/*----------------------------------------------------------------------*/

void UDrawX(labelptr curlabel)
{
   XPoint wpt;

   user_to_window(curlabel->position, &wpt);
   UDrawXAt(&wpt);
}

/*----------------------------------------------------------------------*/
/* Draw "X" on top level (only for LOCAL and GLOBAL pin labels)		*/
/*----------------------------------------------------------------------*/

void UDrawXDown(labelptr curlabel)
{
   XPoint wpt;

   UTransformbyCTM(DCTM, &curlabel->position, &wpt, 1);
   UDrawXAt(&wpt);
}

/*----------------------------------------------------------------------*/
/* Find the "real" width, height, and origin of an object including pin	*/
/* labels and so forth that only show up on a schematic when it is the	*/
/* top-level object.							*/
/*----------------------------------------------------------------------*/

int toplevelwidth(objinstptr bbinst, short *rllx)
{
   short llx, urx;
   short origin, corner;

   if (bbinst->schembbox == NULL) {
      if (rllx) *rllx = bbinst->bbox.lowerleft.x;
      return bbinst->bbox.width;
   }

   origin = bbinst->bbox.lowerleft.x;
   corner = origin + bbinst->bbox.width;

   llx = bbinst->schembbox->lowerleft.x;
   urx = llx + bbinst->schembbox->width;

   bboxcalc(llx, &origin, &corner);
   bboxcalc(urx, &origin, &corner);

   if (rllx) *rllx = origin;
   return(corner - origin);
}

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

int toplevelheight(objinstptr bbinst, short *rlly)
{
   short lly, ury;
   short origin, corner;

   if (bbinst->schembbox == NULL) {
      if (rlly) *rlly = bbinst->bbox.lowerleft.y;
      return bbinst->bbox.height;
   }

   origin = bbinst->bbox.lowerleft.y;
   corner = origin + bbinst->bbox.height;

   lly = bbinst->schembbox->lowerleft.y;
   ury = lly + bbinst->schembbox->height;

   bboxcalc(lly, &origin, &corner);
   bboxcalc(ury, &origin, &corner);

   if (rlly) *rlly = origin;
   return(corner - origin);
}

/*----------------------------------------------------------------------*/
/* Add dimensions of schematic pins to an object's bounding box		*/
/*----------------------------------------------------------------------*/

void extendschembbox(objinstptr bbinst, XPoint *origin, XPoint *corner)
{
   short llx, lly, urx, ury;

   if ((bbinst == NULL) || (bbinst->schembbox == NULL)) return;

   llx = bbinst->schembbox->lowerleft.x;
   lly = bbinst->schembbox->lowerleft.y;
   urx = llx + bbinst->schembbox->width;
   ury = lly + bbinst->schembbox->height;

   bboxcalc(llx, &(origin->x), &(corner->x));
   bboxcalc(lly, &(origin->y), &(corner->y));
   bboxcalc(urx, &(origin->x), &(corner->x));
   bboxcalc(ury, &(origin->y), &(corner->y));
}

/*----------------------------------------------------------------------*/
/* Adjust a pinlabel position to account for pad spacing		*/
/*----------------------------------------------------------------------*/

void pinadjust (short justify, short *xpoint, short *ypoint, short dir)
{
   int delx, dely;

   dely = (justify & NOTBOTTOM) ?
            ((justify & TOP) ? -PADSPACE : 0) : PADSPACE;
   delx = (justify & NOTLEFT) ?
            ((justify & RIGHT) ? -PADSPACE : 0) : PADSPACE;

   if (xpoint != NULL) *xpoint += (dir > 0) ? delx : -delx;
   if (ypoint != NULL) *ypoint += (dir > 0) ? dely : -dely;
}

/*----------------------------------------------------------------------*/
/* Draw line for editing text (position of cursor in string is given by */
/*   tpos (2nd parameter)						*/
/*----------------------------------------------------------------------*/

void UDrawTextLine(labelptr curlabel, short tpos)
{
   XPoint  points[2]; /* top and bottom of text cursor line */
   short   xdist, xbase, tmpjust;
   TextExtents tmpext;

   /* correct for position, rotation, scale, and flip invariance of text */

   UPushCTM();
   UPreMultCTM(DCTM, curlabel->position, curlabel->scale, curlabel->rotation);
   tmpjust = flipadjust(curlabel->justify);

   XSetFunction(dpy, areastruct.gc, GXxor);
   XSetForeground(dpy, areastruct.gc, AUXCOLOR ^ BACKGROUND);

   tmpext = ULength(curlabel->string, areastruct.topinstance, 0.0, tpos, NULL);
   xdist = tmpext.width;
   xbase = tmpext.base;
   tmpext = ULength(curlabel->string, areastruct.topinstance, 0.0, 0, NULL);

   points[0].x = (tmpjust & NOTLEFT ?
        (tmpjust & RIGHT ? -tmpext.width : -tmpext.width >> 1) : 0)
	+ xdist;
   points[0].y = (tmpjust & NOTBOTTOM ?
        (tmpjust & TOP ? -tmpext.ascent : -(tmpext.ascent + tmpext.base) / 2)
	: -tmpext.base) + xbase - 3;
   points[1].x = points[0].x;
   points[1].y = points[0].y + TEXTHEIGHT + 6;

   if (curlabel->pin) {
      pinadjust(tmpjust, &(points[0].x), &(points[0].y), 1);
      pinadjust(tmpjust, &(points[1].x), &(points[1].y), 1);
   }

   /* draw the line */

   UDrawLine(&points[0], &points[1]);
   UPopCTM();

   UDrawX(curlabel);
}

/*-----------------------------------------------------------------*/
/* Draw lines for editing text when multiple characters are chosen */
/*-----------------------------------------------------------------*/

void UDrawTLine(labelptr curlabel)
{
   UDrawTextLine(curlabel, textpos);
   if ((textend > 0) && (textend < textpos)) {
      UDrawTextLine(curlabel, textend);
   }
}

/*----------------------*/
/* Draw an X		*/
/*----------------------*/

void UDrawXLine(XPoint opt, XPoint cpt)
{
   XPoint upt, vpt;

   XSetForeground(dpy, areastruct.gc, AUXCOLOR ^ BACKGROUND);
   XSetFunction(dpy, areastruct.gc, GXxor);

   user_to_window(cpt, &upt);
   user_to_window(opt, &vpt);

   XSetLineAttributes(dpy, areastruct.gc, 0, LineOnOffDash, CapButt, JoinMiter);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, vpt.x, vpt.y, upt.x, upt.y);

   XSetLineAttributes(dpy, areastruct.gc, 0, LineSolid, CapButt, JoinMiter);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, upt.x - 3, upt.y - 3,
		upt.x + 3, upt.y + 3);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, upt.x + 3, upt.y - 3,
		upt.x - 3, upt.y + 3);

   XSetFunction(dpy, areastruct.gc, areastruct.gctype);
   XSetForeground(dpy, areastruct.gc, areastruct.gccolor);
}

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

void UDrawBox(XPoint origin, XPoint corner)
{
   XPoint	worig, wcorn;

   user_to_window(origin, &worig);
   user_to_window(corner, &wcorn);

   XSetFunction(dpy, areastruct.gc, GXxor);
   XSetForeground(dpy, areastruct.gc, AUXCOLOR ^ BACKGROUND);
   XSetLineAttributes(dpy, areastruct.gc, 0, LineSolid, CapRound, JoinBevel);

   XDrawLine(dpy, areastruct.areawin, areastruct.gc, worig.x, worig.y,
		worig.x, wcorn.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, worig.x, wcorn.y,
		wcorn.x, wcorn.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wcorn.x, wcorn.y,
		wcorn.x, worig.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wcorn.x, worig.y,
		worig.x, worig.y);
}

/*-------------------------------------------------------------------------*/
void UDrawBBox()
{
   XPoint	origin;
   XPoint	worig, wcorn, corner;
   objinstptr	bbinst = areastruct.topinstance;

   if ((!areastruct.bboxon) || (checkforbbox(topobject) != NULL)) return;

   origin = bbinst->bbox.lowerleft;
   corner.x = origin.x + bbinst->bbox.width;
   corner.y = origin.y + bbinst->bbox.height;

   /* Include any schematic labels in the bounding box.	*/
   extendschembbox(bbinst, &origin, &corner);

   user_to_window(origin, &worig);
   user_to_window(corner, &wcorn);

   XSetForeground(dpy, areastruct.gc, BBOXCOLOR);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, worig.x, worig.y,
		worig.x, wcorn.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, worig.x, wcorn.y,
		wcorn.x, wcorn.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wcorn.x, wcorn.y,
		wcorn.x, worig.y);
   XDrawLine(dpy, areastruct.areawin, areastruct.gc, wcorn.x, worig.y,
		worig.x, worig.y);
}

/*-------------------------------------------------------------------------*/
/* Fill and/or draw a border around the stroking path			   */
/*-------------------------------------------------------------------------*/

void strokepath(XPoint *pathlist, short number, short style, float width)
{
   char         solidpart;
   char         dashstring[3];
   short        tmpwidth;

   tmpwidth = max(1, UTopTransScale(xobjs.pagelist[areastruct.page]->wirewidth *
	width));

   if (style & FILLED || (!(style & FILLED) && style & OPAQUE)) {
      if ((style & FILLSOLID) == FILLSOLID)
         XSetFillStyle(dpy, areastruct.gc, FillSolid);
      else if (!(style & FILLED)) {
         XSetFillStyle(dpy, areastruct.gc, FillOpaqueStippled); 
	 XSetStipple(dpy, areastruct.gc, STIPPLE[7]);
      }
      else {
	 if (style & OPAQUE)
            XSetFillStyle(dpy, areastruct.gc, FillOpaqueStippled);
	 else
            XSetFillStyle(dpy, areastruct.gc, FillStippled);
         XSetStipple(dpy, areastruct.gc, STIPPLE[ (style &
                FILLSOLID) >> 5] );
      }
      XFillPolygon(dpy, areastruct.areawin, areastruct.gc, pathlist, number, Nonconvex,
		CoordModeOrigin);
      /* return to original state */
      XSetFillStyle(dpy, areastruct.gc, FillSolid);
   }
   if (!(style & NOBORDER)) {
      /* set up dots or dashes */
      if (style & DASHED) solidpart = (char)(4 * tmpwidth);
      else if (style & DOTTED) solidpart = (char)tmpwidth;
      sprintf(dashstring, "%c%c", solidpart, (char)(4 * tmpwidth));
      if (style & (DASHED | DOTTED)) {
         XSetDashes(dpy, areastruct.gc, 0, dashstring, 2);
         XSetLineAttributes(dpy, areastruct.gc, tmpwidth >= 2.0 ?
            (int)tmpwidth : 0, LineOnOffDash, CapButt, JoinBevel);
      }
      else
         XSetLineAttributes(dpy, areastruct.gc, tmpwidth >= 2.0 ?
            (int)tmpwidth : 0, LineSolid, CapRound, JoinBevel);

      /* draw the spline and close off if so specified */
      XDrawLines(dpy, areastruct.areawin, areastruct.gc, pathlist,
		number, CoordModeOrigin);
      if (!(style & UNCLOSED))
         XDrawLine(dpy, areastruct.areawin, areastruct.gc, pathlist[0].x,
		pathlist[0].y, pathlist[number - 1].x, pathlist[number - 1].y);
   }
}

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

void makesplinepath(splineptr thespline, XPoint *pathlist)
{
   XPoint *tmpptr = pathlist;

   UTransformbyCTM(DCTM, &(thespline->ctrl[0]), tmpptr, 1);
   UfTransformbyCTM(DCTM, thespline->points, ++tmpptr, INTSEGS);
   UTransformbyCTM(DCTM, &(thespline->ctrl[3]), tmpptr + INTSEGS, 1);
}

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

void UDrawSpline(splineptr thespline)
{
   XPoint       tmppoints[SPLINESEGS];

   makesplinepath(thespline, tmppoints);
   strokepath(tmppoints, SPLINESEGS, thespline->style, thespline->width);
}

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

void UDrawEditSpline(splineptr thespline)
{
   UDrawSpline(thespline);
   UDrawXLine(thespline->ctrl[0], thespline->ctrl[1]);  
   UDrawXLine(thespline->ctrl[3], thespline->ctrl[2]);
}

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

void UDrawPolygon(polyptr thepoly)
{
   XPoint *tmppoints = (pointlist) malloc(thepoly->number * sizeof(XPoint));

   UTransformbyCTM(DCTM, thepoly->points, tmppoints, thepoly->number);
   strokepath(tmppoints, thepoly->number, thepoly->style, thepoly->width);
   free(tmppoints);
}

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

void UDrawArc(arcptr thearc)
{
   XPoint  tmppoints[RSTEPS + 2];

   UfTransformbyCTM(DCTM, thearc->points, tmppoints, thearc->number);
   strokepath(tmppoints, thearc->number, thearc->style, thearc->width);
}

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

void UDrawPath(pathptr thepath)
{
   XPoint	*tmppoints = (pointlist) malloc(sizeof(XPoint));
   genericptr	*genpath;
   polyptr	thepoly;
   splineptr	thespline;
   arcptr	thearc;
   int		pathsegs = 0, curseg = 0;
   
   for (genpath = thepath->plist; genpath < thepath->plist + thepath->parts;
	  genpath++) {
      switch(ELEMENTTYPE(*genpath)) {
	 case POLYGON:
	    thepoly = TOPOLY(genpath);
	    pathsegs += thepoly->number;
	    tmppoints = (pointlist) realloc(tmppoints, pathsegs * sizeof(XPoint));
   	    UTransformbyCTM(DCTM, thepoly->points, tmppoints + curseg, thepoly->number);
	    curseg = pathsegs;
	    break;
	 case SPLINE:
	    thespline = TOSPLINE(genpath);
	    pathsegs += SPLINESEGS;
	    tmppoints = (pointlist) realloc(tmppoints, pathsegs * sizeof(XPoint));
   	    makesplinepath(thespline, tmppoints + curseg);
	    curseg = pathsegs;
	    break;
	 case ARC:
	    thearc = TOARC(genpath);
	    pathsegs += thearc->number;
	    tmppoints = (pointlist) realloc(tmppoints, pathsegs * sizeof(XPoint));
   	    UfTransformbyCTM(DCTM, thearc->points, tmppoints + curseg, thearc->number);
	    curseg = pathsegs;
	    break;
      }
   } 
   strokepath(tmppoints, pathsegs, thepath->style, thepath->width);
   free(tmppoints);
}

/*----------------------------------------------------------------------*/
/* Main recursive object instance drawing routine.			*/
/*    context is the instance information passed down from above	*/
/*    theinstance is the object instance to be drawn			*/
/*    level is the level of recursion 					*/
/*    passcolor is the inherited color value passed to object		*/
/*----------------------------------------------------------------------*/

void UDrawObject(objinstptr theinstance, short level, int passcolor, pushlistptr *stack)
{
   genericptr	*areagen;
   short	tmpwidth;
   int		defaultcolor = passcolor;
   int		curcolor = passcolor;
   XPoint 	bboxin, bboxout[2];
   u_char	xm, ym;
   objectptr	theobject = theinstance->thisobject;

   /* All parts are given in the coordinate system of the object, unless */
   /* this is the top-level object, in which they will be interpreted as */
   /* relative to the screen.						 */

   UPushCTM();

   if (stack) push_stack(stack, theinstance);
   if (level != 0)
       UPreMultCTM(DCTM, theinstance->position, theinstance->scale,
			theinstance->rotation);

   /* do a quick test for intersection with the display window */

   UTransformbyCTM(DCTM, &(theobject->bbox.lowerleft), &(bboxout[0]), 1);
   bboxin.x = theobject->bbox.lowerleft.x + theobject->bbox.width;
   bboxin.y = theobject->bbox.lowerleft.y + theobject->bbox.height; 
   UTransformbyCTM(DCTM, &bboxin, &(bboxout[1]), 1);

   xm = (bboxout[0].x < bboxout[1].x) ? 0 : 1;  
   ym = (bboxout[0].y < bboxout[1].y) ? 0 : 1;  

   if (bboxout[xm].x < areastruct.width && bboxout[ym].y < areastruct.height &&
       bboxout[1 - xm].x > 0 && bboxout[1 - ym].y > 0) {       

     /* make parameter substitutions */
     psubstitute(theinstance);

     /* draw all of the elements */
   
     tmpwidth = UTopTransScale(xobjs.pagelist[areastruct.page]->wirewidth);
     XSetLineAttributes(dpy, areastruct.gc, tmpwidth >= 2.0 ? (int)tmpwidth :
		0, LineSolid, CapRound, JoinBevel);

     for (areagen = theobject->plist; areagen < theobject->plist +
           theobject->parts; areagen++) {

       if ((*areagen)->type & DRAW_HIDE) continue;

       if (defaultcolor != DOFORALL) {
	  if ((*areagen)->color != curcolor) {
	     if ((*areagen)->color == DEFAULTCOLOR)
		curcolor = defaultcolor;
	     else
		curcolor = (*areagen)->color;
	     XTopSetForeground(curcolor);
	  }
       }

       switch(ELEMENTTYPE(*areagen)) {
	  case(POLYGON):
	     if (level == 0 || !((TOPOLY(areagen))->style & BBOX))
                UDrawPolygon(TOPOLY(areagen));
	     break;
   
	  case(SPLINE):
             UDrawSpline(TOSPLINE(areagen));
	     break;
   
	  case(ARC):
             UDrawArc(TOARC(areagen));
	     break;

	  case(PATH):
	     UDrawPath(TOPATH(areagen));
	     break;
   
          case(OBJINST):
	     if (areastruct.editinplace && stack && (TOOBJINST(areagen)
			== areastruct.topinstance)) {
		/* If stack matches areastruct.stack, then don't draw */
		/* because it would be redundant.		 */
		pushlistptr alist = *stack, blist = areastruct.stack;
		while (alist && blist) {
		   if (alist->thisinst != blist->thisinst) break;
		   alist = alist->next;
		   blist = blist->next;
		}
		if ((!alist) || (!blist)) break;
	     }
             UDrawObject(TOOBJINST(areagen), level + 1, curcolor, stack);
	     break;
   
  	  case(LABEL): 
	     if (level == 0 || TOLABEL(areagen)->pin == False ||
			(TOLABEL(areagen)->justify & PINVISIBLE))
             UDrawString(TOLABEL(areagen), curcolor, theinstance);
	     else if (level == 1 && TOLABEL(areagen)->pin &&
			TOLABEL(areagen)->pin != INFO && areastruct.pinpointon)
		UDrawXDown(TOLABEL(areagen));
	     break;
       }
     }

     /* restore the color passed to the object, if different from current color */

     if ((defaultcolor != DOFORALL) && (passcolor != curcolor)) {
	XTopSetForeground(passcolor);
     }
   }
   UPopCTM();
   if (stack) pop_stack(stack);
}

/*----------------------------------------------------------------------*/
/* Recursively run through the current page and find any labels which	*/
/* are declared to be style LATEX.  If "checkonly" is present, we set	*/
/* it to TRUE or FALSE depending on whether or not LATEX labels have	*/
/* been encountered.  If NULL, then we write LATEX output appropriately	*/
/* to a file named with the page filename + suffix ".tex".		*/
/*----------------------------------------------------------------------*/

void UDoLatex(objinstptr theinstance, short level, FILE *f,
	float scale, int tx, int ty, Boolean *checkonly)
{
   XPoint	lpos, xlpos;
   XfPoint	xfpos;
   labelptr	thislabel;
   genericptr	*areagen;
   objectptr	theobject = theinstance->thisobject;
   char		*ltext;
   int		lrjust, tbjust;

   UPushCTM();
   if (level != 0)
       UPreMultCTM(DCTM, theinstance->position, theinstance->scale,
			theinstance->rotation);

   /* make parameter substitutions */
   psubstitute(theinstance);

   /* find all of the elements */
   
   for (areagen = theobject->plist; areagen < theobject->plist +
           theobject->parts; areagen++) {

      switch(ELEMENTTYPE(*areagen)) {
         case(OBJINST):
            UDoLatex(TOOBJINST(areagen), level + 1, f, scale, tx, ty, checkonly);
	    break;
   
	 case(LABEL): 
	    thislabel = TOLABEL(areagen);
	    if (level == 0 || thislabel->pin == False ||
			(thislabel->justify & PINVISIBLE))
		if (thislabel->justify & LATEXLABEL) {
		    if (checkonly) {
		       *checkonly = TRUE;
		       return;
		    }
		    else {
		       lpos.x = thislabel->position.x;
		       lpos.y = thislabel->position.y;
		       UTransformbyCTM(DCTM, &lpos, &xlpos, 1);
		       xlpos.x += tx;
		       xlpos.y += ty;
		       xfpos.x = (float)xlpos.x * scale;
		       xfpos.y = (float)xlpos.y * scale;
		       xfpos.x /= 72.0;
		       xfpos.y /= 72.0;
		       xfpos.x -= 1.0;
		       xfpos.y -= 1.0;
		       xfpos.x += 0.056;
		       xfpos.y += 0.056;
		       ltext = textprint(thislabel->string, theinstance);
		       tbjust = thislabel->justify & (NOTBOTTOM | TOP);
		       lrjust = thislabel->justify & (NOTLEFT | RIGHT);
		       fprintf(f, "\\putbox{%3.2fin}{%3.2fin}{",
				xfpos.x, xfpos.y);
		       if (lrjust == (NOTLEFT | RIGHT)) fprintf(f, "\\rightbox{");
		       else if (lrjust == NOTLEFT) fprintf(f, "\\centbox{");
		       if (tbjust == (NOTBOTTOM | TOP)) fprintf(f, "\\topbox{");
		       else if (tbjust == NOTBOTTOM) fprintf(f, "\\midbox{");
		       fprintf(f, "%s", ltext);
		       if (lrjust != NORMAL) fprintf(f, "}");
		       if (tbjust != NORMAL) fprintf(f, "}");
		       fprintf(f, "}%%\n");
		       free(ltext);
		    }
		}
	    break;
       }
   }
   UPopCTM();
}

/*----------------------------------------------------------------------*/
/*  Top level routine for writing LATEX output.				*/
/*----------------------------------------------------------------------*/

void TopDoLatex()
{
   FILE *f;
   float psscale;
   int tx, ty, width, height;
   XPoint origin;
   Boolean checklatex = FALSE;
   char filename[50];

   UDoLatex(areastruct.topinstance, 0, NULL, 1.0, 0, 0, &checklatex);

   if (checklatex == FALSE) return;	/* No LaTeX labels to write */

   sprintf(filename, "%s.tex", xobjs.pagelist[areastruct.page]->filename);
   f = fopen(filename, "w");

   fprintf(f, "%% XCircuit output \"%s\" for LaTeX input from %s.ps\n",
		filename, xobjs.pagelist[areastruct.page]->filename);
   fprintf(f, "\\def\\putbox#1#2#3{\\makebox[0in][l]{\\makebox[#1][l]{}"
		"\\raisebox{\\baselineskip}[0in][0in]"
		"{\\raisebox{#2}[0in][0in]{#3}}}}\n");
   fprintf(f, "\\def\\rightbox#1{\\makebox[0in][r]{#1}}\n");
   fprintf(f, "\\def\\centbox#1{\\makebox[0in]{#1}}\n");
   fprintf(f, "\\def\\topbox#1{\\raisebox{-\\baselineskip}[0in][0in]{#1}}\n");
   fprintf(f, "\\def\\midbox#1{\\raisebox{-0.5\\baselineskip}[0in][0in]{#1}}\n");
   fprintf(f, "\\begin{flushleft}\n");
   fprintf(f, "\\epsfig{file=%s.ps}\\\\\n",
		xobjs.pagelist[areastruct.page]->filename);

   psscale = getpsscale(xobjs.pagelist[areastruct.page]->outscale,
		areastruct.page);

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

   tx = (int)(72 / psscale) - origin.x,
   ty = (int)(72 / psscale) - origin.y;

   fprintf(f, "%% translate x=%d y=%d scale %3.2f\n", tx, ty, psscale);

   UPushCTM();		/* Save current state */
   UResetCTM(DCTM);	/* Set to identity matrix */
   UDoLatex(areastruct.topinstance, 0, f, psscale, tx, ty, NULL);
   UPopCTM();		/* Restore state */

   fprintf(f, "\\end{flushleft}\n");
   fclose(f);
}

