/*-------------------------------------------------------------------------*/
/* 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 <limits.h>

#ifndef _MSC_VER
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#endif

/*-------------------------------------------------------------------------*/
/* 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 XCWindowData *areawin;
extern Globaldata xobjs;
extern int number_colors;
extern colorindex *colorlist;

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

/*----------------------------------------------------------------------*/
/* Decompose an arc segment into one to four bezier curves according	*/
/* the approximation algorithm lifted from the paper by  L. Maisonobe	*/
/* (spaceroots.org).  This decomposition is done when an arc in a path	*/
/* is read from an (older) xcircuit file, or when an arc is a selected	*/
/* item when a path is created.	 Because arcs are decomposed when	*/
/* encountered, we assume that the arc is the last element of the path.	*/
/*----------------------------------------------------------------------*/

void decomposearc(pathptr thepath, XPoint *startpoint)
{
   float fnc, ang1, ang2;
   short ncurves, i;
   arcptr thearc;
   genericptr *pgen;
   splineptr *newspline;
   polyptr *newpoly;
   double nu1, nu2, lambda1, lambda2, alpha, tansq;
   XfPoint E1, E2, Ep1, Ep2;
   XPoint P1;
   Boolean reverse = FALSE;

   pgen = thepath->plist + thepath->parts - 1;
   if (ELEMENTTYPE(*pgen) != ARC) return;
   thearc = TOARC(pgen);

   if (thearc->radius < 0) {
      reverse = TRUE;
      thearc->radius = -thearc->radius;
   }

   fnc = (thearc->angle2 - thearc->angle1) / 90.0;
   ncurves = (short)fnc;
   if (fnc - (float)((int)fnc) > 0.01) ncurves++;

   thepath->parts--;  /* Forget the arc */

   for (i = 0; i < ncurves; i++) {
      if (reverse) {			/* arc path is reverse direction */
         if (i == 0)
	    ang1 = thearc->angle2;
         else
	    ang1 -= 90;

         if (i == ncurves - 1)
	    ang2 = thearc->angle1;
         else
	    ang2 = ang1 - 90;
      }
      else {				/* arc path is forward direction */
         if (i == 0)
	    ang1 = thearc->angle1;
         else
	    ang1 += 90;

         if (i == ncurves - 1)
	    ang2 = thearc->angle2;
         else
	    ang2 = ang1 + 90;
      }

      lambda1 = (double)ang1 * RADFAC;
      lambda2 = (double)ang2 * RADFAC;

      nu1 = atan2(sin(lambda1) / (double)thearc->yaxis,
		cos(lambda1) / (double)thearc->radius);
      nu2 = atan2(sin(lambda2) / (double)thearc->yaxis,
		cos(lambda2) / (double)thearc->radius);
      E1.x = (float)thearc->position.x +
		(float)thearc->radius * (float)cos(nu1);
      E1.y = (float)thearc->position.y +
		(float)thearc->yaxis * (float)sin(nu1);
      E2.x = (float)thearc->position.x +
		(float)thearc->radius * (float)cos(nu2);
      E2.y = (float)thearc->position.y +
		(float)thearc->yaxis * (float)sin(nu2);
      Ep1.x = -(float)thearc->radius * (float)sin(nu1);
      Ep1.y = (float)thearc->yaxis * (float)cos(nu1);
      Ep2.x = -(float)thearc->radius * (float)sin(nu2);
      Ep2.y = (float)thearc->yaxis * (float)cos(nu2);

      P1.x = (int)(roundf(E1.x));
      P1.y = (int)(roundf(E1.y));

      tansq = tan((nu2 - nu1) / 2.0);
      tansq *= tansq;
      alpha = sin(nu2 - nu1) * 0.33333 * (sqrt(4 + (3 * tansq)) - 1);

      /* If the arc 1st point is not the same as the previous path point,
       * then add a straight line to the 1st arc point (mimics PostScript
       * behavior).
       */

      if (startpoint && (i == 0)) {
         if ((startpoint->x != P1.x) || (startpoint->y != P1.y)) {
	    NEW_POLY(newpoly, thepath);
	    polydefaults(*newpoly, 2, startpoint->x, startpoint->y);
	    (*newpoly)->style = thearc->style;
	    (*newpoly)->color = thearc->color;
	    (*newpoly)->width = thearc->width;
            (*newpoly)->points[1].x = P1.x;
            (*newpoly)->points[1].y = P1.y;
	 }
      }

      NEW_SPLINE(newspline, thepath);
      splinedefaults(*newspline, 0, 0);
      (*newspline)->style = thearc->style;
      (*newspline)->color = thearc->color;
      (*newspline)->width = thearc->width;

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

      (*newspline)->ctrl[1].x = (int)(roundf(E1.x + alpha * Ep1.x));
      (*newspline)->ctrl[1].y = (int)(roundf(E1.y + alpha * Ep1.y));

      (*newspline)->ctrl[2].x = (int)(roundf(E2.x - alpha * Ep2.x));
      (*newspline)->ctrl[2].y = (int)(roundf(E2.y - alpha * Ep2.y));

      (*newspline)->ctrl[3].x = (int)(roundf(E2.x));
      (*newspline)->ctrl[3].y = (int)(roundf(E2.y));

      calcspline(*newspline);
   }

   /* Delete the arc */
   free_single((genericptr)thearc);
}

/*----------------------------------------------------------------------*/
/* 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, float *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 = 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;
}

/*----------------------------------------------------------------------*/
/* Convert a polygon to a Bezier curve path				*/
/* Curve must be selected and there must be only one selection.		*/
/*									*/
/* Note that this routine will draw inside the perimeter of a convex	*/
/* hull.  A routine that places spline endpoints on the polygon		*/
/* vertices will draw outside the perimeter of a convex hull.  An	*/
/* optimal algorithm presumably zeros the total area between the curve	*/
/* and the polygon (positive and negative), but I haven't worked out	*/
/* what that solution is.  The algorithm below seems good enough for	*/
/* most purposes.							*/
/*----------------------------------------------------------------------*/

