/* 
 * xvps.c - Postscript dialog box, file output functions
 *
 * callable functions:
 *
 *   CreatePSD(geom)           -  creates the psW window.  Doesn't map it.
 *   PSDialog()                -  maps psW
 *   PSCheckEvent(event)       -  called by event handler
 *   PSSaveParams(str,int)     -  tells PSDialog what to do when 'Ok' clicked
 *   PSResize()                -  called whenever ePic changes size
 */

/*
 * Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                       The University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu
 */


#include "xv.h"

#define PSWIDE 431
#define PSHIGH 350
#define PMAX   200    /* size of square that a 'page' has to fit into */

#define PS_NBUTTS 6
#define PS_BOK    0
#define PS_BCANC  1
#define PS_BCENT  2
#define PS_BMAX   3
#define PS_BPOSX  4
#define PS_BPOSY  5

/* paperRB indicies */
#define PSZ_NORM  0
#define PSZ_A4    1
#define PSZ_B5    2
#define PSZ_LEGAL 3
#define PSZ_BSIZE 4
#define PSZ_4BY5  5
#define PSZ_35MM  6

/* orientRB indicies */
#define ORNT_PORT 0
#define ORNT_LAND 1

#define BUTTH 24

#define IN2CM 2.54

#define PIX2INCH 72.0   /* # of pixels per inch, at 100% scaling */

#ifdef __STDC__
static void drawPSD(int, int, int, int);
static void drawPosStr();
static void drawSizeStr();
static void drawResStr();
static void drawPage();
static void clickPSD(int, int);
static void clickPage(int, int);
static void doCmd(int);
static void changedScale(void);
static void setScale(void);
static void changedPaper(void);
static void setPaper(void);
static void drawIRect(int);
static void centerImage(void);
static void maxImage(void);
static void moveImage(double, double);
static void writePS(void);
static int  rle_encode(byte *, byte *, int);
static void psColorImage();
static void psColorMap(FILE *fp, int, int, byte *, byte *, byte *);
static void psRleCmapImage();
static void epsPreview();
static int  writeBWStip(FILE *, byte *, char *, int, int);
#else
static void drawPSD(), drawPosStr(), drawSizeStr(), drawResStr();
static void drawPage(), clickPSD(), clickPage(), doCmd(), changedScale();
static void setScale(), changedPaper(), setPaper(), drawIRect();
static void centerImage(), maxImage(), moveImage(), writePS();
static void psColorImage(), psColorMap(), epsPreview();
static void psRleCmapImage();
static int  rle_encode(), writeBWStip();
#endif


/* local variables */
static Window pageF;
static DIAL   xsDial, ysDial;
static RBUTT *orientRB, *paperRB;
static CBUTT  lockCB;
static BUTT   psbut[PS_NBUTTS];
static double sz_inx, sz_iny;     /* image size, in inches */
static double pos_inx, pos_iny;   /* top-left offset of image, in inches */
static int    dpix, dpiy;         /* # of image pixels per inch */
static int    tracking=0;         /* used in changedScale */
static int    posxType, posyType;

/* sizes of pages in inches */
static double paperSize[7][2] = { { 8.500, 11.000},   /* US NORMAL */
				  { 8.267, 11.811},   /* A4 */
				  { 7.283, 10.630},   /* B5 */
				  { 8.500, 14.000},   /* US LEGAL */
				  {11.000, 17.000},   /* B-size */
				  { 3.875,  4.875},   /* 4 by 5 */
				  { 0.945,  1.417}};  /* 35mm (24x36) */

/* size of l+r margin and t+b margin.  image is centered */
static double margins[7][2] = { { 1.000, 1.000},   /* US NORMAL */
				{ 1.000, 1.000},   /* A4 */
				{ 1.000, 1.000},   /* B5 */
				{ 1.000, 1.000},   /* US LEGAL */
				{ 1.000, 1.000},   /* B-size */
				{ 0.275, 0.275},   /* 4 by 5 */
				{ 0.078, 0.078}};  /* 35mm (24x36) */


static double psizex, psizey;   /* current paper size, in inches */
static double in2pix;           /* inch to pixels in 'pageF' */
static XRectangle pageRect;     /* bounding rect of page, in screen coords */

static char *filename;          /* filename to save to */
static int   colorType;         /* value of 'Colors' rbutt in dir box */
static int   firsttime=1;       /* first time PSDialog being opened ? */