void converttocurve()
{
   genericptr *ggen;
   splineptr *newspline;
   polyptr thispoly;
   pathptr *newpath;
   short *newselect;
   XPoint firstpoint, lastpoint, initpoint;
   int i, numpoints;

   if (areawin->selects != 1) return;

   thispoly = TOPOLY(topobject->plist + (*areawin->selectlist));
   if (ELEMENTTYPE(thispoly) != POLYGON) return;
   if (thispoly->number < 3) return;	/* Will not convert */

   standard_element_delete(ERASE);
   if ((thispoly->style & UNCLOSED) && (thispoly->number == 3)) {
      NEW_SPLINE(newspline, topobject);
      splinedefaults(*newspline, 0, 0);
      (*newspline)->ctrl[0] = thispoly->points[0];
      (*newspline)->ctrl[1] = thispoly->points[1];
      (*newspline)->ctrl[2] = thispoly->points[1];
      (*newspline)->ctrl[3] = thispoly->points[2];
   }
   else {
      numpoints = thispoly->number;

      /* If the polygon is closed but the first and last points	*/
      /* overlap, treat the last point as if it doesn't exist.	*/

      if (!(thispoly->style & UNCLOSED))
         if ((thispoly->points[0].x == thispoly->points[thispoly->number - 1].x)
		&& (thispoly->points[0].y ==
		thispoly->points[thispoly->number - 1].y))
	    numpoints--;

      NEW_PATH(newpath, topobject);
      pathdefaults(*newpath, 0, 0);
      (*newpath)->style = thispoly->style;

      if (!(thispoly->style & UNCLOSED)) {
	 lastpoint = thispoly->points[numpoints - 1];
	 initpoint.x = (lastpoint.x + thispoly->points[0].x) / 2;
	 initpoint.y = (lastpoint.y + thispoly->points[0].y) / 2;
	 firstpoint.x = (thispoly->points[0].x
		+ thispoly->points[1].x) / 2;
	 firstpoint.y = (thispoly->points[0].y
			+ thispoly->points[1].y) / 2;

         NEW_SPLINE(newspline, (*newpath));
	 splinedefaults(*newspline, 0, 0);
	 (*newspline)->ctrl[0] = initpoint;
	 (*newspline)->ctrl[1] = thispoly->points[0];
	 (*newspline)->ctrl[2] = thispoly->points[0];
	 (*newspline)->ctrl[3] = firstpoint;
         calcspline(*newspline);
      }
      else
         firstpoint = thispoly->points[0];

      for (i = 0; i < numpoints - ((!(thispoly->style & UNCLOSED)) ?
		2 : 3); i++) {
	 lastpoint.x = (thispoly->points[i + 1].x
		+ thispoly->points[i + 2].x) / 2;
	 lastpoint.y = (thispoly->points[i + 1].y
			+ thispoly->points[i + 2].y) / 2;

         NEW_SPLINE(newspline, (*newpath));
	 splinedefaults(*newspline, 0, 0);
	 (*newspline)->ctrl[0] = firstpoint;
	 (*newspline)->ctrl[1] = thispoly->points[i + 1];
	 (*newspline)->ctrl[2] = thispoly->points[i + 1];
	 (*newspline)->ctrl[3] = lastpoint;
	 firstpoint = lastpoint;
         calcspline(*newspline);
      }
      if (!(thispoly->style & UNCLOSED))
	 lastpoint = initpoint;
      else
	 lastpoint = thispoly->points[i + 2];

      NEW_SPLINE(newspline, (*newpath));
      splinedefaults(*newspline, 0, 0);
      (*newspline)->ctrl[0] = firstpoint;
      (*newspline)->ctrl[1] = thispoly->points[i + 1];
      (*newspline)->ctrl[2] = thispoly->points[i + 1];
      (*newspline)->ctrl[3] = lastpoint;
   }
   calcspline(*newspline);
   calcbbox(areawin->topinstance);
   setoptionmenu();
   drawarea(NULL, NULL, NULL);
}

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

short closepointdistance(polyptr curpoly, XPoint *cursloc, short *mindist)
{
   short curdist;
   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);
}

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

short closepoint(polyptr curpoly, XPoint *cursloc)
{
   short mindist;
   return closepointdistance(curpoly, cursloc, &mindist);
}

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

short closedistance(polyptr curpoly, XPoint *cursloc)
{
   short mindist;
   closepointdistance(curpoly, cursloc, &mindist);
   return mindist;
}

/*----------------------------------------------------------------------------*/
/* 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()
{
   long lval;

   /* check window-to-user space */

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

   /* check user-to-window space */

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

   lval = (long)((float)(topobject->bbox.lowerleft.x + topobject->bbox.width -
	areawin->pcorner.x) * areawin->vscale);
   if (lval != (long)((short)lval)) return -1;
   lval = (long)areawin->height - (long)((float)(topobject->bbox.lowerleft.y +
	topobject->bbox.height - areawin->pcorner.y) * areawin->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 / areawin->vscale + (float)areawin->pcorner.x;
  tmpy = (float)(areawin->height - yw) / areawin->vscale +
	(float)areawin->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 - areawin->pcorner.x) * areawin->vscale;
  tmpy = (float)areawin->height - (float)(upt.y - areawin->pcorner.y)
	* areawin->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				*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/* Return rotation relative to a specific CTM				*/
/*----------------------------------------------------------------------*/

float UGetCTMRotation(Matrix *ctm)
{
   float rads = (float)atan2((double)(ctm->d), (double)(ctm->a));
   return rads / RADFAC;
}

/*----------------------------------------------------------------------*/
/* Return rotation relative to the top level				*/
/* Note that UTopRotation() is also the rotation relative to the window	*/
/* since the top-level drawing page is always upright relative to the	*/
/* window.  Thus, there is no routine UTopDrawingRotation().		*/
/*----------------------------------------------------------------------*/

float UTopRotation()
{
   return UGetCTMRotation(DCTM);
}

/*----------------------------------------------------------------------*/
/* Return scale relative to a specific CTM				*/
/*----------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------*/
/* Return scale relative to window					*/
/*----------------------------------------------------------------------*/

float UTopScale()
{
   return UGetCTMScale(DCTM);
}

/*----------------------------------------------------------------------*/
/* Return scale multiplied by length					*/
/*----------------------------------------------------------------------*/

float UTopTransScale(float length)
{
   return (float)(length * UTopScale());
}

/*----------------------------------------------------------------------*/
/* Return scale relative to the top-level schematic (not the window)	*/
/*----------------------------------------------------------------------*/

float UTopDrawingScale()
{
   Matrix lctm, wctm;
   UCopyCTM(DCTM, &lctm);
   UResetCTM(&wctm);
   UMakeWCTM(&wctm);
   InvertCTM(&wctm);
   UPreMultCTMbyMat(&wctm, &lctm);
   return UGetCTMScale(&wctm);
}

/*----------------------------------------------------------------------*/
/* Return position offset relative to a specific CTM			*/
/*----------------------------------------------------------------------*/

void UGetCTMOffset(Matrix *ctm, int *offx, int *offy)
{
   if (offx) *offx = (int)ctm->c;
   if (offy) *offy = (int)ctm->f;
}

/*----------------------------------------------------------------------*/
/* Return position offset relative to top-level				*/
/*----------------------------------------------------------------------*/

void UTopOffset(int *offx, int *offy)
{
   UGetCTMOffset(DCTM, offx, offy);
}

/*----------------------------------------------------------------------*/
/* Return postion relative to the top-level schematic (not the window)	*/
/*----------------------------------------------------------------------*/

void UTopDrawingOffset(int *offx, int *offy)
{
   Matrix lctm, wctm;
   UCopyCTM(DCTM, &lctm);
   UResetCTM(&wctm);
   UMakeWCTM(&wctm);
   InvertCTM(&wctm);
   UPreMultCTMbyMat(&wctm, &lctm);
   UGetCTMOffset(&wctm, offx, offy);
}

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

XPoint UGetCursor()
{
   Window nullwin;
   int    nullint, xpos, ypos;
   u_int   nullui;
   XPoint newpos;

   /* Apparently this routine can get called before the display is valid */
   if ((areawin->area == NULL) || (dpy == NULL)) {
      newpos.x = newpos.y = 0;
      return newpos;
   }

#ifdef TCL_WRAPPER
   /* Don't use areawin->window;  if called from inside an object	*/
   /* (e.g., "here" in a Tcl expression), areawin->window will be	*/
   /* an off-screen pixmap, and cause a crash.				*/

   if (Tk_WindowId(areawin->area) == (Window)NULL) {
      newpos.x = newpos.y = 0;
      return newpos;
   }

#ifndef _MSC_VER
   XQueryPointer(dpy, Tk_WindowId(areawin->area), &nullwin, &nullwin,
	&nullint, &nullint, &xpos, &ypos, &nullui);
#else
   XQueryPointer_TkW32(dpy, Tk_WindowId(areawin->area), &nullwin, &nullwin,
	&nullint, &nullint, &xpos, &ypos, &nullui);
#endif
#else
   XQueryPointer(dpy, areawin->window, &nullwin, &nullwin, &nullint,
	&nullint, &xpos, &ypos, &nullui);
#endif

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

   return newpos;
}

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

XPoint UGetCursorPos()
{
   XPoint winpos, userpos;

   if (areawin->area == NULL) {
      winpos.x = winpos.y = 0;
   }
   else
      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 (areawin->snapto) {
      tmpx = (float)uvalue->x / xobjs.pagelist[areawin->page]->snapspace;
      if (tmpx > 0)
	 tmpix = (float)((int)(tmpx + 0.5));
      else
         tmpix = (float)((int)(tmpx - 0.5));

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

      tmpix *= xobjs.pagelist[areawin->page]->snapspace;
      tmpix += (tmpix > 0) ? 0.5 : -0.5;
      tmpiy *= xobjs.pagelist[areawin->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? */

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

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;

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

void UCopyCTM(Matrix *fctm, Matrix *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;

#ifdef HAVE_CAIRO
   if (tctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(tctm);
#endif /* HAVE_CAIRO */
}

/*-------------------------------------------------------------------------*/
/* 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 *= areawin->vscale;
   ctm->b *= areawin->vscale;
   ctm->c = (ctm->c - (float)areawin->pcorner.x) * areawin->vscale
	 + areawin->panx;

   ctm->d *= -areawin->vscale;
   ctm->e *= -areawin->vscale;
   ctm->f = (float)areawin->height + ((float)areawin->pcorner.y - ctm->f) *
	 areawin->vscale + areawin->pany;

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

void UMultCTM(Matrix *ctm, XPoint position, float scale, float 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;

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

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

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

short flipadjust(short anchor)
{
   short tmpanchor = anchor & (~FLIPINV);

   if (anchor & FLIPINV) {
      if (((DCTM)->a < -EPS) || (((DCTM)->a < EPS) && ((DCTM)->a > -EPS) &&
		(((DCTM)->d * (DCTM)->b) < 0))) {
         if ((tmpanchor & (RIGHT | NOTLEFT)) != NOTLEFT)
            tmpanchor ^= (RIGHT | NOTLEFT);

	 /* NOTE:  Justification does not change under flip invariance.	*/
      }
      if ((DCTM)->e > EPS) {
         if ((tmpanchor & (TOP | NOTBOTTOM)) != NOTBOTTOM)
            tmpanchor ^= (TOP | NOTBOTTOM);
      }
      UPreScaleCTM(DCTM);
   }
   return tmpanchor;
}

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

void UPreMultCTM(Matrix *ctm, XPoint position, float scale, float 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;

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

/*----------------------------------------------------------------------*/
/* 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;

#ifdef HAVE_CAIRO
   if (ctm == DCTM && areawin->redraw_ongoing)
      xc_cairo_set_matrix(ctm);
#endif /* HAVE_CAIRO */
}

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

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

   for (current = ipoints; current < ipoints + number; current++, ptptr++) {
      fx = ctm->a * (float)current->x + ctm->b * (float)current->y + ctm->c;
      fy = ctm->d * (float)current->x + ctm->e * (float)current->y + ctm->f;

      ptptr->x = (fx >= 0) ? (short)(fx + 0.5) : (short)(fx - 0.5);
      ptptr->y = (fy >= 0) ? (short)(fy + 0.5) : (short)(fy - 0.5);
   }
}

/*------------------------------------------------------------------------*/
/* (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;
   float fx, fy;

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

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

void UPopCTM()
{
   Matrixptr lastmatrix;

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

#ifdef HAVE_CAIRO
   if (areawin->area) {
      xc_cairo_set_matrix(lastmatrix);
   }
#endif /* HAVE_CAIRO */
}

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

void UPushCTM()
{
   Matrixptr nmatrix;

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

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

void UTransformPoints(XPoint *points, XPoint *newpoints, short number,
	XPoint atpt, float scale, float 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, float rotate)
{
   Matrix LCTM;

   UResetCTM(&LCTM);
   UPreMultCTM(&LCTM, atpt, scale, rotate);
   InvertCTM(&LCTM);
   UTransformbyCTM(&LCTM, points, newpoints, number);
}

/*----------------------------------------------------------------------*/
/* Adjust wire coords to force a wire to a horizontal or vertical	*/
/* position.								*/
/* "pospt" is the target position for the point of interest.		*/
/* "cycle" is the point number in the polygon of the point of interest.	*/
/* cycle == -1 is equivalent to the last point of the polygon.		*/
/* If "strict" is TRUE then single-segment wires are forced manhattan	*/
/* even if that means that the endpoint drifts from the target point.	*/
/* If "strict" is FALSE then single-segment wires will become non-	*/
/* manhattan so that the target point is reached.			*/
/* NOTE:  It might be preferable to add a segment to maintain a		*/
/* manhattan layout, except that we want to avoid merging nets		*/
/* together. . .							*/
/*----------------------------------------------------------------------*/

void manhattanize(XPoint *pospt, polyptr newpoly, short cycle, Boolean strict)
{
   XPoint *curpt, *bpt, *bbpt, *fpt, *ffpt;
   int deltax, deltay;

   if (newpoly->number == 1) return;	/* sanity check */

   if (cycle == -1 || cycle == newpoly->number - 1) {
      curpt = newpoly->points + newpoly->number - 1;
      bpt = newpoly->points + newpoly->number - 2;
      fpt = NULL;
      ffpt = NULL;
      if (newpoly->number > 2)
	 bbpt = newpoly->points + newpoly->number - 3;
      else
	 bbpt = NULL;
   }
   else if (cycle == 0) {
      curpt = newpoly->points;
      fpt = newpoly->points + 1;
      bpt = NULL;
      bbpt = NULL;
      if (newpoly->number > 2)
	 ffpt = newpoly->points + 2;
      else
	 ffpt = NULL;
   }
   else {
      curpt = newpoly->points + cycle;
      fpt = newpoly->points + cycle + 1;
      bpt = newpoly->points + cycle - 1;
      if (cycle > 1)
	 bbpt = newpoly->points + cycle - 2;
      else
	 bbpt = NULL;

      if (cycle < newpoly->number - 2)
	 ffpt = newpoly->points + cycle + 2;
      else
	 ffpt = NULL;
   }

   /* enforce constraints on point behind cycle position */

   if (bpt != NULL) {
      if (bbpt != NULL) {
         if (bpt->x == bbpt->x) bpt->y = pospt->y;
         if (bpt->y == bbpt->y) bpt->x = pospt->x;
      }
      else if (strict) {
         deltax = abs(bpt->x - pospt->x);
         deltay = abs(bpt->y - pospt->y);

         /* Only one segment---just make sure it's horizontal or vertical */
         if (deltay > deltax) pospt->x = bpt->x;
         else pospt->y = bpt->y;
      }
   }

   /* enforce constraints on point forward of cycle position */

   if (fpt != NULL) {
      if (ffpt != NULL) {
         if (fpt->x == ffpt->x) fpt->y = pospt->y;
         if (fpt->y == ffpt->y) fpt->x = pospt->x;
      }
      else if (strict) {
         deltax = abs(fpt->x - pospt->x);
         deltay = abs(fpt->y - pospt->y);

         /* Only one segment---just make sure it's horizontal or vertical */
         if (deltay > deltax) pospt->x = fpt->x;
         else pospt->y = fpt->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, int extend)
{
   XPoint points[4];

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

   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, callinst, NULL);
   points[0].x = points[1].x = (labox->anchor & NOTLEFT ?
	       (labox->anchor & RIGHT ? -tmpext.maxwidth :
		-tmpext.maxwidth / 2) : 0);
   points[2].x = points[3].x = points[0].x + tmpext.maxwidth;
   points[0].y = points[3].y = (labox->anchor & NOTBOTTOM ?
	       (labox->anchor & 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->anchor, &points[j].x, &points[j].y, 1);

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

/*----------------------------------------------------------------------*/
/* Calculate the bounding box of a graphic image			*/
/*----------------------------------------------------------------------*/

void graphicbbox(graphicptr gp, XPoint *npoints)
{
   XPoint points[4];
   int hw = xcImageGetWidth(gp->source) >> 1;
   int hh = xcImageGetHeight(gp->source) >> 1;

   points[1].x = points[2].x = hw;
   points[0].x = points[3].x = -hw;

   points[0].y = points[1].y = -hh;
   points[2].y = points[3].y = hh;

   UTransformPoints(points, npoints, 4, gp->position,
		gp->scale, gp->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, areawin->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, 0);

         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(GRAPHIC):
	 graphicbbox(TOGRAPHIC(bboxgen), npoints);
         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;
   Boolean didparamsubs = 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->anchor & PINVISIBLE)) {
	    hasschembbox = TRUE;
	    calcbboxsingle(gelem, thisinst, &pllx, &plly, &purx, &pury);
	    continue;
	 }
      }

      if (has_param(*gelem)) {
	 if (didparamsubs == FALSE) {
	    psubstitute(thisinst);
	    didparamsubs = TRUE;
	 }
	 calcbboxsingle(gelem, thisinst, &llx, &lly, &urx, &ury);
      }

      /* If we have a clipmask, the clipmask is used to calculate the	*/
      /* bounding box, not the element it is masking.			*/

      switch(ELEMENTTYPE(*gelem)) {
	 case POLYGON: case SPLINE: case ARC: case PATH:
	    if (TOPOLY(gelem)->style & CLIPMASK) gelem++;
	    break;
      }
   }

   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 == areawin->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(areawin->topinstance, (genericptr *)gelem);
   updatepagebounds(topobject);
}

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