/***************************************************/
void CreatePSD(geom)
char *geom;
{
  psW = CreateWindow("xv postscript", geom, PSWIDE, PSHIGH, infofg, infobg);
  if (!psW) FatalError("can't create postscript window!");
  StoreDeleteWindowProp(psW);
  
  pageF = XCreateSimpleWindow(theDisp, psW, 20,30, PMAX+1,PMAX+1,
			      1,infofg,infobg);
  if (!pageF) FatalError("couldn't create frame windows");

  XSetWindowBackgroundPixmap(theDisp, pageF, grayTile);

  XSelectInput(theDisp, pageF, ExposureMask | ButtonPressMask);
  XSelectInput(theDisp, psW,   ExposureMask | ButtonPressMask | KeyPressMask);

  CBCreate(&encapsCB, psW, 240, 7, "preview", infofg, infobg);
  CBCreate(&pscompCB, psW, 331, 7, "compress", infofg, infobg);

  DCreate(&xsDial, psW, 240, 30, 80, 100, 10, 800, 100, 5, 
	  infofg, infobg, "Width", "%");
  DCreate(&ysDial, psW, 331, 30, 80, 100, 10, 800, 100, 5, 
	  infofg, infobg, "Height", "%");
  xsDial.drawobj = changedScale;
  ysDial.drawobj = changedScale;

  CBCreate(&lockCB, psW, 319, 136, "", infofg, infobg);
  lockCB.val = 1;

  orientRB = RBCreate(NULL, psW, 36, 240+18, "Portrait", infofg, infobg);
  RBCreate(orientRB, psW, 36+80, 240+18, "Landscape", infofg, infobg);

  paperRB = RBCreate(NULL, psW,36, 240+18+36, "8.5\"x11\"", infofg, infobg);
  RBCreate(paperRB, psW, 36+80,    240+18+36, "A4",         infofg, infobg);
  RBCreate(paperRB, psW, 36+154,   240+18+36, "B5",         infofg, infobg);
  RBCreate(paperRB, psW, 36,       240+36+36, "8.5\"x14\"", infofg, infobg);
  RBCreate(paperRB, psW, 36+80,    240+36+36, "11\"x17\"",  infofg, infobg);
  RBCreate(paperRB, psW, 36,       240+54+36, "4\"x5\"",    infofg, infobg);
  RBCreate(paperRB, psW, 36+80,    240+54+36, "35mm slide", infofg, infobg);

  BTCreate(&psbut[PS_BOK], psW, PSWIDE-160, PSHIGH-10-BUTTH, 60, BUTTH, 
	   "Ok", infofg, infobg);

  BTCreate(&psbut[PS_BCANC], psW, PSWIDE-80, PSHIGH-10-BUTTH, 60, BUTTH, 
	   "Cancel", infofg, infobg);

  BTCreate(&psbut[PS_BCENT], psW, 240, 153, 88, BUTTH, 
	   "Center", infofg, infobg);

  BTCreate(&psbut[PS_BMAX], psW, 240+88+5, 153, 87, BUTTH, 
	   "Maxpect", infofg, infobg);

  BTCreate(&psbut[PS_BPOSX], psW, 256-14, 190+13-8, 8,8, "", infofg, infobg);
  BTCreate(&psbut[PS_BPOSY], psW, 256-14, 190+26-8, 8,8, "", infofg, infobg);

  posxType = posyType = 0;
  pos_inx = 1.0;  pos_iny = 1.0;   /* temporary bootstrapping... */
  setPaper();
  setScale();

  XMapSubwindows(theDisp, psW);
}
  

/***************************************************/
void PSDialog(vis)
int vis;
{
  if (vis) {
    setScale();
    if (firsttime) centerImage();
    firsttime = 0;
    CenterMapWindow(psW, psbut[PS_BOK].x + psbut[PS_BOK].w/2,
		    psbut[PS_BOK].y + psbut[PS_BOK].h/2, PSWIDE, PSHIGH);
  }
  else     XUnmapWindow(theDisp, psW);
  psUp = vis;
}


/***************************************************/
int PSCheckEvent(xev)
XEvent *xev;
{
  /* check event to see if it's for one of our subwindows.  If it is,
     deal accordingly, and return '1'.  Otherwise, return '0' */

  int rv;
  rv = 1;

  if (!psUp) return 0;

  if (xev->type == Expose) {
    int x,y,w,h;
    XExposeEvent *e = (XExposeEvent *) xev;
    x = e->x;  y = e->y;  w = e->width;  h = e->height;

    /* throw away excess expose events for 'dumb' windows */
    if (e->count > 0 && 
	(e->window == xsDial.win || e->window == ysDial.win ||
	 e->window == pageF)) {}

    else if (e->window == psW)         drawPSD(x, y, w, h);
    else if (e->window == xsDial.win)  DRedraw(&xsDial);
    else if (e->window == ysDial.win)  DRedraw(&ysDial);
    else if (e->window == pageF)       drawPage();
    else rv = 0;
  }

  else if (xev->type == ButtonPress) {
    XButtonEvent *e = (XButtonEvent *) xev;
    int x,y;
    x = e->x;  y = e->y;

    if (e->button == Button1) {
      if      (e->window == psW)   clickPSD(x,y);
      else if (e->window == pageF) clickPage(x,y);

      else if (e->window == xsDial.win || e->window == ysDial.win) {
	if (e->window == xsDial.win) {
	  tracking = 1;
	  DTrack(&xsDial, x,y);
	  tracking = 0;
	}

	else if (e->window == ysDial.win) {
	  tracking = 2;
	  DTrack(&ysDial, x,y);
	  tracking = 0;
	}
      }
      else rv = 0;
    }  /* button1 */
    else rv = 0;
  }  /* button press */


  else if (xev->type == KeyPress) {
    XKeyEvent *e = (XKeyEvent *) xev;
    char buf[128];  KeySym ks;
    int stlen;
	
    stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL);
    buf[stlen] = '\0';

    if (e->window == psW) {
      double dx, dy;
      int movekey;

      movekey = 0;  dx = dy = 0.0;

      if      (ks==XK_Left  || ks==XK_KP_4) { dx = -0.001;  movekey = 1; }
      else if (ks==XK_Right || ks==XK_KP_6) { dx =  0.001;  movekey = 1; }
      else if (ks==XK_Up    || ks==XK_KP_8) { dy = -0.001;  movekey = 1; }
      else if (ks==XK_Down  || ks==XK_KP_2) { dy =  0.001;  movekey = 1; }

      else if (stlen) {
	if (buf[0] == '\r' || buf[0] == '\n') { /* enter */
	  FakeButtonPress(&psbut[PS_BOK]);
	}
	else if (buf[0] == '\033') {            /* ESC */
	  FakeButtonPress(&psbut[PS_BCANC]);
	}
      }

      if (movekey) {
	if (e->state & ShiftMask) { dx *= 10.0;  dy *= 10.0; }
	moveImage(pos_inx+dx, pos_iny+dy);
      }
    }
    else rv = 0;
  }
  else rv = 0;

  if (rv==0 && (xev->type == ButtonPress || xev->type == KeyPress)) {
    XBell(theDisp, 50);
    rv = 1;   /* eat it */
  }

  return rv;
}


/***************************************************/
void PSSaveParams(fname, col)
char *fname;
int col;
{
  filename = fname;
  colorType = col;
}


/***************************************************/
void PSResize()
{
  if (!savenormCB.val) changedScale();
}