void calcbboxselect()
{
   short *bsel;
   for (bsel = areawin->selectlist; bsel < areawin->selectlist +
		areawin->selects; bsel++)
      calcbboxvalues(areawin->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->anchor & PINVISIBLE)) {
	       goto nextgen;
	    }
	 }
	 calcbboxsingle(bboxgen, thisinst, &llx, &lly, &urx, &ury);

	 if (newelement == NULL)
	    switch(ELEMENTTYPE(*bboxgen)) {
	       case POLYGON: case SPLINE: case ARC: case PATH:
	          if (TOPOLY(bboxgen)->style & CLIPMASK)
		     bboxgen++;
		  break;
	    }
      }
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.  In pathological cases, such as a page	*/
   /* with only pin labels, the bounds may not have been changed from	*/
   /* their initial values.  If so, then don't touch the bounding box.	*/

   if ((llx <= urx) && (lly <= ury)) {
      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)areawin->width / ((float)width + 2 * DEFAULTGRIDSPACE);
   fitheight = (float)areawin->height / ((float)height + 2 * DEFAULTGRIDSPACE);

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

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

   /* Copy new position values to the current window */

   if ((areawin->topinstance != NULL) && (tobj == topobject)) {
      areawin->pcorner = tobj->pcorner;
      areawin->vscale = tobj->viewscale;
   }
}

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

void refresh(xcWidget bw, caddr_t clientdata, caddr_t calldata)
{
   areawin->redraw_needed = True;
   drawarea(NULL, NULL, NULL);
   if (areawin->scrollbarh)
      drawhbar(areawin->scrollbarh, NULL, NULL);
   if (areawin->scrollbarv)
      drawvbar(areawin->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 == COPY_MODE ||
	 eventmode == MOVE_MODE || eventmode == CATALOG_MODE ||
	eventmode == FONTCAT_MODE || eventmode == EFONTCAT_MODE ||
	eventmode == CATMOVE_MODE) {

      if (areawin->topinstance)
	 centerview(areawin->topinstance);
      areawin->lastbackground = NULL;
      renderbackground();
      refresh(NULL, NULL, NULL);
   }
}

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

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

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

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

   DrawLine(dpy, areawin->window, areawin->gc,
		newpt1.x, newpt1.y, newpt2.x, newpt2.y);
}
#endif /* !HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void UDrawLine(XPoint *pt1, XPoint *pt2)
{
   float tmpwidth = UTopTransScale(xobjs.pagelist[areawin->page]->wirewidth);

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   SetLineAttributes(dpy, areawin->gc, tmpwidth, LineSolid, CapRound, JoinBevel);
   UDrawSimpleLine(pt1, pt2);
}
#endif /* !HAVE_CAIRO */

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

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

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   user_to_window(*upt, &wpt);
   SetThinLineAttributes(dpy, areawin->gc, 0, LineSolid, CapButt, JoinMiter);

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

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

#ifndef HAVE_CAIRO
void UDrawXAt(XPoint *wpt)
{
   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   SetThinLineAttributes(dpy, areawin->gc, 0, LineSolid, CapButt, JoinMiter);
   DrawLine(dpy, areawin->window, areawin->gc, wpt->x - 3,
		wpt->y - 3, wpt->x + 3, wpt->y + 3);
   DrawLine(dpy, areawin->window, areawin->gc, wpt->x + 3,
		wpt->y - 3, wpt->x - 3, wpt->y + 3);
}
#endif /* !HAVE_CAIRO */