/***************************************************/
static void drawPSD(x,y,w,h)
int x,y,w,h;
{
  char *title = "Save PostScript File...";
  int  i,cx;
  XRectangle xr;

  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);

  RBRedraw(orientRB,-1);
  RBRedraw(paperRB,-1);

  for (i=0; i<PS_NBUTTS; i++) BTRedraw(&psbut[i]);

  CBRedraw(&encapsCB);
  if (colorType != F_BWDITHER) CBRedraw(&pscompCB);
  CBRedraw(&lockCB);

  ULineString(psW, "Orientation", orientRB->x-16, orientRB->y-3-DESCENT);
  ULineString(psW, "Paper Size",  paperRB->x-16,   paperRB->y-3-DESCENT);

  /* draw 'lock' arrows */
  cx = 240 + 40;  /* center of xsDial */
  XDrawLine(theDisp, psW, theGC, lockCB.x, lockCB.y+5,  cx+2, lockCB.y+5);
  XDrawLine(theDisp, psW, theGC, cx+2, lockCB.y+5, cx+2, lockCB.y-2);

  XDrawLine(theDisp, psW, theGC, lockCB.x, lockCB.y+9,  cx-2, lockCB.y+9);
  XDrawLine(theDisp, psW, theGC, cx-2, lockCB.y+9, cx-2, lockCB.y-2);

  XDrawLine(theDisp, psW, theGC, cx-2-3, lockCB.y-2+3, cx, lockCB.y-2-2);
  XDrawLine(theDisp, psW, theGC, cx+2+3, lockCB.y-2+3, cx, lockCB.y-2-2);

  cx = 330 + 40;  /* center of ysDial */
  XDrawLine(theDisp, psW, theGC, lockCB.x+15, lockCB.y+5,  cx-2, lockCB.y+5);
  XDrawLine(theDisp, psW, theGC, cx-2, lockCB.y+5, cx-2, lockCB.y-2);

  XDrawLine(theDisp, psW, theGC, lockCB.x+15, lockCB.y+9,  cx+2, lockCB.y+9);
  XDrawLine(theDisp, psW, theGC, cx+2, lockCB.y+9, cx+2, lockCB.y-2);

  XDrawLine(theDisp, psW, theGC, cx-2-3, lockCB.y-2+3, cx, lockCB.y-2-2);
  XDrawLine(theDisp, psW, theGC, cx+2+3, lockCB.y-2+3, cx, lockCB.y-2-2);

  XDrawString(theDisp, psW, theGC, 10, 19, title, strlen(title));

  ULineString(psW, "Position:",   240, 190);
  drawPosStr();
  ULineString(psW, "Size:",       240, 190+45);
  drawSizeStr();
  ULineString(psW, "Resolution:", 240, 190+90);
  drawResStr();


  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
static void drawPosStr()
{
  int x,y;
  double cmx, cmy, inx, iny;
  char   str[64], str1[64], *xst, *yst;

  x = 256;  y = 190 + 13;

  switch (posxType) {
  case 0:  xst = "Left: ";  inx = pos_inx;                      break;
  case 1:  xst = "Right:";  inx = psizex - (pos_inx + sz_inx);  break;
  case 2:  xst = "X Mid:";  inx = pos_inx + sz_inx/2;           break;
  }

  switch (posyType) {
  case 0:  yst = "Top:  ";  iny = pos_iny;                      break;
  case 1:  yst = "Bot:  ";  iny = psizey - (pos_iny + sz_iny);  break;
  case 2:  yst = "Y Mid:";  iny = pos_iny + sz_iny/2;           break;
  }

  cmx = inx * IN2CM;
  cmy = iny * IN2CM;

  sprintf(str,  "%s %.3f\" (%.2fcm)       ", xst, inx, cmx);
  sprintf(str1, "%s %.3f\" (%.2fcm)       ", yst, iny, cmy);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);

  XSetFont(theDisp, theGC, monofont);
  XDrawImageString(theDisp, psW, theGC, x, y,    str,  strlen(str));
  XDrawImageString(theDisp, psW, theGC, x, y+13, str1, strlen(str1));
  XSetFont(theDisp, theGC, mfont);
}

  
/***************************************************/
static void drawSizeStr()
{
  int x,y;
  double cmx, cmy;
  char   str[64], str1[64];

  x = 256;  y = 190+13+45;

  cmx = sz_inx * IN2CM;
  cmy = sz_iny * IN2CM;

  sprintf(str,  "%.3f\" x %.3f\"        ", sz_inx, sz_iny);
  sprintf(str1, "%.2fcm x %.2fcm        ", cmx, cmy);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);
  XSetFont(theDisp, theGC, monofont);

  XDrawImageString(theDisp, psW, theGC, x, y,    str, strlen(str));
  XDrawImageString(theDisp, psW, theGC, x, y+13, str1, strlen(str1));
  XSetFont(theDisp, theGC, mfont);
}

  
/***************************************************/
static void drawResStr()
{
  int x,y;
  char   str[64];

  x = 256;  y = 190 + 13 + 90;

  sprintf(str,  "%ddpi x %ddpi        ", dpix, dpiy);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);
  XSetFont(theDisp, theGC, monofont);
  XDrawImageString(theDisp, psW, theGC, x, y, str, strlen(str));
  XSetFont(theDisp, theGC, mfont);
}

  
  
  
/***************************************************/
static void drawPage()
{
  /* draw page */
  XSetForeground(theDisp, theGC, infobg);
  XFillRectangle(theDisp, pageF, theGC, pageRect.x+1, pageRect.y+1,
		 pageRect.width-1, pageRect.height-1);

  XSetForeground(theDisp, theGC, infofg);
  XDrawRectangle(theDisp, pageF, theGC, pageRect.x, pageRect.y,
		 pageRect.width, pageRect.height);

  drawIRect(1);
}


/***************************************************/
static void clickPSD(x,y)
int x,y;
{
  int i;
  BUTT *bp;

  /* check BUTTs */

  for (i=0; i<PS_NBUTTS; i++) {
    bp = &psbut[i];
    if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
  }

  if (i<PS_NBUTTS) {  /* found one */
    if (BTTrack(bp)) doCmd(i);
  }

  /* check RBUTTs */

  else if ((i=RBClick(orientRB,x,y)) >= 0) {
    if (RBTrack(orientRB, i)) changedPaper();
  }

  else if ((i=RBClick(paperRB,x,y)) >= 0) {
    if (RBTrack(paperRB, i)) changedPaper();
  }

  /* check CBUTTs */

  else if (CBClick(&lockCB,x,y)) {
    if (CBTrack(&lockCB) && lockCB.val) {  /* turned on lock */
      DSetVal(&ysDial, xsDial.val);        /* copy xsDial.val to ysDial */
      changedScale();
    }
  }

  else if (CBClick(&encapsCB,x,y)) CBTrack(&encapsCB);
  else if (CBClick(&pscompCB,x,y)) CBTrack(&pscompCB);
}