/*----------------------------------------------------------------------*/
/* 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 anchor, short *xpoint, short *ypoint, short dir)
{
   int delx, dely;

   dely = (anchor & NOTBOTTOM) ?
            ((anchor & TOP) ? -PADSPACE : 0) : PADSPACE;
   delx = (anchor & NOTLEFT) ?
            ((anchor & 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   tmpanchor, xbase;
   int     maxwidth;
   TextExtents tmpext;
   TextLinesInfo tlinfo;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

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

   UPushCTM();
   UPreMultCTM(DCTM, curlabel->position, curlabel->scale, curlabel->rotation);
   tmpanchor = flipadjust(curlabel->anchor);

   SetForeground(dpy, areawin->gc, AUXCOLOR);

   tlinfo.dostop = 0;
   tlinfo.tbreak = NULL;
   tlinfo.padding = NULL;

   tmpext = ULength(curlabel, areawin->topinstance, &tlinfo);
   maxwidth = tmpext.maxwidth;
   xbase = tmpext.base;
   tlinfo.dostop = tpos;
   tmpext = ULength(curlabel, areawin->topinstance, &tlinfo);

   points[0].x = (tmpanchor & NOTLEFT ?
        (tmpanchor & RIGHT ? -maxwidth : -maxwidth >> 1) : 0) + tmpext.width;
   if ((tmpanchor & JUSTIFYRIGHT) && tlinfo.padding)
      points[0].x += tlinfo.padding[tlinfo.line];
   else if ((tmpanchor & TEXTCENTERED) && tlinfo.padding)
      points[0].x += 0.5 * tlinfo.padding[tlinfo.line];
   points[0].y = (tmpanchor & NOTBOTTOM ?
        (tmpanchor & TOP ? -tmpext.ascent : -(tmpext.ascent + xbase) / 2)
	: -xbase) + tmpext.base - 3;
   points[1].x = points[0].x;
   points[1].y = points[0].y + TEXTHEIGHT + 6;

   if (curlabel->pin) {
      pinadjust(tmpanchor, &(points[0].x), &(points[0].y), 1);
      pinadjust(tmpanchor, &(points[1].x), &(points[1].y), 1);
   }
   if (tlinfo.padding != NULL) free(tlinfo.padding);

   /* 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, areawin->textpos);
   if ((areawin->textend > 0) && (areawin->textend < areawin->textpos)) {
      UDrawTextLine(curlabel, areawin->textend);
   }
}

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

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

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   SetForeground(dpy, areawin->gc, AUXCOLOR);

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

   SetThinLineAttributes(dpy, areawin->gc, 0, LineOnOffDash, CapButt, JoinMiter);
   DrawLine(dpy, areawin->window, areawin->gc, vpt.x, vpt.y, upt.x, upt.y);

   SetThinLineAttributes(dpy, areawin->gc, 0, LineSolid, CapButt, JoinMiter);
   DrawLine(dpy, areawin->window, areawin->gc, upt.x - 3, upt.y - 3,
		upt.x + 3, upt.y + 3);
   DrawLine(dpy, areawin->window, areawin->gc, upt.x + 3, upt.y - 3,
		upt.x - 3, upt.y + 3);

   SetForeground(dpy, areawin->gc, areawin->gccolor);
}
#endif /* HAVE_CAIRO */

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

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

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

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

   SetForeground(dpy, areawin->gc, AUXCOLOR);
   SetThinLineAttributes(dpy, areawin->gc, 0, LineSolid, CapRound, JoinBevel);
   DrawLine(dpy, areawin->window, areawin->gc, worig.x, worig.y,
		worig.x, wcorn.y);
   DrawLine(dpy, areawin->window, areawin->gc, worig.x, wcorn.y,
		wcorn.x, wcorn.y);
   DrawLine(dpy, areawin->window, areawin->gc, wcorn.x, wcorn.y,
		wcorn.x, worig.y);
   DrawLine(dpy, areawin->window, areawin->gc, wcorn.x, worig.y,
		worig.x, worig.y);
}
#endif /* HAVE_CAIRO */

/*----------------------------------------------------------------------*/
/* Get a box indicating the dimensions of the edit element that most	*/
/* closely reach the position "corner".					*/
/*----------------------------------------------------------------------*/

float UGetRescaleBox(XPoint *corner, XPoint *newpoints)
{
   genericptr	rgen;
   float	savescale, newscale;
   long		mindist, testdist, refdist;
   labelptr	rlab;
   graphicptr   rgraph;
   objinstptr	rinst;
   int		i;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      // return 0.0;
   }

   if (areawin->selects == 0) return 0.0;

   /* Use only the 1st selection as a reference to set the scale */

   rgen = SELTOGENERIC(areawin->selectlist);

   switch(ELEMENTTYPE(rgen)) {
      case LABEL:
	 rlab = (labelptr)rgen;
	 labelbbox(rlab, newpoints, areawin->topinstance);
	 newpoints[4] = newpoints[0];
	 mindist = LONG_MAX;
	 for (i = 0; i < 4; i++) {
	    testdist = finddist(&newpoints[i], &newpoints[i+1], corner);
	    if (testdist < mindist)
	       mindist = testdist;
	 }
	 refdist = wirelength(corner, &(rlab->position));
	 mindist = (int)sqrt(abs((double)mindist));
	 savescale = rlab->scale;
	 if (!test_insideness((int)corner->x, (int)corner->y, newpoints))
	    mindist = -mindist;
	 if (refdist == mindist) refdist = 1 - mindist;
	 if (rlab->scale < 0) rlab->scale = -rlab->scale;
	 newscale = fabs(rlab->scale * (float)refdist / (float)(refdist + mindist));
	 if (newscale > 10 * rlab->scale) newscale = 10 * rlab->scale;
	 if (areawin->snapto) {
	    float snapstep = 2 * (float)xobjs.pagelist[areawin->page]->gridspace
			/ (float)xobjs.pagelist[areawin->page]->snapspace;
	    newscale = (float)((int)(newscale * snapstep)) / snapstep;
	    if (newscale < (1.0 / snapstep)) newscale = (1.0 / snapstep);
	 }
	 else if (newscale < 0.1 * rlab->scale) newscale = 0.1 * rlab->scale;
	 rlab->scale = (savescale < 0) ? -newscale : newscale;
	 labelbbox(rlab, newpoints, areawin->topinstance);
	 rlab->scale = savescale;
	 if (savescale < 0) newscale = -newscale;
         break;

      case GRAPHIC:
	 rgraph = (graphicptr)rgen;
	 graphicbbox(rgraph, newpoints);
	 newpoints[4] = newpoints[0];
	 mindist = LONG_MAX;
	 for (i = 0; i < 4; i++) {
	    testdist = finddist(&newpoints[i], &newpoints[i+1], corner);
	    if (testdist < mindist)
	       mindist = testdist;
	 }
	 refdist = wirelength(corner, &(rgraph->position));
	 mindist = (int)sqrt(abs((double)mindist));
	 savescale = rgraph->scale;
	 if (!test_insideness((int)corner->x, (int)corner->y, newpoints))
	    mindist = -mindist;
	 if (refdist == mindist) refdist = 1 - mindist;  /* avoid inf result */
	 if (rgraph->scale < 0) rgraph->scale = -rgraph->scale;
	 newscale = fabs(rgraph->scale * (float)refdist / (float)(refdist + mindist));
	 if (newscale > 10 * rgraph->scale) newscale = 10 * rgraph->scale;
	 if (areawin->snapto) {
	    float snapstep = 2 * (float)xobjs.pagelist[areawin->page]->gridspace
			/ (float)xobjs.pagelist[areawin->page]->snapspace;
	    newscale = (float)((int)(newscale * snapstep)) / snapstep;
	    if (newscale < (1.0 / snapstep)) newscale = (1.0 / snapstep);
	 }
	 else if (newscale < 0.1 * rgraph->scale) newscale = 0.1 * rgraph->scale;
	 rgraph->scale = (savescale < 0) ? -newscale : newscale;
	 graphicbbox(rgraph, newpoints);
	 rgraph->scale = savescale;
	 if (savescale < 0) newscale = -newscale;
         break;

      case OBJINST:
	 rinst = (objinstptr)rgen;
	 objinstbbox(rinst, newpoints, 0);
	 newpoints[4] = newpoints[0];
	 mindist = LONG_MAX;
	 for (i = 0; i < 4; i++) {
	    testdist = finddist(&newpoints[i], &newpoints[i+1], corner);
	    if (testdist < mindist)
	       mindist = testdist;
	 }
	 refdist = wirelength(corner, &(rinst->position));
	 mindist = (int)sqrt(abs((double)mindist));
	 savescale = rinst->scale;
	 if (!test_insideness((int)corner->x, (int)corner->y, newpoints))
	    mindist = -mindist;
	 if (refdist == mindist) refdist = 1 - mindist;  /* avoid inf result */
	 if (rinst->scale < 0) rinst->scale = -rinst->scale;
	 newscale = fabs(rinst->scale * (float)refdist / (float)(refdist + mindist));
	 if (newscale > 10 * rinst->scale) newscale = 10 * rinst->scale;
	 if (areawin->snapto) {
	    float snapstep = 2 * (float)xobjs.pagelist[areawin->page]->gridspace
			/ (float)xobjs.pagelist[areawin->page]->snapspace;
	    newscale = (float)((int)(newscale * snapstep)) / snapstep;
	    if (newscale < (1.0 / snapstep)) newscale = (1.0 / snapstep);
	 }
	 else if (newscale < 0.1 * rinst->scale) newscale = 0.1 * rinst->scale;
	 rinst->scale = (savescale < 0) ? -newscale : newscale;
	 objinstbbox(rinst, newpoints, 0);
	 rinst->scale = savescale;
	 if (savescale < 0) newscale = -newscale;
         break;
   }

   return newscale;
}