/***************************************************/
static void clickPage(mx,my)
int mx,my;
{
  Window       rW,cW;
  int          rx,ry,x,y;
  unsigned int mask;
  double       offx, offy, newx, newy;

  /* compute offset (in inches) between 'drag point' and 
     the top-left corner of the image */

  offx = ((mx - pageRect.x) / in2pix) - pos_inx;
  offy = ((my - pageRect.y) / in2pix) - pos_iny;

  /* if clicked outside of image rectangle, ignore */
  if (offx<0.0 || offy < 0.0 || offx>=sz_inx || offy >= sz_iny) return;

  while (1) {
    if (XQueryPointer(theDisp,pageF,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
      if (!(mask & Button1Mask)) break;    /* button released */

      /* compute new pos_inx, pos_iny based on x,y coords */
      newx = ((x-pageRect.x) / in2pix) - offx;
      newy = ((y-pageRect.y) / in2pix) - offy;

      moveImage(newx, newy);
    }
  }
}



/***************************************************/
static void doCmd(cmd)
int cmd;
{
  switch (cmd) {
  case PS_BOK:    writePS();    PSDialog(0);  break;

  case PS_BCANC:  PSDialog(0);  break;

  case PS_BCENT:  drawIRect(0);
                  centerImage();
                  drawIRect(1);
                  drawPosStr();
                  break;

  case PS_BMAX:   drawIRect(0);
                  maxImage();
                  drawIRect(1);
                  drawPosStr();
                  drawSizeStr();
                  drawResStr();
                  break;

  case PS_BPOSX:  posxType = (posxType + 1) % 3;
                  drawPosStr();
                  break;

  case PS_BPOSY:  posyType = (posyType + 1) % 3;
                  drawPosStr();
                  break;

  default:        break;
  }
}


/***************************************************/
static void changedScale()
{
  double oldx,oldy;

  drawIRect(0);

  if (lockCB.val) {
    if      (tracking == 1) DSetVal(&ysDial, xsDial.val);
    else if (tracking == 2) DSetVal(&xsDial, ysDial.val);
  }

  oldx = pos_inx;  oldy = pos_iny;
  setScale();

  drawIRect(1);

  if (pos_inx != oldx || pos_iny != oldy ||
      posxType != 0 || posyType != 0) drawPosStr();
  drawSizeStr();
  drawResStr();
  XFlush(theDisp);
}


/***************************************************/
static void setScale()
{
  double hsx, hsy;

  int w,h;

  if (savenormCB.val) { w = cWIDE;  h = cHIGH; }
                 else { w = eWIDE;  h = eHIGH; }

  sz_inx = (double) w / PIX2INCH * (xsDial.val / 100.0);  
  sz_iny = (double) h / PIX2INCH * (ysDial.val / 100.0);  

  /* round to integer .001ths of an inch */
  sz_inx = floor(sz_inx * 1000.0 + 0.5) / 1000.0;
  sz_iny = floor(sz_iny * 1000.0 + 0.5) / 1000.0;

  dpix = (int) (PIX2INCH / (xsDial.val / 100.0));  
  dpiy = (int) (PIX2INCH / (ysDial.val / 100.0));  

  /* make sure 'center' of image is still on page */
  hsx = sz_inx/2;  hsy = sz_iny/2;
  RANGE(pos_inx, -hsx, psizex-hsx);
  RANGE(pos_iny, -hsy, psizey-hsy);

  /* round to integer .001ths of an inch */
  pos_inx = floor(pos_inx * 1000.0 + 0.5) / 1000.0;
  pos_iny = floor(pos_iny * 1000.0 + 0.5) / 1000.0;

}


/***************************************************/
static void changedPaper()
{
  setPaper();
  XClearWindow(theDisp, pageF);
  centerImage();
  drawPosStr();
  drawPage();
}


/***************************************************/
static void setPaper()
{
  double tmp;

  psizex = paperSize[RBWhich(paperRB)][0];
  psizey = paperSize[RBWhich(paperRB)][1];

  in2pix = (double) PMAX / psizey;

  if (RBWhich(orientRB)==ORNT_LAND) {
    tmp = psizex;  psizex = psizey;  psizey = tmp;
  }

  pageRect.x = (int) ((PMAX/2) - ((psizex/2.0) * in2pix));
  pageRect.y = (int) ((PMAX/2) - ((psizey/2.0) * in2pix));
  pageRect.width  = (int) (psizex * in2pix);
  pageRect.height = (int) (psizey * in2pix);
}


/***************************************************/
static void drawIRect(draw)
int draw;
{
  int x,y,w,h;
  XRectangle xr;

  x = pageRect.x + (int) (pos_inx * in2pix);
  y = pageRect.y + (int) (pos_iny * in2pix);
  w = sz_inx * in2pix;
  h = sz_iny * in2pix;

  xr.x = pageRect.x + 1;
  xr.y = pageRect.y + 1;
  xr.width  = pageRect.width - 1;
  xr.height = pageRect.height - 1;

  if (draw) XSetForeground(theDisp, theGC, infofg);
       else XSetForeground(theDisp, theGC, infobg);

  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);
  XDrawRectangle(theDisp, pageF, theGC, x, y, w, h);
  XDrawLine(theDisp, pageF, theGC, x, y, x+w, y+h);
  XDrawLine(theDisp, pageF, theGC, x, y+h, x+w, y);
  XSetClipMask(theDisp, theGC, None);
}



/***************************************************/
static void centerImage()
{
  pos_inx = psizex/2 - sz_inx/2;
  pos_iny = psizey/2 - sz_iny/2;

  /* round to integer .001ths of an inch */
  pos_inx = floor(pos_inx * 1000.0 + 0.5) / 1000.0;
  pos_iny = floor(pos_iny * 1000.0 + 0.5) / 1000.0;
}