/*----------------------------------------------------------------------*/
/* Draw a box indicating the dimensions of the edit element that most	*/
/* closely reach the position "corner".					*/
/*----------------------------------------------------------------------*/

#ifndef HAVE_CAIRO
void UDrawRescaleBox(XPoint *corner)
{
   XPoint origpoints[5], newpoints[5];

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   if (areawin->selects == 0)
      return;

   UGetRescaleBox(corner, newpoints);

   SetForeground(dpy, areawin->gc, AUXCOLOR);
   SetThinLineAttributes(dpy, areawin->gc, 0, LineSolid, CapRound, JoinBevel);

   UTransformbyCTM(DCTM, newpoints, origpoints, 4);
   strokepath(origpoints, 4, 0, 1);
}
#endif /* HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void UDrawBBox()
{
   XPoint	origin;
   XPoint	worig, wcorn, corner;
   objinstptr	bbinst = areawin->topinstance;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   if ((!areawin->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);

   SetForeground(dpy, areawin->gc, BBOXCOLOR);
   DrawLine(dpy, areawin->window, areawin->gc, worig.x, worig.y,
		worig.x, wcorn.y);
   DrawLine(dpy, areawin->window, areawin->gc, worig.x, wcorn.y,
		wcorn.x, wcorn.y);
   DrawLine(dpy, areawin->window, areawin->gc, wcorn.x, wcorn.y,
		wcorn.x, worig.y);
   DrawLine(dpy, areawin->window, areawin->gc, wcorn.x, worig.y,
		worig.x, worig.y);
}
#endif /* !HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void strokepath(XPoint *pathlist, short number, short style, float width)
{
   float        tmpwidth;

   tmpwidth = UTopTransScale(width);

   if (!(style & CLIPMASK) || (areawin->showclipmasks == TRUE) ||
		(areawin->clipped < 0)) {
      if (style & FILLED || (!(style & FILLED) && style & OPAQUE)) {
         if ((style & FILLSOLID) == FILLSOLID)
            SetFillStyle(dpy, areawin->gc, FillSolid);
         else if (!(style & FILLED)) {
            SetFillStyle(dpy, areawin->gc, FillOpaqueStippled);
	    SetStipple(dpy, areawin->gc, 7);
         }
         else {
	    if (style & OPAQUE)
               SetFillStyle(dpy, areawin->gc, FillOpaqueStippled);
	    else
               SetFillStyle(dpy, areawin->gc, FillStippled);
            SetStipple(dpy, areawin->gc, ((style & FILLSOLID) >> 5));
         }
         FillPolygon(dpy, areawin->window, areawin->gc, pathlist, number, Nonconvex,
		CoordModeOrigin);
         /* return to original state */
         SetFillStyle(dpy, areawin->gc, FillSolid);
      }
      if (!(style & NOBORDER)) {
         if (style & (DASHED | DOTTED)) {
	    /* Set up dots or dashes */
	    char dashstring[2];
	    /* prevent values greater than 255 from folding back into	*/
	    /* type char.  Limit to 63 (=255/4) to keep at least the	*/
	    /* dot/gap ratio to scale when 'gap' is at its maximum	*/
	    /* value.							*/
	    unsigned char dotsize = min(63, max(1, (short)tmpwidth));
	    if (style & DASHED)
	       dashstring[0] = 4 * dotsize;
	    else if (style & DOTTED)
	       dashstring[0] = dotsize;
	    dashstring[1] = 4 * dotsize;
            SetDashes(dpy, areawin->gc, 0, dashstring, 2);
            SetLineAttributes(dpy, areawin->gc, tmpwidth, LineOnOffDash,
		CapButt, (style & SQUARECAP) ? JoinMiter : JoinBevel);
         }
         else
            SetLineAttributes(dpy, areawin->gc, tmpwidth, LineSolid,
		(style & SQUARECAP) ? CapProjecting : CapRound,
		(style & SQUARECAP) ? JoinMiter : JoinBevel);

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

   if (style & CLIPMASK) {
      if (areawin->clipped == 0) {
         XSetForeground(dpy, areawin->cmgc, 0);
         XFillRectangle(dpy, areawin->clipmask, areawin->cmgc, 0, 0,
		areawin->width, areawin->height);
         XSetForeground(dpy, areawin->cmgc, 1);
         FillPolygon(dpy, areawin->clipmask, areawin->cmgc, pathlist,
		number, Nonconvex, CoordModeOrigin);
         XSetClipMask(dpy, areawin->gc, areawin->clipmask);
	 // printf("level 0: Clip to clipmask\n");	// Diagnostic
         areawin->clipped++;
      }
      else if ((areawin->clipped > 0) && (areawin->clipped & 1) == 0) {
	 if (areawin->pbuf == (Pixmap)NULL) {
	    areawin->pbuf = XCreatePixmap (dpy, areawin->window,
			areawin->width, areawin->height, 1);
	 }
	 XCopyArea(dpy, areawin->clipmask, areawin->pbuf, areawin->cmgc,
		0, 0, areawin->width, areawin->height, 0, 0);
         XSetForeground(dpy, areawin->cmgc, 0);
         XFillRectangle(dpy, areawin->clipmask, areawin->cmgc, 0, 0,
		areawin->width, areawin->height);
         XSetForeground(dpy, areawin->cmgc, 1);
         FillPolygon(dpy, areawin->clipmask, areawin->cmgc, pathlist,
		number, Nonconvex, CoordModeOrigin);
         XSetFunction(dpy, areawin->cmgc, GXand);
	 XCopyArea(dpy, areawin->pbuf, areawin->clipmask, areawin->cmgc,
		0, 0, areawin->width, areawin->height, 0, 0);
         XSetFunction(dpy, areawin->cmgc, GXcopy);
         XSetClipMask(dpy, areawin->gc, areawin->clipmask);
	 // printf("level X: Clip to clipmask\n");	// Diagnostic
         areawin->clipped++;
      }
   }
}
#endif /* !HAVE_CAIRO */

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

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

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

#ifndef HAVE_CAIRO
void UDrawSpline(splineptr thespline, float passwidth)
{
   XPoint       tmppoints[SPLINESEGS];
   float	scaledwidth;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   scaledwidth = thespline->width * passwidth;

   makesplinepath(thespline, tmppoints);
   strokepath(tmppoints, SPLINESEGS, thespline->style, scaledwidth);
}
#endif /* HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void UDrawPolygon(polyptr thepoly, float passwidth)
{
   XPoint *tmppoints = (pointlist) malloc(thepoly->number * sizeof(XPoint));
   float scaledwidth;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   scaledwidth = thepoly->width * passwidth;

   UTransformbyCTM(DCTM, thepoly->points, tmppoints, thepoly->number);
   strokepath(tmppoints, thepoly->number, thepoly->style, scaledwidth);
   free(tmppoints);
}
#endif /* HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void UDrawArc(arcptr thearc, float passwidth)
{
   XPoint  tmppoints[RSTEPS + 2];
   float scaledwidth;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   scaledwidth = thearc->width * passwidth;

   UfTransformbyCTM(DCTM, thearc->points, tmppoints, thearc->number);
   strokepath(tmppoints, thearc->number, thearc->style, scaledwidth);
}
#endif /* HAVE_CAIRO */

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

#ifndef HAVE_CAIRO
void UDrawPath(pathptr thepath, float passwidth)
{
   XPoint	*tmppoints = (pointlist) malloc(sizeof(XPoint));
   genericptr	*genpath;
   polyptr	thepoly;
   splineptr	thespline;
   int		pathsegs = 0, curseg = 0;
   float	scaledwidth;

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   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;
      }
   }
   scaledwidth = thepath->width * passwidth;

   strokepath(tmppoints, pathsegs, thepath->style, scaledwidth);
   free(tmppoints);
}
#endif /* HAVE_CAIRO */

/*----------------------------------------------------------------------*/
/* 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		*/
/*    passwidth is the inherited linewidth value passed to the object	*/
/*    stack contains graphics context information			*/
/*----------------------------------------------------------------------*/

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

   if (!areawin->redraw_ongoing) {
      areawin->redraw_needed = True;
      return;
   }

   /* Save the number of selections and set it to zero while we do the	*/
   /* object drawing.							*/

   savesel = areawin->selects;
   areawin->selects = 0;

   /* 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) {
      /* Save the current clipping mask and push it on the stack */
      if (areawin->clipped > 0) {
         push_stack((pushlistptr *)stack, theinstance, (char *)areawin->clipmask);
	 areawin->clipmask = XCreatePixmap(dpy, areawin->window, areawin->width,
		areawin->height, 1);
	 XCopyArea(dpy, (Pixmap)(*stack)->clientdata, areawin->clipmask, areawin->cmgc,
		0, 0, areawin->width, areawin->height, 0, 0);
      }
      else
         push_stack((pushlistptr *)stack, theinstance, (char *)NULL);
   }
   if (level != 0)
       UPreMultCTM(DCTM, theinstance->position, theinstance->scale,
			theinstance->rotation);

   if (theinstance->style & LINE_INVARIANT)
      passwidth /= fabs(theinstance->scale);

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

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

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

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

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

     /* draw all of the elements */

     tmpwidth = UTopTransScale(passwidth);
     SetLineAttributes(dpy, areawin->gc, tmpwidth, LineSolid, CapRound,
		JoinBevel);

     /* guard against plist being regenerated during a redraw by the	*/
     /* expression parameter mechanism (should that be prohibited?)	*/

     for (thispart = 0; thispart < theobject->parts; thispart++) {
       areagen = theobject->plist + thispart;
       if ((*areagen)->type & DRAW_HIDE) continue;

       if (defaultcolor != DOFORALL) {
	  Boolean clipcolor = FALSE;
	  switch(ELEMENTTYPE(*areagen)) {
 	     case(POLYGON): case(SPLINE): case(ARC): case(PATH):
	        if (TOPOLY(areagen)->style & CLIPMASK)
		   clipcolor = TRUE;
		break;
	  }
	  if (((*areagen)->color != curcolor) || (clipcolor == TRUE)) {
	     if (clipcolor)
		curcolor = CLIPMASKCOLOR;
	     else if ((*areagen)->color == DEFAULTCOLOR)
		curcolor = defaultcolor;
	     else
		curcolor = (*areagen)->color;

	     XcTopSetForeground(curcolor);
	  }
       }

       switch(ELEMENTTYPE(*areagen)) {
	  case(POLYGON):
	     if (level == 0 || !((TOPOLY(areagen))->style & BBOX))
                UDrawPolygon(TOPOLY(areagen), passwidth);
	     break;

	  case(SPLINE):
             UDrawSpline(TOSPLINE(areagen), passwidth);
	     break;

	  case(ARC):
             UDrawArc(TOARC(areagen), passwidth);
	     break;

	  case(PATH):
	     UDrawPath(TOPATH(areagen), passwidth);
	     break;

	  case(GRAPHIC):
	     UDrawGraphic(TOGRAPHIC(areagen));
	     break;

          case(OBJINST):
	     if (areawin->editinplace && stack && (TOOBJINST(areagen)
			== areawin->topinstance)) {
		/* If stack matches areawin->stack, then don't draw */
		/* because it would be redundant.		 */
		pushlistptr alist = *stack, blist = areawin->stack;
		while (alist && blist) {
		   if (alist->thisinst != blist->thisinst) break;
		   alist = alist->next;
		   blist = blist->next;
		}
		if ((!alist) || (!blist)) break;
	     }
	     if (areawin->clipped > 0) areawin->clipped += 2;
             UDrawObject(TOOBJINST(areagen), level + 1, curcolor, passwidth, stack);
	     if (areawin->clipped > 0) areawin->clipped -= 2;
	     break;

  	  case(LABEL):
	     if (level == 0 || TOLABEL(areagen)->pin == False)
                UDrawString(TOLABEL(areagen), curcolor, theinstance);
	     else if ((TOLABEL(areagen)->anchor & PINVISIBLE) && areawin->pinpointon)
                UDrawString(TOLABEL(areagen), curcolor, theinstance);
	     else if (TOLABEL(areagen)->anchor & PINVISIBLE)
                UDrawStringNoX(TOLABEL(areagen), curcolor, theinstance);
	     else if (level == 1 && TOLABEL(areagen)->pin &&
			TOLABEL(areagen)->pin != INFO && areawin->pinpointon)
		UDrawXDown(TOLABEL(areagen));
	     break;
       }
       if (areawin->clipped > 0) {
	  if ((areawin->clipped & 3) == 1) {
	     areawin->clipped++;
	  }
	  else if ((areawin->clipped & 3) == 2) {
	     areawin->clipped -= 2;
	     if ((!stack) || ((*stack)->clientdata == (char *)NULL)) {
	        XSetClipMask(dpy, areawin->gc, None);
	 	// printf("1: Clear clipmask\n");	// Diagnostic
	     }
	     else {
	        XSetClipMask(dpy, areawin->gc, (Pixmap)((*stack)->clientdata));
	 	// printf("1: Set to pushed clipmask\n");	// Diagnostic
	     }
	  }
       }
     }

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

     if ((defaultcolor != DOFORALL) && (passcolor != curcolor)) {
	XTopSetForeground(passcolor);
     }
     if (areawin->clipped > 0) {
	if ((areawin->clipped & 3) != 3) {
	   if ((!stack) || ((*stack)->clientdata == (char *)NULL)) {
	      XSetClipMask(dpy, areawin->gc, None);
	      // printf("2: Clear clipmask\n");	// Diagnostic
	   }
	   else {
	      XSetClipMask(dpy, areawin->gc, (Pixmap)((*stack)->clientdata));
	      // printf("2: Set to pushed clipmask\n");	// Diagnostic
	   }
	}
	areawin->clipped &= ~3;
     }
   }

   /* restore the selection list (if any) */
   areawin->selects = savesel;
   UPopCTM();
   if (stack) {
      if ((*stack) != NULL) {
	 if ((*stack)->clientdata != (char *)NULL) {
	    XFreePixmap(dpy, areawin->clipmask);
            areawin->clipmask = (Pixmap)(*stack)->clientdata;
	    // printf("3: Restore clipmask\n");	// Diagnostic
	 }
      }
      pop_stack(stack);
   }
}
#endif /* HAVE_CAIRO */