/***************************************************/
static void maxImage()
{
  double scx, scy;
  int w,h;

  if (savenormCB.val) { w = cWIDE;  h = cHIGH; }
                 else { w = eWIDE;  h = eHIGH; }

  sz_inx = psizex - margins[RBWhich(paperRB)][0];
  sz_iny = psizey - margins[RBWhich(paperRB)][1];

  /* choose the smaller scaling factor */
  scx = sz_inx / w;
  scy = sz_iny / h;

  if (scx < scy) { sz_iny = w * scx; }
            else { sz_inx = h * scy; }

  DSetVal(&xsDial, (int) ((100 * (sz_inx * PIX2INCH) / w) + .5));
  DSetVal(&ysDial, xsDial.val);

  sz_inx = (double) w / PIX2INCH * (xsDial.val / 100.0);  
  sz_iny = (double) h / PIX2INCH * (ysDial.val / 100.0);  

  /* round to integer .001ths of an inch */
  sz_inx = floor(sz_inx * 1000.0 + 0.5) / 1000.0;
  sz_iny = floor(sz_iny * 1000.0 + 0.5) / 1000.0;

  dpix = (int) (PIX2INCH / (xsDial.val / 100.0));  
  dpiy = (int) (PIX2INCH / (ysDial.val / 100.0));  

  pos_inx = psizex/2 - sz_inx/2;
  pos_iny = psizey/2 - sz_iny/2;

  /* round to integer .001ths of an inch */
  pos_inx = floor(pos_inx * 1000.0 + 0.5) / 1000.0;
  pos_iny = floor(pos_iny * 1000.0 + 0.5) / 1000.0;
}


/***************************************************/
static void moveImage(newx,newy)
double newx, newy;
{
  double hsx, hsy;

  hsx = sz_inx/2;  hsy = sz_iny/2;

  /* round to integer .001ths of an inch */
  newx = floor(newx * 1000.0 + 0.5) / 1000.0;
  newy = floor(newy * 1000.0 + 0.5) / 1000.0;

  /* keep center of image within page limits */
  RANGE(newx, -hsx, psizex-hsx);
  RANGE(newy, -hsy, psizey-hsy);

  if (newx != pos_inx || newy != pos_iny) {  /* moved */
    drawIRect(0);
    pos_inx = newx;
    pos_iny = newy;
    drawIRect(1);
    drawPosStr();
  }
}


/***************************************************/
static void writePS()
{
  FILE *fp;
  int   i, j, q, err, rpix, gpix, bpix, nc;
  int   iw, ih, ox, oy, slen, lwidth, bits, colorps, w, h;
  byte *inpix, *bwpic, *rmap, *gmap, *bmap;
  
  if (savenormCB.val) { inpix = cpic;  w = cWIDE;  h = cHIGH; }
                 else { inpix = epic;  w = eWIDE;  h = eHIGH; }

  bwpic = NULL;  rmap = r;  gmap = g;  bmap = b;
  nc = numcols;

  fp = OpenOutFile(filename);
  if (!fp) return;

  WaitCursor();
  

  bwpic = HandleBWandReduced(colorType, &nc, &rmap, &gmap, &bmap);
  if (bwpic) inpix = bwpic;

    

  /* printed image will have size iw,ih (in picas) */
  iw = (int) (sz_inx * 72.0 + 0.5);
  ih = (int) (sz_iny * 72.0 + 0.5);   

  /* compute offset to bottom-left of image (in picas) */
  ox = (int) (pos_inx * 72.0 + 0.5);
  oy = (int) ((psizey - (pos_iny + sz_iny)) * 72.0 + 0.5);


  /*** write PostScript header ***/


  fprintf(fp,"%%!PS-Adobe-2.0 EPSF-2.0\n");
  fprintf(fp,"%%%%Title: %s\n",filename);
  fprintf(fp,"%%%%Creator: XV %s  -  by John Bradley\n",REVDATE);

  if (RBWhich(orientRB)==ORNT_LAND)   /* Landscape mode */
    fprintf(fp,"%%%%BoundingBox: %d %d %d %d\n", 
	    (int) (pos_iny * 72.0 + 0.5),
	    (int) (pos_inx * 72.0 + 0.5),
	    (int) (pos_iny * 72.0 + 0.5) + iw,
	    (int) (pos_inx * 72.0 + 0.5) + ih);
  else 
    fprintf(fp,"%%%%BoundingBox: %d %d %d %d\n", ox, oy, ox+iw, oy+ih);

  fprintf(fp,"%%%%Pages: 1\n");
  fprintf(fp,"%%%%DocumentFonts:\n");
  fprintf(fp,"%%%%EndComments\n");


  switch (colorType) {
  case F_FULLCOLOR:
  case F_REDUCED:   slen = w*3;      bits = 8;  colorps = 1;  break;
  case F_GREYSCALE: slen = w;        bits = 8;  colorps = 0;  break;
  case F_BWDITHER:  slen = (w+7)/8;  bits = 1;  colorps = 0;  break;
  default:  FatalError("unknown colorType in writePS()");   break;
  }
  
  if (encapsCB.val) epsPreview(fp, inpix, colorType, w, h);

  fprintf(fp,"%%%%EndProlog\n\n");

  fprintf(fp,"%%%%Page: 1 1\n\n");

  fprintf(fp,"%% remember original state\n");
  fprintf(fp,"/origstate save def\n\n");

  fprintf(fp,"%% build a temporary dictionary\n");
  fprintf(fp,"20 dict begin\n\n");

  if (colorType == F_BWDITHER || !pscompCB.val) {
    fprintf(fp,"%% define string to hold a scanline's worth of data\n");
    fprintf(fp,"/pix %d string def\n\n", slen);
  }

  if (RBWhich(orientRB)==ORNT_LAND) {   /* Landscape mode */
    fprintf(fp,"%% print in landscape mode\n");
    fprintf(fp,"90 rotate 0 %d translate\n\n",(int) (-psizey*72.0));
  }
    
  if (RBWhich(paperRB) == PSZ_4BY5 ||
      RBWhich(paperRB) == PSZ_35MM) {
    fprintf(fp,"%% we're going to a 4x5 or a 35mm film recorder.\n");
    fprintf(fp,"%% clear page to black to avoid registration problems\n");
    fprintf(fp,"newpath\n");
    fprintf(fp,"  0 0 moveto\n");
    fprintf(fp,"  0 %d rlineto\n", (int) (psizey * 72.0));
    fprintf(fp,"  %d 0 rlineto\n", (int) (psizex * 72.0));
    fprintf(fp,"  0 %d rlineto\n", (int) (-psizey * 72.0));
    fprintf(fp,"  closepath\n");
    fprintf(fp,"  0 setgray\n");
    fprintf(fp,"  fill\n\n");
  }


  fprintf(fp,"%% lower left corner\n");
  fprintf(fp,"%d %d translate\n\n",ox,oy);

  fprintf(fp,"%% size of image (on paper, in 1/72inch coords)\n");
  fprintf(fp,"%d %d scale\n\n",iw,ih);

  if (colorType == F_BWDITHER) {   /* 1-bit dither code uses 'image' */
    fprintf(fp,"%% dimensions of data\n");
    fprintf(fp,"%d %d %d\n\n",w,h,bits);

    fprintf(fp,"%% mapping matrix\n");
    fprintf(fp,"[%d 0 0 %d 0 %d]\n\n", w, -h, h);

    fprintf(fp,"{currentfile pix readhexstring pop}\n");
    fprintf(fp,"image\n");

    /* write the actual image data */

    err = writeBWStip(fp, inpix, "", w, h);
  }

  else {      /* all other formats */
    byte *rleline;
    unsigned long outbytes = 0;

    /* if we're using color, make sure 'colorimage' is defined */
    if (colorps) psColorImage(fp);

    if (pscompCB.val) {   /* write colormap and rle-colormapped image funct */
      psColorMap(fp, colorps, nc, rmap, gmap, bmap);
      psRleCmapImage(fp);
    }

    fprintf(fp,"%d %d %d\t\t\t%% dimensions of data\n",w,h,bits);
    fprintf(fp,"[%d 0 0 %d 0 %d]\t\t%% mapping matrix\n", w, -h, h);

    if (pscompCB.val) fprintf(fp,"rlecmapimage\n");
    else {
      fprintf(fp,"{currentfile pix readhexstring pop}\n");
      if (colorps) fprintf(fp,"false 3 colorimage\n");
              else fprintf(fp,"image\n");
    }

    /* dump the image data to the file */
    err = 0;

    if (pscompCB.val) {
      rleline  = (byte *) malloc(w * 2);  /* much worse than possible */
      if (!rleline) FatalError("unable to malloc rleline in writePS()\n");
    }

    for (i=0; i<h && err != EOF; i++) {
      int rlen;
      lwidth = 0;
      putc('\n',fp);

      if ((i&0x1f) == 0) WaitCursor();

      if (pscompCB.val) {  /* write rle-encoded colormapped image data */
	rlen = rle_encode(inpix, rleline, w);
	inpix += w;
	outbytes += rlen;

	for (j=0; j<rlen && err != EOF; j++) {
	  err = fprintf(fp,"%02x", rleline[j]);
	  lwidth += 2;

	  if (lwidth>70) { putc('\n',fp); lwidth = 0; }
	}
      }
      else {  /* write non-rle raw (gray/rgb) image data */
	for (j=0; j<w && err != EOF; j++) {
	  rpix = rmap[*inpix];
	  gpix = gmap[*inpix];
	  bpix = bmap[*inpix];
	  
	  if (colorps) { 
	    err = fprintf(fp,"%02x%02x%02x",rpix,gpix,bpix);
	    lwidth+=6;
	  }
      
	  else {  /* greyscale */
	    q = (rpix*21 + gpix*32 + bpix*11) / 64;
	    err = fprintf(fp,"%02x", q);
	    lwidth+=2;
	  }

	  if (lwidth>70) { putc('\n',fp); lwidth = 0; }
	  inpix++;
	}
      }
    }

    if (pscompCB.val) {
      free(rleline);
      fprintf(fp,"\n\n");
      fprintf(fp,"%%\n");
      fprintf(fp,"%% Compression made this file %.2lf%% %s\n",
	      100.0 * ((double) outbytes) / 
	      ((double) eWIDE * eHIGH * ((colorps) ? 3 : 1)),
	      "of the uncompressed size.");
      fprintf(fp,"%%\n");
    }
  }


  fprintf(fp,"\n\nshowpage\n\n");

  fprintf(fp,"%% stop using temporary dictionary\n");
  fprintf(fp,"end\n\n");

  fprintf(fp,"%% restore original state\n");
  fprintf(fp,"origstate restore\n\n");
  fprintf(fp,"%%%%Trailer\n");

  if (bwpic) free(bwpic);

  if (CloseOutFile(fp, filename, (err==EOF)) == 0) {
    DirBox(0);
    LoadCurrentDirectory();   /* wrote file: rescan directory */
  }

  SetCursors(-1);
}