/*----------------------------------------------------------------------*/
/* 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, float scale2, int tx, int ty, Boolean *checkonly)
{
   XPoint	lpos, xlpos;
   XfPoint	xfpos;
   labelptr	thislabel;
   genericptr	*areagen;
   objectptr	theobject = theinstance->thisobject;
   char		*ltext;
   int		lranchor, tbanchor;

   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, scale2, tx, ty, checkonly);
	    break;

	 case(LABEL):
	    thislabel = TOLABEL(areagen);
	    if (level == 0 || thislabel->pin == False ||
			(thislabel->anchor & PINVISIBLE))
		if (thislabel->anchor & 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;
		       xfpos.x /= scale2;
		       xfpos.y /= scale2;
		       ltext = textprinttex(thislabel->string, theinstance);
		       tbanchor = thislabel->anchor & (NOTBOTTOM | TOP);
		       lranchor = thislabel->anchor & (NOTLEFT | RIGHT);

		       /* The 1.2 factor accounts for the difference between	 */
		       /* Xcircuit's label scale of "1" and LaTeX's "normalsize" */

		       fprintf(f, "   \\putbox{%3.2fin}{%3.2fin}{%3.2f}{",
				xfpos.x, xfpos.y, 1.2 * thislabel->scale);
		       if (thislabel->rotation != 0)
			  fprintf(f, "\\rotatebox{-%d}{", thislabel->rotation);
		       if (lranchor == (NOTLEFT | RIGHT)) fprintf(f, "\\rightbox{");
		       else if (lranchor == NOTLEFT) fprintf(f, "\\centbox{");
		       if (tbanchor == (NOTBOTTOM | TOP)) fprintf(f, "\\topbox{");
		       else if (tbanchor == NOTBOTTOM) fprintf(f, "\\midbox{");
		       fprintf(f, "%s", ltext);
		       if (lranchor != NORMAL) fprintf(f, "}");
		       if (tbanchor != NORMAL) fprintf(f, "}");
		       if (thislabel->rotation != 0) fprintf(f, "}");
		       fprintf(f, "}%%\n");
		       free(ltext);
		    }
		}
	    break;
       }
   }
   UPopCTM();
}

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