/**********************************************/
static int rle_encode(scanline, rleline, wide)
     byte *scanline, *rleline;
     int wide;
{
  /* generates a rle-compressed version of the scan line.
   * rle is encoded as such:
   *    <count> <value>                      # 'run' of count+1 equal pixels
   *    <count | 0x80> <count+1 data bytes>  # count+1 non-equal pixels
   *
   * count can range between 0 and 127
   *
   * returns length of the rleline vector
   */
  
  int  i, j, blocklen, isrun, rlen;
  byte block[256], pix;
  
  blocklen = isrun = rlen = 0;

  for (i=0; i<wide; i++) {
    /* there are 5 possible states:
     *   0: block empty.
     *   1: block not empty, block is  a run, current pix == previous pix
     *   2: block not empty, block is  a run, current pix != previous pix
     *   3: block not empty, block not a run, current pix == previous pix
     *   4: block not empty, block not a run, current pix != previous pix
     */

    pix = scanline[i];

    if (!blocklen) {                    /* case 0:  empty */
      block[blocklen++] = pix;
      isrun = 1;
    }

    else if (isrun) {
      if (pix == block[blocklen-1]) {   /* case 1:  isrun, prev==cur */
	block[blocklen++] = pix;
      }
      else {                            /* case 2:  isrun, prev!=cur */
	if (blocklen>1) {               /*   we have a run block to flush */
	  rleline[rlen++] = blocklen-1;
	  rleline[rlen++] = block[0];
	  block[0] = pix;               /*   start new run block with pix */
	  blocklen = 1;
	}
	else {
	  isrun = 0;                    /*   blocklen<=1, turn into non-run */
	  block[blocklen++] = pix;
	}
      }
    }
	
    else {   /* not a run */
      if (pix == block[blocklen-1]) {   /* case 3:  non-run, prev==cur */
	if (blocklen>1) {               /*  have a non-run block to flush */
	  rleline[rlen++] = (blocklen-1) | 0x80;
	  for (j=0; j<blocklen; j++)
	    rleline[rlen++] = block[j];

	  block[0] = pix;               /*  start new run block with pix */
	  blocklen = isrun = 1;
	}
	else {
	  isrun = 1;                    /*  blocklen<=1 turn into a run */
	  block[blocklen++] = pix;
	}
      }
      else {                            /* case 4:  non-run, prev!=cur */
	block[blocklen++] = pix;
      }
    }

    if (blocklen == 128) {   /* max block length.  flush */
      if (isrun) {
	rleline[rlen++] = blocklen-1;
	rleline[rlen++] = block[0];
      }

      else {
	rleline[rlen++] = (blocklen-1) | 0x80;
	for (j=0; j<blocklen; j++) 
	  rleline[rlen++] = block[j];
      }

      blocklen = 0;
    }
  }

  /* flush last block */
  if (isrun) {
    rleline[rlen++] = blocklen-1;
    rleline[rlen++] = block[0];
  }

  else {
    rleline[rlen++] = (blocklen-1) | 0x80;
    for (j=0; j<blocklen; j++) 
      rleline[rlen++] = block[j];
  }

  return rlen;
}
	  
	    
/**********************************************/
static void psColorImage(fp)
FILE *fp;
{
  /* spits out code that checks if the PostScript device in question
     knows about the 'colorimage' operator.  If it doesn't, it defines
     'colorimage' in terms of image (ie, generates a greyscale image from
     RGB data) */


  fprintf(fp,"%% define 'colorimage' if it isn't defined\n");
  fprintf(fp,"%%   ('colortogray' and 'mergeprocs' come from xwd2ps\n");
  fprintf(fp,"%%     via xgrab)\n");
  fprintf(fp,"/colorimage where   %% do we know about 'colorimage'?\n");
  fprintf(fp,"  { pop }           %% yes: pop off the 'dict' returned\n");
  fprintf(fp,"  {                 %% no:  define one\n");
  fprintf(fp,"    /colortogray {  %% define an RGB->I function\n");
  fprintf(fp,"      /rgbdata exch store    %% call input 'rgbdata'\n");
  fprintf(fp,"      rgbdata length 3 idiv\n");
  fprintf(fp,"      /npixls exch store\n");
  fprintf(fp,"      /rgbindx 0 store\n");
  fprintf(fp,"      /grays npixls string store  %% str to hold the result\n");
  fprintf(fp,"      0 1 npixls 1 sub {\n");
  fprintf(fp,"        grays exch\n");
  fprintf(fp,"        rgbdata rgbindx       get 20 mul    %% Red\n");
  fprintf(fp,"        rgbdata rgbindx 1 add get 32 mul    %% Green\n");
  fprintf(fp,"        rgbdata rgbindx 2 add get 12 mul    %% Blue\n");
  fprintf(fp,"        add add 64 idiv      %% I = .5G + .31R + .18B\n");
  fprintf(fp,"        put\n");
  fprintf(fp,"        /rgbindx rgbindx 3 add store\n");
  fprintf(fp,"      } for\n");
  fprintf(fp,"      grays\n");
  fprintf(fp,"    } bind def\n\n");

  fprintf(fp,"    %% Utility procedure for colorimage operator.\n");
  fprintf(fp,"    %% This procedure takes two procedures off the\n");
  fprintf(fp,"    %% stack and merges them into a single procedure.\n\n");
  
  fprintf(fp,"    /mergeprocs { %% def\n");
  fprintf(fp,"      dup length\n");
  fprintf(fp,"      3 -1 roll\n");
  fprintf(fp,"      dup\n");
  fprintf(fp,"      length\n");
  fprintf(fp,"      dup\n");
  fprintf(fp,"      5 1 roll\n");
  fprintf(fp,"      3 -1 roll\n");
  fprintf(fp,"      add\n");
  fprintf(fp,"      array cvx\n");
  fprintf(fp,"      dup\n");
  fprintf(fp,"      3 -1 roll\n");
  fprintf(fp,"      0 exch\n");
  fprintf(fp,"      putinterval\n");
  fprintf(fp,"      dup\n");
  fprintf(fp,"      4 2 roll\n");
  fprintf(fp,"      putinterval\n");
  fprintf(fp,"    } bind def\n\n");

  fprintf(fp,"    /colorimage { %% def\n");
  fprintf(fp,"      pop pop     %% remove 'false 3' operands\n");
  fprintf(fp,"      {colortogray} mergeprocs\n");
  fprintf(fp,"      image\n");
  fprintf(fp,"    } bind def\n");
  fprintf(fp,"  } ifelse          %% end of 'false' case\n");
  fprintf(fp,"\n\n\n");
}


/**********************************************/
static void psColorMap(fp, color, nc, rmap, gmap, bmap)
     FILE *fp;
     int color, nc;
     byte *rmap, *gmap, *bmap;
{
  /* spits out code for the colormap of the following image
     if !color, it spits out a mono-ized graymap */

  int i;

  fprintf(fp,"%% define the colormap\n");
  fprintf(fp,"/cmap %d string def\n\n\n", nc * ((color) ? 3 : 1));

  fprintf(fp,"%% load up the colormap\n");
  fprintf(fp,"currentfile cmap readhexstring\n");

  for (i=0; i<nc; i++) {
    if (color) fprintf(fp,"%02x%02x%02x ", rmap[i],gmap[i],bmap[i]);
    else fprintf(fp,"%02x ", MONO(rmap[i],gmap[i],bmap[i]));
    
    if ((i%10) == 9) fprintf(fp,"\n");
  }
  if (i%10) fprintf(fp,"\n");
  fprintf(fp,"pop pop   %% lose return values from readhexstring\n\n\n");
		 
}


/**********************************************/
static void psRleCmapImage(fp, color)
FILE *fp;
{
  /* spits out code that defines the 'rlecmapimage' operator */

  fprintf(fp,"%% rlecmapimage expects to have 'w h bits matrix' on stack\n");
  fprintf(fp,"/rlecmapimage {\n");
  fprintf(fp,"  /buffer 1 string def\n");
  fprintf(fp,"  /rgbval 3 string def\n");
  fprintf(fp,"  /block  384 string def\n\n");

  fprintf(fp,"  %% proc to read a block from file, and return RGB data\n");
  fprintf(fp,"  { currentfile buffer readhexstring pop\n");
  fprintf(fp,"    /bcount exch 0 get store\n");
  fprintf(fp,"    bcount 128 ge\n");
  fprintf(fp,"    {  %% it's a non-run block\n");
  fprintf(fp,"      0 1 bcount 128 sub\n");
  fprintf(fp,"      { currentfile buffer readhexstring pop pop\n\n");

  if (color) {
    fprintf(fp,"        %% look up value in color map\n");
    fprintf(fp,"%s/rgbval cmap buffer 0 get 3 mul 3 getinterval store\n\n",
	    "        ");
    fprintf(fp,"        %% and put it in position i*3 in block\n");
    fprintf(fp,"        block exch 3 mul rgbval putinterval\n");
    fprintf(fp,"      } for\n");
    fprintf(fp,"      block  0  bcount 127 sub 3 mul  getinterval\n");
    fprintf(fp,"    }\n\n");
  }
  else {
    fprintf(fp,"        %% look up value in gray map\n");
    fprintf(fp,"%s/rgbval cmap buffer 0 get 1 getinterval store\n\n",
	    "        ");
    fprintf(fp,"        %% and put it in position i in block\n");
    fprintf(fp,"        block exch rgbval putinterval\n");
    fprintf(fp,"      } for\n");
    fprintf(fp,"      block  0  bcount 127 sub  getinterval\n");
    fprintf(fp,"    }\n\n");
  }

  fprintf(fp,"    { %% else it's a run block\n");
  fprintf(fp,"      currentfile buffer readhexstring pop pop\n\n");

  if (color) {
    fprintf(fp,"      %% look up value in colormap\n");
    fprintf(fp,"%s/rgbval cmap buffer 0 get 3 mul 3 getinterval store\n\n",
	    "      ");
    fprintf(fp,"%s0 1 bcount { block exch 3 mul rgbval putinterval } for\n\n",
	    "      ");
    fprintf(fp,"      block 0 bcount 1 add 3 mul getinterval\n");
  }
  else {
    fprintf(fp,"      %% look up value in graymap\n");
    fprintf(fp,"      /rgbval cmap buffer 0 get 1 getinterval store\n\n");
    fprintf(fp,"      0 1 bcount { block exch rgbval putinterval } for\n\n");
    fprintf(fp,"      block 0 bcount 1 add getinterval\n");
  }

  fprintf(fp,"    } ifelse\n");
  fprintf(fp,"  } %% end of proc\n");

  if (color) fprintf(fp,"  false 3 colorimage\n");
        else fprintf(fp,"  image\n");

  fprintf(fp,"} bind def\n\n\n");
}



/**********************************************/
static void epsPreview(fp, pic, colorType, w, h)
     FILE *fp;
     byte *pic;
     int   colorType;
     int   w, h;
{
  byte *prev;

  /* put in an EPSI preview */
  
  if (colorType != F_BWDITHER) { /* have to generate a preview */
    prev = (byte *) malloc(w * h);
    if (!prev) {
      fprintf(stderr,"Unable to malloc in epsPreview\n");
      return;
    }
    FSDither(pic, w, h, prev);
  }
  else prev = pic;
  
  fprintf(fp,"%%%%BeginPreview: %d %d %d %d\n", w, h, 1, 
	  (w/(72*4) + 1) * h);

  writeBWStip(fp, prev, "% ", w, h);

  fprintf(fp,"%%%%EndPreview\n");

  if (colorType == F_BWDITHER) free(prev);
}


/***********************************/
static int writeBWStip(fp, pic, prompt, w, h)
     FILE *fp;
     byte *pic;
     char *prompt;
     int  w, h;
{
  /* write the given 'pic' (B/W stippled, 1 byte per pixel, 0=blk,1=wht) 
     out as hexadecimal, max of 72 hex chars per line.

     returns '0' if everythings fine, 'EOF' if writing failed */

  int err, i, j, lwidth;
  byte outbyte, bitnum, bit;

  err = 0;

  for (i=0; i<h && err != EOF; i++) {
    fprintf(fp, "%s", prompt);

    outbyte = bitnum = lwidth = 0;

    if ((i&0x3f) == 0) WaitCursor();

    for (j=0; j<w && err != EOF; j++) {
      bit = *pic;
      outbyte = (outbyte<<1) | ((bit)&0x01);
      bitnum++;

      if (bitnum==8) {
	err = fprintf(fp,"%02x",outbyte);
	lwidth+=2;
	outbyte = bitnum = 0;
      }

      if (lwidth>=72 && j+1<w) { fprintf(fp, "\n%s", prompt); lwidth = 0; }
      pic++;
    }

    if (bitnum) {   /* few bits left over... */
      for ( ; bitnum<8; bitnum++) outbyte <<= 1;
      err = fprintf(fp,"%02x",outbyte);
      lwidth+=2;
    }

    fprintf(fp, "\n");
  }

  return err;
}