void TopDoLatex()
{
   FILE *f;
   float psscale, outscale;
   int tx, ty, width, height;
   polyptr framebox;
   XPoint origin;
   Boolean checklatex = FALSE;
   char filename[100], extension[10], *dotptr;

   UDoLatex(areawin->topinstance, 0, NULL, 1.0, 1.0, 0, 0, &checklatex);

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

   /* Handle cases where the file might have a ".eps" extension.	*/
   /* Thanks to Graham Sheward for pointing this out.			*/

   /* Modified file path routines:                                      */
   /* Solved problems with incomplete paths, NULL file pointers,        */
   /* added tilde and variable expansion by Agustín Campeny, April 2020 */

   sprintf(filename, "%s", xobjs.pagelist[areawin->page]->filename);

   xc_tilde_expand(filename, 100);
   while(xc_variable_expand(filename, 100));

   dotptr = strrchr(filename, '.');
   sprintf(extension, "%s", dotptr);
   filename[dotptr - filename] = '\0';
   sprintf(filename, "%s.tex", filename);

   f = fopen(filename, "w");

   if (!f) {
     Wprintf("Couldn't save .tex file. Check file path");
     return;
   }

   *dotptr = '\0';

   fprintf(f, "%% XCircuit output \"%s.tex\" for LaTeX input from %s%s\n",
		filename, filename, extension);
   fprintf(f, "\\def\\putbox#1#2#3#4{\\makebox[0in][l]{\\makebox[#1][l]{}"
		"\\raisebox{\\baselineskip}[0in][0in]"
		"{\\raisebox{#2}[0in][0in]{\\scalebox{#3}{#4}}}}}\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{-0.60\\baselineskip}[0in][0in]{#1}}\n");
   fprintf(f, "\\def\\midbox#1{\\raisebox{-0.20\\baselineskip}[0in][0in]{#1}}\n");

   /* Modified to use \scalebox and \parbox by Alex Tercete, June 2008 */

   // fprintf(f, "\\begin{center}\n");

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

   width = toplevelwidth(areawin->topinstance, &origin.x);
   height = toplevelheight(areawin->topinstance, &origin.y);

   /* Added 10/19/10:  If there is a specified bounding box, let it	*/
   /* determine the figure origin;  otherwise, the labels will be	*/
   /* mismatched to the bounding box.					*/

   if ((framebox = checkforbbox(topobject)) != NULL) {
      int i, maxx, maxy;

      origin.x = maxx = framebox->points[0].x;
      origin.y = maxy = framebox->points[0].y;
      for (i = 1; i < framebox->number; i++) {
	 if (framebox->points[i].x < origin.x) origin.x = framebox->points[i].x;
	 if (framebox->points[i].x > maxx) maxx = framebox->points[i].x;
	 if (framebox->points[i].y < origin.y) origin.y = framebox->points[i].y;
	 if (framebox->points[i].y > maxy) maxy = framebox->points[i].y;
      }
      origin.x -= ((width - maxx + origin.x) / 2);
      origin.y -= ((height - maxy + origin.y) / 2);
   }

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

   fprintf(f, "   \\scalebox{%g}{\n", outscale);
   fprintf(f, "   \\normalsize\n");
   fprintf(f, "   \\parbox{%gin}{\n", (((float)width * psscale) / 72.0) / outscale);
   fprintf(f, "   \\includegraphics[scale=%g]{%s%s}\\\\\n", 1.0 / outscale,
    			filename, extension);
   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(areawin->topinstance, 0, f, psscale, outscale, tx, ty, NULL);
   UPopCTM();		/* Restore state */

   fprintf(f, "   } %% close \'parbox\'\n");
   fprintf(f, "   } %% close \'scalebox\'\n");
   fprintf(f, "   \\vspace{-\\baselineskip} %% this is not"
		" necessary, but looks better\n");
   // fprintf(f, "\\end{center}\n");
   fclose(f);

   Wprintf("Wrote auxiliary file %s.tex", filename);
}

