/*
 * Copyright 1990 by Baylor College of Medicine ALL RIGHTS RESERVED. 
 *
 * This program is subject to a license agreement between 
 * Baylor College of Medicine and MIT. Any use inconsistent with
 * said license and any use by persons other than the faculty, 
 * students and staff at MIT or any use on a computer not operated 
 * as part of the Athena Computing Environment (ACE) is expressly 
 * prohibited.
 */
/*******************************************************************************
* SYSTEM...: Virtual Notebook System
* 
* MODULE...: page_to_ps.c
*
* PURPOSE..:
*    Provide driver to permit VNS to print a page on a postscript printer
*
* TAG: PS
*
* REVISIONS:
*   Christopher St. James       Initial Programming
*   Ross Dargahi                December 1990
*
*******************************************************************************/

#define PAGE_TO_PS_C    /*define source name for includes*/

/*******************************************************************************
Include Files
*******************************************************************************/

#include <math.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include "pagelist.h"
#include "page_to_ps.h"

#include <action.xbm>   /*action link icon bitmap*/
#include <badlink.xbm>  /*locked page link icon*/
#include <dlink.xbm>    /*dangling link icon bitmap*/
#include <link.xbm>     /*link icon bitmap*/
#include <qmark.xbm>    /*empty action link icon bitmap*/

/*******************************************************************************
Private Defines
*******************************************************************************/

#define FALSE       0
#define FONT_SIZE   12
#define FONT_TYPE   "Helvetica"
#define ICONXINDENT 4                /*indentation for icons - x axis*/
#define ICONYINDENT -2               /*indentation for icons - y axis*/
#define TEXTXOFFSET ICONXINDENT + 4  /*offset of label txt from icon - x axis*/
#define TRUE        !FALSE
#define UCHAR       unsigned char

/*******************************************************************************
Private Data Structures
*******************************************************************************/

typedef struct currState_s  /*current job calculated state*/
{
  float         bottomMargin;  /*calculated bottom margin/page grid point*/
  float         leftMargin;    /*calculated left margin/page grid point*/
  float         rightMargin;   /*calculated right margin/page grid point*/
  float         topMargin;     /*calculated top margin/page grid point*/
  float         bMAdjust;      /*margin adj for right margin (poster mode)*/
  float         lMAdjust;      /*margin adj for left margin (poster mode)*/
  float         pWidth;        /*current page width*/
  float         pHeight;       /*current page height*/
  float         physBMargin;   /*bottom margin forced by printer in use*/
  float         physLMargin;   /*left margin forced by printer in use*/
  float         physRMargin;   /*right margin forced by printer in use*/
  float         physTMargin;   /*top margin forced by printer in use*/
  float         userBMargin;   /*bottom margin selected by the user*/
  float         userLMargin;   /*left margin selected by the user*/
  float         userRMargin;   /*right margin selected by the user*/
  float         userTMargin;   /*top margin selected by the user*/
  P_Method      method;        /*current method*/
  P_Orientation orientation;   /*orientation*/
  int           scale;         /*scale*/
  float         xPrintDim;     /*horixontal print dimensions in inches*/
  float         yPrintDim;     /*vertical print dimensions in inches*/
} CURRSTATE_S;

typedef enum objType_e
{
  IMAGE,
  TEXT,
  ACTIONLINK,
  LINK
} OBJTYPE;

typedef struct psState_s  /*printer facility set state*/
{
  float         bottomMargin;  /*bottom margin*/
  P_Bool        color;         /*generate a color postscript file*/
  int           copies;        /*number of copies*/
  SrvContext    *dbServerId;   /*VNS database server id*/
  P_Output      outputTo;      /*output direction*/
  char          *fileName;     /*filename root*/
  FILE          *fp;           /*file pointer for postscript file*/
  float         leftMargin;    /*left margin*/
  P_Orientation orientation;   /*portrait or landscape*/
  P_PaperType   paper_type;    /*Paper type*/
  float         pheight;       /*height of paper output area (inches)*/
  P_Method      method;        /*printing method*/
  float         physBMargin;   /*bottom margin forced by printer in use*/
  float         physLMargin;   /*left margin forced by printer in use*/
  float         physRMargin;   /*right margin forced by printer in use*/
  float         physTMargin;   /*top margin forced by printer in use*/
  char          *printer;      /*printer to print to*/
  float         pwidth;        /*width of paper output (inches)*/
  float         rightMargin;   /*right margin*/
  int           scale;         /*scaling factor (can be <100%<*/
  P_Bool        show_alinks;   /*show action links or not*/
  P_Bool        show_audios;   /*show audio or not*/
  P_Bool        show_grid;     /*print grid*/
  P_Bool        show_images;   /*show images or not*/
  P_Bool        show_outline;  /*outline objects*/
  P_Bool        show_plinks;   /*show notebook links or not*/
  P_Bool        show_text;     /*show text or not*/
  float         topMargin;     /*top margin*/
  P_Bool        uniqueFname;   /*T:generate unique file names*/
  Display       *xDisplay;     /*connection to the X server*/
} PSSTATE_S;


/*******************************************************************************
Private Global Declarations
*******************************************************************************/

static short       colNo;       /*current page column (> 1 poster mode only)*/
static CURRSTATE_S currState;   /*current job state*/
static short       pCols;       /*#cols in job (> 1 poster mode only)*/
static short       pRows;       /*# rows in job (> 1 poster mode only)*/
static PSSTATE_S   ps;          /*ps engine current state structure*/
static int         psYOffset;   /*vns->ps coord offset in y*/
static int         ps_x, ps_y;  /*object anchor points in postscript coords*/
static short       rowNo;       /*current page row (> 1 poster mode only)*/

/*******************************************************************************
Private Function Prototypes
*******************************************************************************/

static void bitmapToPSByte();
static void calcFitToPageScale();
static void calcPageBounds(); 
static void calcPageRowsCols();
static void calcSetupMargins();
static void doObjects();
static void genPageDesc();
static void printGrid();
static void printObjectText();
static void printPixelVal();
static void printPSIcon();
static void prolog();
static void setup();
static void setupOrientation();
static void textClipRect();
static void vnscoordtopscoord();

/*******************************************************************************
****************************    PUBLIC FUNCTIONS    ****************************
*******************************************************************************/

void set_bot_margin(b)   float b;         {ps.bottomMargin = b;}
void set_color(s)        P_Bool s;        {ps.color = s;}
void set_copies(c)       int c;           {ps.copies = c;}
void set_dbServerId(c)   SrvContext *c;   {ps.dbServerId = c;}
void set_file(fp)        FILE *fp;        {ps.fp = fp;}
void set_fileName(f)     char *f;        {ps.fileName = f;}
void set_left_margin(l)  float l;         {ps.leftMargin = l;}
void set_method(p)       P_Method p;      {ps.method = p;}
void set_orientation(r)  P_Orientation r; {ps.orientation = r;}
void set_outputTo(b)     P_Output b;      {ps.outputTo = b;}
void set_page_height(h)  float h;         {ps.pheight = h;}
void set_page_width(w)   float w;         {ps.pwidth  = w;}
void set_papertype(p)    P_PaperType p;   {ps.paper_type = p;}
void set_physBMargin(l)  float l;         {ps.physBMargin = l;}
void set_physLMargin(l)  float l;         {ps.physLMargin = l;}
void set_physRMargin(l)  float l;         {ps.physRMargin = l;}
void set_physTMargin(l)  float l;         {ps.physTMargin = l;}
void set_printer(p)      char *p;         {ps.printer =  p;}
void set_right_margin(l) float l;         {ps.rightMargin = l;}
void set_scale(s)        int s;           {ps.scale = s;}
void set_show_alinks(s)  P_Bool s;        {ps.show_alinks = s;}
void set_show_audios(s)  P_Bool s;        {ps.show_audios = s;}
void set_show_grid(s)    P_Bool s;        {ps.show_grid = s;}
void set_show_images(s)  P_Bool s;        {ps.show_images = s;}
void set_show_outline(s) P_Bool s;        {ps.show_outline = s;}
void set_show_plinks(s)  P_Bool s;        {ps.show_plinks = s;}
void set_show_text(s)    P_Bool s;        {ps.show_text = s;}
void set_top_margin(b)   float b;         {ps.topMargin = b;}
void set_uniqueFname(b)  int b;           {ps.uniqueFname = b;}
void set_xDisplay(d)     Display *d;      {ps.xDisplay = d;}

float         get_bot_margin()   { return(ps.bottomMargin);}
int           get_copies()       { return(ps.copies);}
FILE          *get_file()        { return(ps.fp);}
char          *get_fileName()    { return(ps.fileName);}
float         get_left_margin()  { return(ps.leftMargin);}
P_Method      get_method()       { return(ps.method);}
P_Orientation get_orientation()  { return(ps.orientation);}
P_Output      get_outputTo()     { return(ps.outputTo);}
float         get_page_height()  { return(ps.pheight);}
float         get_page_width()   { return(ps.pwidth);}
P_PaperType   get_papertype()    { return(ps.paper_type);}
float         get_physBMargin()  { return(ps.physBMargin);}
float         get_physLMargin()  { return(ps.physLMargin);}
float         get_physRMargin()  { return(ps.physRMargin);}
float         get_physTMargin()  { return(ps.physTMargin);}
char          *get_printer()     { return(ps.printer);}
float         get_right_margin() { return(ps.rightMargin);}
int           get_scale()        { return(ps.scale);}
P_Bool        get_show_alinks()  {return(ps.show_alinks);}
P_Bool        get_show_audios()  {return(ps.show_audios);}
P_Bool        get_show_grid()    {return(ps.show_grid);}
P_Bool        get_show_images()  {return(ps.show_images);}
P_Bool        get_show_outline() {return(ps.show_outline);}
P_Bool        get_show_plinks()  {return(ps.show_plinks);}
P_Bool        get_show_text()    {return(ps.show_text);}
P_Bool        get_uniqueFname()  {return(ps.uniqueFname);}
float         get_top_margin()   { return(ps.topMargin);}

/******************************************************************************/
void init_ps()

/*Initialize the page_to_ps program and set defualt parameters.
This should be called before calling any other page_to_ps functions.*/

{
  /* set default values for all of the page_to_ps parameters */

  /*set up page constraints*/

  ps.pheight = 11.00;
  ps.pwidth = 8.50;
  
  ps.bottomMargin = 0;
  ps.leftMargin = 0;
  ps.rightMargin = 0;
  ps.topMargin = 0;

  ps.orientation = P_PORTRAIT;
  ps.paper_type = P_US_LETTER;
  ps.method = P_CLIP_PAGE;
  ps.outputTo = P_PRINTER;

  /*setup other parameters*/
  
  ps.copies = 1;
  ps.printer = "";
  ps.fileName = "";
  ps.uniqueFname = P_YES;
  ps.fp = NULL;
  ps.scale = 100;
  ps.show_alinks = P_YES;
  ps.show_audios = P_YES;
  ps.show_grid = P_NO;
  ps.show_images = P_YES;
  ps.show_outline = P_NO;
  ps.show_plinks = P_YES;
  ps.show_text = P_YES;

  /*convert X bitmaps to postscript format*/

  bitmapToPSByte(badlink_bits, sizeof(badlink_bits) / sizeof(badlink_bits[0]));
  bitmapToPSByte(dlink_bits, sizeof(dlink_bits) / sizeof(dlink_bits[0]));
  bitmapToPSByte(link_bits, sizeof(link_bits) / sizeof(link_bits[0]));
  bitmapToPSByte(action_bits, sizeof(action_bits) / sizeof(action_bits[0]));
  bitmapToPSByte(qmark_bits, sizeof(qmark_bits) / sizeof(qmark_bits[0]));

} /*init_ps*/

/******************************************************************************/
void PSprint(pageList)

char *pageList;    /*handle to pagelist*/

/*interface to postscript printing.

ASSERTIONS
 - <pageList> contains at least one item */

{
  static short unique;  /*unique per job delimiter*/

  char           command[512];  /*command to lpr*/
  PLL_LISTDATA_S data;          /*data from the page list*/
  char           *fnRoot;       /*filename root*/
  Page_Info      info;
  char           *newFileName;  /*generated/modified filename*/
  int            nobjs;         /*number of objects in the list*/
  Object_Block   *objs;         /*list of objects to print for the page*/
  short          pageNo;        /*current page number*/
  short          xBounds;       /*object extents on page in x*/
  short          yBounds;       /*object extents on page in y*/

  /*allocate space for the new filename. Allow 10 characters for the
   *notebook id, 10 characters for the page id, 6 characters for page 
   *delimiters and 10 characters for the user id. Also allow 4 characters 
   *for other separators and 3 characters for a unique separator
   *to prevent multiple print jobs of the same file by the same user
   *causing a collision*/

  if (ps.fileName && strlen(ps.fileName) > 0)
     newFileName = (char *)malloc(strlen(ps.fileName) + 43);
  else  /*user did not supply filename*/
     newFileName = (char *)malloc(52);

  data.nid = PLL_FIRST; /*get first page in the list*/

  while (PLLgoto(pageList, PLL_PAGE, &data) == PLL_OK)
   {
     currState.method = ps.method;
     pRows = pCols = 1;
     pageNo = 1;
     PLLgetData(pageList, PLL_PAGE, &data);
     srv_get_blurted_page(ps.dbServerId, data.nid, data.pid, &info, &objs,
                          &nobjs);

     /*If fit to page is the method we must set the current state scale to
      *100% so that when the user margins are calculated in setupOrientation,
      *they are not scaled up by ps.scale thus causing an erroneous scaling
      *when calculating fit to page scale*/

     if (currState.method == P_FIT_TO_PAGE)
        currState.scale = 100;
     else
        currState.scale = ps.scale;

     /*depending on the orientation and mode calculate spacing*/

     calcPageBounds(objs, nobjs, &xBounds, &yBounds);
     setupOrientation(ps.orientation, xBounds, yBounds);

    /*calculate an offset for converting vns y to ps y coordinates. This is
     *important when the vns objects extend past the physical extents of a
     *postscript page also for different printing methods calculate the 
     *necessary attributes for that mode.*/

     if (currState.method == P_POSTER)
      {
        calcPageRowsCols(xBounds, yBounds);
        psYOffset = yBounds;
      }
     else if (currState.method == P_FIT_TO_PAGE && 
              (xBounds > (int)(currState.xPrintDim * 72) ||
              (yBounds > (int)(currState.yPrintDim * 72))))
      {
        calcFitToPageScale(xBounds, yBounds, &currState.scale);
        psYOffset = yBounds;
      }
     else 
      {
        currState.method = P_CLIP_PAGE;  /*in case fit to page needs no 
                                          *scaling*/ 
        psYOffset = currState.pHeight * 72;  /*print Top left portion*/
      }

     /*for each page row/page column output a page description. There will be
      *one row & one column in every case except postering*/

     for (rowNo = 1; rowNo <= pRows; rowNo++)
        for (colNo = 1; colNo <= pCols; colNo++)
         {
           /*build new file name using page id, notebook id and process 
            *id + a unique character*/

           if (ps.fileName && strlen(ps.fileName) > 0)
              fnRoot = ps.fileName;
           else  /*user did not supply filename*/
              fnRoot = "/tmp/vns";

           if (ps.uniqueFname)
              sprintf(newFileName, "%s.%d.%d.%02dof%02d.%d%03d", fnRoot, 
                      data.nid, data.pid, pageNo++, pRows * pCols, getpid(), 
                      unique);
           else
              sprintf(newFileName, "%s", fnRoot);

           if (!(ps.fp = fopen(newFileName, "w")))
            {
              printf("Could Not Open File: %s For Writing Postscript\n");
              rowNo = pRows + 1;
              colNo = pCols + 1;
            }
           else
            {
              genPageDesc(objs, nobjs, data.nid, data.pid);
              fclose(ps.fp);  /* close postscript file */

              if (ps.outputTo != P_FILE)
               {
                 if (ps.outputTo == P_PRINTER)
                    sprintf(command, "lpr -r -s -#%d -P%s %s", ps.copies, 
                            ps.printer, newFileName);
                 else
                    sprintf(command, "lpr -s -#%d -P%s %s", ps.copies, 
                            ps.printer, newFileName);

                 system(command);
               }
            }
         }

     free(objs);
     data.nid = PLL_NEXT;
   }

  unique = (unique + 1) % 1000;  /*increment/cycle to next no*/
  free(newFileName);

} /*PSprint*/

/*******************************************************************************
****************************    PRIVATE FUNCTIONS    ***************************
*******************************************************************************/

static void addImage(obj)

Object_Block *obj;  /*Image object*/

/*obtain the image data via srvlib and generate postscript code*/

{
  short cline;
  int i;
  int j;
  unsigned int gray[256];
  int line_len;
  int line_len16;  /*each image scanline is rounded to nearest 16 bits*/
  int nshort;
  int printDepth;  /*depth for postscript printing*/
  int x, y;

  line_len = (obj->img.depth * obj->img.width + 7) / 8;
  line_len16 = (obj->img.depth * obj->img.width + 15) / 16 * 2;
  printDepth = (obj->img.depth == 1) ? 1 : 8;

  cline = 1;
  vnscoordtopscoord(obj->info.x, obj->info.y, &x, &y);

  fprintf(ps.fp, "gsave\n");
  fprintf(ps.fp, "  %d %d translate\n", x, y - obj->info.height);
  
  if (ps.show_outline)
   {
     fprintf(ps.fp, "  gsave\n");
     fprintf(ps.fp, "    newpath\n");
     fprintf(ps.fp, "      0 0 moveto\n");
     fprintf(ps.fp, "      %d 0 rlineto\n", obj->info.width);
     fprintf(ps.fp, "      0 %d rlineto\n", obj->info.height);
     fprintf(ps.fp, "      %d neg 0 rlineto\n", obj->info.width);
     fprintf(ps.fp, "    closepath\n");
     fprintf(ps.fp, "    0 setgray\n");
     fprintf(ps.fp, "    stroke\n");
     fprintf(ps.fp, "  grestore\n");
   }

  fprintf(ps.fp, "  %d %d scale\n", obj->info.width, obj->info.height);
  fprintf(ps.fp, "  %d %d %d [%d 0 0 %d 0 %d]\n",
          obj->img.width, obj->img.height, printDepth,
          obj->img.width, -obj->img.height, obj->img.height);

  if (obj->img.depth == 1)  /*Monochrome*/
   {
     fprintf(ps.fp, "  printImage\n  ");

     for (i = 0; i < obj->img.height; i++)
        for (j = 0; j < line_len; j++)
           printPixelVal(~obj->img.data[j+i*line_len16], &cline);
   }
  else if (obj->img.cmap_type == CMAP_RGB) /*Pseudo-colour*/
   {
     if (!ps.color)
      {
        fprintf(ps.fp, "  printImage\n  ");

        for (i = 0; i < obj->img.cmap_data.rgb.map_size; i++)
           gray[i] = obj->img.cmap_data.rgb.map[i].r * .30 +
                     obj->img.cmap_data.rgb.map[i].g * .59 +
                     obj->img.cmap_data.rgb.map[i].b * .11; 
         
        for (i = 0; i < obj->img.height; i++)
           for (j = 0; j < line_len; j++)
              printPixelVal((UCHAR)gray[obj->img.data[j+i*line_len16]], &cline);
      }
     else /*colour PS*/
      {
        fprintf(ps.fp, "  printColourImage\n  ");

        for (i = 0; i < obj->img.height; i++)
           for (j = 0; j < line_len; j++)
            {
              int vector;

              vector = obj->img.data[j+i*line_len16];
              printPixelVal((UCHAR)obj->img.cmap_data.rgb.map[vector].r, 
                            &cline);
              printPixelVal((UCHAR)obj->img.cmap_data.rgb.map[vector].g, 
                            &cline);
              printPixelVal((UCHAR)obj->img.cmap_data.rgb.map[vector].b, 
                            &cline);
            }
      }
   }
  else if (obj->img.depth == 8 && obj->img.cmap_type == CMAP_NONE) /*8 bit gs*/
   {

     fprintf(ps.fp, "  printImage\n  ");

     for (i = 0; i < obj->img.height; i++)
        for (j = 0; j < line_len; j++)
           printPixelVal((UCHAR)obj->img.data[j+i*line_len16], &cline);
   }
  else /*"direct" colour*/
   {
     if (!ps.color)
      {
        fprintf(ps.fp, "  printImage\n  ");

        for (i = 0; i < obj->img.height; i++)
           for (j = 0; j < line_len; j += 3)
              printPixelVal((UCHAR)(obj->img.data[j+i*line_len16] * .30 +
                            obj->img.data[j+1*i*line_len16] * .59 +
                            obj->img.data[j+2+i*line_len16] * .11) , &cline);
      }
     else /*colour PS*/
      {
        fprintf(ps.fp, "  printColourImage\n  ");

        for (i = 0; i < obj->img.height; i++)
           for (j = 0; j < line_len; j++)
              printPixelVal(obj->img.data[j+i*line_len16], &cline);
      }
   }

  fprintf(ps.fp, "\ngrestore\n");

} /*addImage*/

/******************************************************************************/
static void bitmapToPSByte(data, dataSize)

char *data;     /*X bitmap data array*/
int  dataSize;  /*size of the data array*/

/*Converts a X bitmap byte to a PostScript image byte, this is byte and nibble
reversed as follows:

 X  1 2 4 8  1 2 4 8
     HI        LO
     LO        HI
 PS 8 4 2 1  8 4 2 1

stores the resulting values back into the bitmap arrays, this is done once at
initialization time. These arrays are local to this module*/

{
  UCHAR aByte;  /*element of bitmap to be converted*/
  short lcv;    /*loop control variable*/

  for (lcv = 0; lcv < dataSize; lcv++)
   {
     aByte = (UCHAR)data[lcv];
 
     data[lcv] = (((aByte & 0x08) >> 3 | (aByte & 0x01) << 3 |
                 (aByte & 0x04) >> 1 | (aByte & 0x02) << 1) << 4 |
                 ((aByte & 0x80) >> 3 | (aByte & 0x10) << 3 |
                 (aByte & 0x40) >> 1 | (aByte & 0x20) << 1) >> 4);
   }
	
} /*bitmapToPSByte*/

/******************************************************************************/
static void calcFitToPageScale(xBounds, yBounds, newScale)

short xBounds;    /*page object extents in x*/
short yBounds;    /*page object extents in y*/
int   *newScale;  /*the new scale*/

{
  float printHeight;
  float printWidth;
  float xScale;
  float yScale;

  printHeight = currState.pHeight - (currState.physTMargin + 
                ((currState.userTMargin - currState.physTMargin > 0) ?
                  currState.userTMargin - currState.physTMargin : 0)) -
                (currState.physBMargin +
                ((currState.userBMargin - currState.physBMargin > 0) ?
                  currState.userBMargin - currState.physBMargin : 0));

  printWidth = currState.pWidth - (currState.physLMargin + 
                ((currState.userLMargin - currState.physLMargin > 0) ?
                  currState.userLMargin - currState.physLMargin : 0)) -
                (currState.physRMargin +
                ((currState.userRMargin - currState.physRMargin > 0) ?
                  currState.userRMargin - currState.physRMargin : 0));

  xScale = printWidth * 72 /xBounds;
  yScale = printHeight * 72 / yBounds;

  if (xScale < yScale && xScale < 1)
     *newScale = xScale * 100;
  else if (yScale < 1)
     *newScale = yScale * 100;

} /*calcFitToPageScale*/

/******************************************************************************/
static void calcPageBounds(objs, nobjs, x, y) 

Object_Block  *objs;  /*list of objects to print for the page*/
int           nobjs;  /*number of objects in the list*/
short         *x;     /*outermost x position*/
short         *y;     /*outermost y position*/

/*given a list of objects on a page, calculates the maximum extents of those
objects on the page*/

{
  int lcv;        /*loop control variable*/

  *x = *y = 0;

  for (lcv = 0; lcv < nobjs; lcv++)
   {
     if (objs[lcv].info.x + objs[lcv].info.width > *x)
        *x = objs[lcv].info.x + objs[lcv].info.width;

     if (objs[lcv].info.y + objs[lcv].info.height > *y)
        *y = objs[lcv].info.y + objs[lcv].info.height;
   }

} /*calcPageBounds*/

/******************************************************************************/
static void calcPageRowsCols(xBounds, yBounds)

short         xBounds;  /*outermost x position*/
short         yBounds;  /*outermost y position*/

/*Calculates the number of printed pages needed to represent a VNS page given
the outer most bounds of the objects on that page. The routine assumes that
the margin will be at the boundries of the page with interior poster pages
having margins comprising of the minimum physical margins enforced by the
printer*/

{
  pRows = ceil((yBounds + 
               (currState.userTMargin + currState.userBMargin) * 72) * 
               (currState.scale / 100.0) / 
               ((currState.pHeight - currState.physTMargin - 
                 currState.physBMargin) * 72));

  pCols = ceil((xBounds + 
               (currState.userLMargin + currState.userRMargin) * 72) * 
               (currState.scale / 100.0) / 
               ((currState.pWidth - currState.physLMargin - 
                 currState.physRMargin) * 72));

} /*calcPageRowsCols*/

/******************************************************************************/
static void calcSetupMargins()

/*calculates offsets and margins for a page taking into consideration the 
position of the page in postering mode*/

{
  static char vertAdjustFlag;  /*T:must factor in vertical margin adjustments*/

  /*must calculate margin adjustments for user supplied margins. This
   *ensures that the pages in each row and column are offset correctly*/

  if (rowNo == 1) 
   { 
     vertAdjustFlag = FALSE;
     currState.bMAdjust = 0;
   }
  else if (!vertAdjustFlag)
   {
     vertAdjustFlag = TRUE;
     currState.bMAdjust = currState.bottomMargin;
   }

  /*if we are clipping then must accomodate clipping region for the page. Also
   *note that in clip mode we print the top left portion of the page. This is
   *what was requested*/

  if (currState.method == P_CLIP_PAGE)
   {
     currState.topMargin = currState.physTMargin + 
                      ((currState.userTMargin - currState.physTMargin > 0) ?
                       currState.userTMargin - currState.physTMargin : 0);
     currState.rightMargin = currState.physRMargin + 
                        ((currState.userRMargin - currState.physRMargin > 0) ?
                         currState.userRMargin - currState.physRMargin : 0);
   }
  else
     currState.topMargin = currState.rightMargin = 0;

  currState.leftMargin = currState.physLMargin;
  currState.bottomMargin = currState.physBMargin;

  /*depending on where we are in the page grid we must set up the margins
   *accordingly. User margins must only be in effect on the boundry of the
   *page grid. All other margins will be restricted to those required by the
   *printer in use. This means that pages around the edges of the grid will
   *be affected by user menus, the row and column numbers alert us to what
   *margins must be affected. Note that the page grid is processed from left
   *to right and bottom to top, this is because the printing area can be 
   *viewed as being static while be translate the vns page around in it to
   *render it all*/

  if (colNo == 1)
     currState.leftMargin = /*currState.physLMargin + */
                        ((currState.userLMargin - currState.physLMargin > 0) ?
                         currState.userLMargin - currState.physLMargin : 0);

  if (rowNo == 1)
     currState.bottomMargin = /*currState.physBMargin +*/ 
                       ((currState.userBMargin - currState.physBMargin > 0) ?
                        currState.userBMargin - currState.physBMargin : 0);

  /*depending on our location in the page grid, we must set up the page
   *accordingly. this really depends on if we are at an edge or if we
   *are in the interior region of the page grid*/
 
  if (rowNo * colNo == 1) /*first page special case*/
     setup(currState.leftMargin + currState.physLMargin, 
           currState.bottomMargin + currState.physBMargin);
  else if (colNo == 1) /*at a left edge*/
     setup(currState.leftMargin + currState.physLMargin,
           (currState.pHeight - currState.physTMargin - currState.bottomMargin -
            currState.physBMargin) * (rowNo - 1) * -1 + currState.bMAdjust);
  else if (rowNo == 1) /*at the bottom*/
     setup((currState.pWidth - currState.physRMargin - currState.leftMargin -
           currState.physLMargin) * (colNo - 1) * -1 + currState.lMAdjust,
           currState.bottomMargin + currState.physBMargin);
  else
     setup((currState.pWidth - currState.physRMargin - currState.leftMargin -
           currState.physLMargin) * (colNo - 1) * -1 + currState.lMAdjust,
           (currState.pHeight - currState.physTMargin - currState.bottomMargin -
           currState.physBMargin) * (rowNo - 1) * -1 + currState.bMAdjust);

  /*must calculate margin adjustments for user supplied margins. This
   *ensures that the pages in each row and column are offset correctly*/

  if (colNo == 1)
     currState.lMAdjust = currState.leftMargin;

  if (colNo == pCols)
     currState.lMAdjust = 0;

 
} /*calcSetupMargins*/

/******************************************************************************/
static void doImages(objs, nobjs)

Object_Block *objs;  /*list of objects on the page*/
int          nobjs;  /*no objects in the list*/

/*loops through the object list and acts on each image object it finds*/

{
  int i;

  for (i = 0; i < nobjs; i++)
     if (objs[i].info.type == OT_IMAGE)
        addImage(&objs[i]);

} /*doImages*/

/******************************************************************************/
static void doObjects(type, objs, nobjs)

OBJTYPE      type;   /*tpye of object to do*/
Object_Block *objs;  /*list of objects on the page*/
int          nobjs;  /*no objects in the list*/

/*loops through the object list and acts on each action link object it finds*/

{
  int   i;       /*loop control variable*/
  int   offset;  /*offset for text label*/
  short status;  /*status of an object (link, action link)*/
 
  switch (type)
   {
     case IMAGE:
        for (i = 0; i < nobjs; i++)
           if (objs[i].info.type == OT_IMAGE)
              addImage(&objs[i]);
        break;

     case TEXT:
        for (i = 0; i < nobjs; i++)
           if (objs[i].info.type == OT_TXT)
            {
              vnscoordtopscoord(objs[i].info.x, objs[i].info.y, &ps_x, &ps_y);
              printObjectText(&objs[i], objs[i].txt, 
                              objs[i].info.data.t_info.font,
                              objs[i].info.data.t_info.color, 0);
            }

        break;

     case ACTIONLINK:
        for (i = 0; i < nobjs; i++)
           if (objs[i].info.type == OT_ACTION_LINK)
            {
              if (*objs[i].info.data.al_info.command)
                 status = 1;
              else
                 status = 0;

              vnscoordtopscoord(objs[i].info.x, objs[i].info.y, &ps_x, &ps_y);
              printObjectText(&objs[i], objs[i].info.data.al_info.title, 
                              objs[i].info.data.al_info.font,
                              objs[i].info.data.al_info.color,
                              action_width + TEXTXOFFSET);
              printPSIcon(&objs[i], status);
            }

        break;

     case LINK:
        for (i = 0; i < nobjs; i++)
           if (objs[i].info.type == OT_LINK)
            {
              vnscoordtopscoord(objs[i].info.x, objs[i].info.y, &ps_x, &ps_y);
              if ((status = srv_validate_page(ps.dbServerId, 
                                (int)objs[i].info.data.l_info.dest_note,
                                (int)objs[i].info.data.l_info.dest_page)) == 1)
                 offset = link_width;
              else if (status == 2)  /*page does not exist*/
                 offset = dlink_width;
              else  /*no access*/
                 offset = badlink_width;

              printObjectText(&objs[i], objs[i].info.data.l_info.title, 
                              objs[i].info.data.l_info.font,
                              objs[i].info.data.l_info.color,
                              offset + TEXTXOFFSET);
              printPSIcon(&objs[i], status);
            }

        break;
   }
 
} /*doObjects*/

/******************************************************************************/
static void genPageDesc(objs, nobjs, nid, pid)

Object_Block *objs;  /*list of objects to process*/
int          nobjs;  /*no of objects in the list*/
int          nid;    /*note book id of page*/
int          pid;    /*page id of page*/

/*generate the postscript page description*/

{
  prolog();

  /*set up the margins for the page taking into consideration the physical
   *page constraints imposed by the printing device*/

  calcSetupMargins();

  fprintf(ps.fp, "%%%% Page %d (Notebook %d)\n", nid, pid);
  fprintf(ps.fp, "save\n");

  if (ps.show_grid)
     printGrid();

  fprintf(ps.fp, "gsave\n");
  fprintf(ps.fp, "%.2f %.2f scale\n", currState.scale/100.0, 
                                      currState.scale/100.0); /*int-->real */

  if (ps.show_images)
     doObjects(IMAGE, objs, nobjs);

  if (ps.show_text)
     doObjects(TEXT, objs, nobjs);

  if (ps.show_alinks)
     doObjects(ACTIONLINK, objs, nobjs);

  if (ps.show_plinks)
     doObjects(LINK, objs, nobjs);

  fprintf(ps.fp, "showpage\n");
  fprintf(ps.fp, "restore\n");
  fprintf(ps.fp, "%%%% EndPage\n");
  fprintf(ps.fp, "%%%%Trailer\n");  /* print trailer */

} /*genPageDesc*/

/******************************************************************************/
static void printGrid()

/*prints a grid on the postscript page*/

{
  int height;
  int lcv;
  int width;

  width = (currState.pWidth - currState.leftMargin - currState.rightMargin) * 
          72 * pRows;
  height = (currState.pHeight - currState.topMargin - currState.bottomMargin) *
           72 * pCols;

  fprintf(ps.fp, "gsave\n");

  if (!ps.color)
     fprintf(ps.fp, "  0.95 setgray\n");
  else
     fprintf(ps.fp, "  0.75 0.85 0.85 setrgbcolor\n");
 
  for (lcv = 0; lcv < width; lcv += 16)
   {
     fprintf(ps.fp, "  %d %d moveto\n", lcv, 0); 
     fprintf(ps.fp, "  %d %d lineto\n", lcv, height);
   }
     
  for (lcv = 0; lcv < height; lcv += 16)
   {
     fprintf(ps.fp, "  %d %d moveto\n", 0, lcv);
     fprintf(ps.fp, "  %d %d lineto\n", width, lcv);
   }

  fprintf(ps.fp, "stroke\n");
  fprintf(ps.fp, "grestore\n");

} /*printGrid*/

/******************************************************************************/
static void printHeader()

/*creates a standard postscript header according to covention*/

{
  fprintf(ps.fp, "%%!PS-Adobe-2.0\n");
  fprintf(ps.fp, "%%%%Creator: VNS\n");

  if (currState.method == P_POSTER)
     fprintf(ps.fp, "%%%%Pages: %d\n", ps.copies * pRows * pCols);
  else
     fprintf(ps.fp, "%%%%Pages: %d\n", ps.copies);

  if (currState.method != P_POSTER)
     fprintf(ps.fp, "%%%%BoundingBox: %d %d %d %d\n",
             (int)currState.leftMargin,
             (int)currState.bottomMargin,
             (int)(currState.leftMargin + currState.pWidth - 
                   currState.rightMargin),
             (int)(currState.bottomMargin + currState.pHeight - 
                   currState.topMargin));

  fprintf(ps.fp, "%%%%DocumentFonts: (atend)\n");
  fprintf(ps.fp, "%%%%ProofMode: Substitute\n");
  fprintf(ps.fp, "%%%%EndComments\n");
  return;

} /*printHeader*/ 

/******************************************************************************/
static void printImageDefs()

/*ps function for invoking image printing*/

{
  fprintf(ps.fp, "/picstr 1 string def\n"); 
  fprintf(ps.fp, "/printImage\n");
  fprintf(ps.fp, "{\n");
  fprintf(ps.fp, "  {currentfile picstr readhexstring pop}\n");
  fprintf(ps.fp, "  image\n");
  fprintf(ps.fp, "} def\n");

  if (ps.color)
   {
     fprintf(ps.fp, "/picstr 1 string def\n");
     fprintf(ps.fp, "/printColourImage\n");
     fprintf(ps.fp, "{\n");
     fprintf(ps.fp, "  {currentfile picstr readhexstring pop}\n");
     fprintf(ps.fp, "  false 3 colorimage\n");
     fprintf(ps.fp, "} def\n");
   }
  return;

} /*printImageDefs*/

/******************************************************************************/
static void printObjectText(obj, text, font, colour, offset)

Object_Block *obj;     /*text object to print*/
char         *text;    /*text to print*/
char         *font;    /*font to render text in*/
char         *colour;  /*colour to render text in*/
int          offset;   /*offset from x origin*/

/*Given a text object, generates the necessary Postscript code to print that
 object*/

{
  char   ch;            /*current text object character being processed*/
  XColor fontRGBColor;  /*x representation of font colour*/
  int    fsize;         /*font size*/
  short  lastCharNL;    /*T-last character processed is a newline*/
  short  lastCharSpace; /*T-last character processed was whitespace*/
  char   *psfont;       /*object font*/
  char   *psscale;      /*object font size*/
  int    ps_width;      /*object width in PS coords*/
  int    ps_height;     /*object height in PS coords*/
  int    tx, ty;
  int    x, y;

  lastCharNL = FALSE;
  lastCharSpace = FALSE;

  /* determine width and heigth for postscript page */

  x = ps_x + obj->info.width;
  vnscoordtopscoord(x, obj->info.y, &tx, &ty);
  ps_width = tx - ps_x;
  y = ps_y + obj->info.height;
  vnscoordtopscoord(obj->info.x, y, &tx, &ty);
  ps_height = ps_y - ty;

  vnstops(font, &psfont, &psscale);

  if (psscale != NULL)
   {
     fsize = atoi(psscale);
     free(psscale);
   }
  else
     fsize = FONT_SIZE;


  fprintf(ps.fp, "gsave\n");

  /*set the font colour*/

  XParseColor(ps.xDisplay, DefaultColormap(ps.xDisplay, 
              DefaultScreen(ps.xDisplay)), colour, &fontRGBColor);

  if (!ps.color)
     fprintf(ps.fp, "%.2f setgray\n", (fontRGBColor.red * .59 + 
                                       fontRGBColor.green * .30 +
                                       fontRGBColor.blue * .11) / 65535.0);
  else
     fprintf(ps.fp, "%.2f %.2f %.2f setrgbcolor\n", fontRGBColor.red / 65535.0, 
                                                  fontRGBColor.green / 65535.0,
                                                  fontRGBColor.blue  / 65535.0);


  fprintf(ps.fp, "  /%s findfont %d scalefont setfont\n", psfont, fsize);
  fprintf(ps.fp, "  /pointSize %d def\n", fsize);
  fprintf(ps.fp, "  /height    %d def\n", obj->info.height);
  fprintf(ps.fp, "  /startX    %d def\n", offset);
  fprintf(ps.fp, "  /startY    %d def\n", obj->info.height - fsize);
  fprintf(ps.fp, "  /width     %d def\n", obj->info.width - offset);
  fprintf(ps.fp, "  %d %d %d %d 1 textClipRect\n", ps_x, ps_y, obj->info.width,
                                                obj->info.height);
  fprintf(ps.fp, "  (");

  while (ch = *text++)
    switch(ch)
      {
        case '(':
           fprintf(ps.fp, "\\(");
           lastCharSpace = FALSE;
           break;

        case ')':
           fprintf(ps.fp, "\\)");
           lastCharSpace = FALSE;
           break;

        case '\\':
           fprintf(ps.fp, "\\\\");
           lastCharSpace = FALSE;
           break;

        case '\n':
           fprintf(ps.fp, ") width WordWrap\n");
           fprintf(ps.fp, "/startY startY pointSize sub def\n");
           lastCharSpace = FALSE;

           if (*text != '\0')
              fprintf(ps.fp, "  (");
           else
              lastCharNL = TRUE;

           break;

        case '\t':                /* See note in header */
           if (!lastCharSpace)
            {
              fprintf(ps.fp, "\n\t");
              lastCharSpace = FALSE;
            }
           else
              fprintf(ps.fp, "\t");

           break;

        case ' ':
           if (!lastCharSpace)
            {
              fprintf(ps.fp, "\n ");
              lastCharSpace = TRUE;
            }
           else
              fprintf(ps.fp, " ");

           break;

        default:
           fprintf(ps.fp, "%c", ch);
           lastCharSpace = FALSE;
           break;
      }


  if (!lastCharNL)
     fprintf(ps.fp, "\n) width WordWrap\n");

  fprintf(ps.fp, "grestore\n");

  if (!psfont)
     free(psfont);

} /*printObjectText*/

/******************************************************************************/
static void printPixelVal(pixelVal, count)

UCHAR   pixelVal;  /*pixel/component value*/
short   *count;    /*# elements printed on the current line*/

/*prints a pixel in hexadecimal format to the current postscript file, also
creates newlines in the postscript file when neccessary*/

{
  static char *digs = "0123456789abcdef";

  fprintf(ps.fp, "%c%c", digs[pixelVal >> 4], digs[pixelVal & 0xf]);

  if ((*count)++ == 36)
   {
     fprintf(ps.fp, "\n  ");
     *count = 1;
   }
 
} /*printPixelVal*/

/******************************************************************************/
static void printPSIcon(obj, status)

Object_Block *obj;    /*object to render icon for*/
short        status;  /*status of the object*/

/*Generates the necessary postscript call to render an icon in greyscale or
colour*/

{
  char    *colour;       /*colour name specification*/
  XColor  iconRGBColor;  /*RGB values of icon colour*/
  short   linkStatus;    /*status of a link*/
  char    *psProc;       /*postscript function to call*/
  int     x, y;          /*LL corner of the icon image*/

  switch (obj->info.type)
   {
     case OT_ACTION_LINK:
        colour = obj->info.data.al_info.color;
   
        if (status)
           psProc = "doActionLink";
        else
           psProc = "doEmptyActionLink";

        break;

     case OT_LINK:
        colour = obj->info.data.l_info.color;
      
        if (status == 0) 
           psProc = "doDangleLink";
        else if (status == -1)
           psProc = "doLockLink";
        else
           psProc = "doLink";
        break;
   };

  
  /*set the icon colour*/

  XParseColor(ps.xDisplay, DefaultColormap(ps.xDisplay, 
              DefaultScreen(ps.xDisplay)), colour, &iconRGBColor);

  if (!ps.color)
     fprintf(ps.fp, "%.2f ", (iconRGBColor.red * .59 + 
                              iconRGBColor.green * .30 +
                              iconRGBColor.blue * .11) / 65535.0);
  else
     fprintf(ps.fp, "%.2f %.2f %.2f ", iconRGBColor.red / 65535.0, 
                                       iconRGBColor.green / 65535.0,
                                       iconRGBColor.blue  / 65535.0);

  vnscoordtopscoord(obj->info.x, obj->info.y, &x, &y);
  fprintf(ps.fp, "%d %d %s\n", x + ICONXINDENT, y + ICONYINDENT, psProc);

} /*printPSIcon*/

/******************************************************************************/
static void printPSIconsDef(width, height, data, procName)

int  width;      /*bitmap width*/
int  height;     /*bitmap height*/
char *data;      /*data for the bitmap*/
char *procName;  /*postscript function name*/

/*ps function for printing bitmap icon definitions*/

{
  short count; /*number of elements printed*/
  short lcv;   /*loop control variable*/
 
  count = 1; 
  fprintf(ps.fp, "/%s\n", procName);
  fprintf(ps.fp, "{\n");
  fprintf(ps.fp, "gsave\n");
  fprintf(ps.fp, "translate\n");

  if (!ps.color)
    fprintf(ps.fp, "setgray\n");
  else
    fprintf(ps.fp, "setrgbcolor\n");

  fprintf(ps.fp, "0 -%d translate\n", height);
  fprintf(ps.fp, "%d %d scale\n", width, height);
  fprintf(ps.fp, "%d %d true [%d 0 0 -%d 0 %d]\n", width, height, width,
                                                   height, height);
  fprintf(ps.fp, "{<\n");

  for (lcv = 0; lcv < (width / 8.0 + 0.5) * height; lcv++)
     printPixelVal((UCHAR)data[lcv], &count);

  fprintf(ps.fp, ">}\n");
  fprintf(ps.fp, "imagemask\n");
  fprintf(ps.fp, "grestore\n");
  fprintf(ps.fp, "} def\n");
  return;

} /*printPSIconsDef*/

/******************************************************************************/
static void printWordWrapDefs()

/*ps functions for handling word wraping and spacing*/

{
  fprintf(ps.fp, "/wordbreak (\\n) def\n\n");

  fprintf(ps.fp,  "/whiteSpace\n");
  fprintf(ps.fp, "{\n");
  fprintf(ps.fp, "  /eos nextword length 1 sub def\n");
  fprintf(ps.fp, "  /cnt 0 def\n");
  fprintf(ps.fp, "  {\n");
  fprintf(ps.fp, "    cnt eos le\n");
  fprintf(ps.fp, "    {\n");
  fprintf(ps.fp, "      /nextchar nextword cnt 1 getinterval def\n");
  fprintf(ps.fp, "      nextchar (\\t) eq nextchar ( ) eq or\n");
  fprintf(ps.fp, "      {\n");
  fprintf(ps.fp, "        nextchar (\\t) eq\n");
  fprintf(ps.fp, "        {\n");
  fprintf(ps.fp, "          /curwidth curwidth 36 add def\n");
  fprintf(ps.fp, "          /curX curX 36 add def\n");
  fprintf(ps.fp, "        }\n");
  fprintf(ps.fp, "        {\n");
  fprintf(ps.fp, "          /curwidth curwidth breakwidth add def\n");
  fprintf(ps.fp, "          /curX curX breakwidth add def\n");
  fprintf(ps.fp, "        } ifelse\n");
  fprintf(ps.fp, "      }\n");
  fprintf(ps.fp, "      { exit} ifelse\n");
  fprintf(ps.fp, "      /cnt cnt 1 add def\n");
  fprintf(ps.fp, "    }\n");
  fprintf(ps.fp, "    { exit } ifelse\n");
  fprintf(ps.fp, "  } loop\n");
  fprintf(ps.fp, "  /nextword nextword cnt eos cnt sub 1 add getinterval def\n");
  fprintf(ps.fp, "} def\n");

  fprintf(ps.fp, "/breakIfEOLN\n");
  fprintf(ps.fp, "{ /wordwidth nextword stringwidth pop def\n");
  fprintf(ps.fp, "  curwidth wordwidth add linewidth gt curX startX ne and\n");
  fprintf(ps.fp, "  { /startY startY pointSize sub def\n");
  fprintf(ps.fp, "    startX startY moveto\n");
  fprintf(ps.fp, "    nextword show\n");
  fprintf(ps.fp, "    /curwidth wordwidth def\n");
  fprintf(ps.fp, "    /curX wordwidth startX add def\n");
  fprintf(ps.fp, "  }\n");
  fprintf(ps.fp, "  { curX startY moveto\n");
  fprintf(ps.fp, "    nextword show\n"); 
  fprintf(ps.fp, "    /curwidth curwidth wordwidth add def\n");
  fprintf(ps.fp, "    /curX curX wordwidth add def\n");
  fprintf(ps.fp, "  } ifelse\n");
  fprintf(ps.fp, "} def\n"); 

  fprintf(ps.fp, "/WordWrap\n");
  fprintf(ps.fp, "{ /linewidth exch def\n");
  fprintf(ps.fp, "  /textstring exch def\n");
  fprintf(ps.fp, "  /breakwidth ( ) stringwidth pop def\n");
  fprintf(ps.fp, "  /curwidth 0 def\n");
  fprintf(ps.fp, "  /curX startX def\n");
  fprintf(ps.fp, "  /restoftext textstring def\n");
  fprintf(ps.fp, "  { restoftext wordbreak search\n");
  fprintf(ps.fp, "    { /nextword exch def pop\n");
  fprintf(ps.fp, "      /restoftext exch def\n");
  fprintf(ps.fp, "      whiteSpace\n"); 
  fprintf(ps.fp, "      breakIfEOLN\n");
  fprintf(ps.fp, "    }\n");
  fprintf(ps.fp, "    { /nextword exch def\n");
  fprintf(ps.fp, "      whiteSpace\n");
  fprintf(ps.fp, "      breakIfEOLN\n");
  fprintf(ps.fp, "      exit\n");
  fprintf(ps.fp, "    } ifelse\n");
  fprintf(ps.fp, "  } loop\n");
  fprintf(ps.fp, "} def\n");

} /*printWordWrapDefs*/

/******************************************************************************/
static void prolog()

/*Generate the prolog at the top of the postscript file*/

{
  printHeader();
  printWordWrapDefs();
  printPSIconsDef(badlink_width, badlink_height, badlink_bits, "doLockLink");
  printPSIconsDef(dlink_width, dlink_height, dlink_bits, "doDangleLink");
  printPSIconsDef(link_width, link_height, link_bits, "doLink");
  printPSIconsDef(action_width, action_height, action_bits, "doActionLink");
  printPSIconsDef(qmark_width, qmark_height, qmark_bits, "doEmptyActionLink");
  printImageDefs();
  textClipRect();
  /*fprintf(ps.fp, "/Landscape {%d 0 translate 90 rotate} def\n",
                 (int)((ps.pwidth + ps.physRMargin + ps.physLMargin) * 72));*/
  fprintf(ps.fp, "/Landscape {%d 0 translate 90 rotate} def\n",
                 (int)(ps.pwidth * 72));
  fprintf(ps.fp, "%%%%EndProlog\n");
  return;

} /*prolog*/

/******************************************************************************/
static void setup(x, y)

float x; /*x position to translate to*/
float y; /*y position to translate to*/

/*generates the page setup for the postscript file*/

{
  fprintf(ps.fp, "%%%%BeginSetup\n");

  fprintf(ps.fp, "/inch {72 mul} def\n");
  fprintf(ps.fp, "/leftmargin  %.3f inch def\n", currState.leftMargin);
  fprintf(ps.fp, "/rightmargin %.3f inch def\n", currState.rightMargin);
  fprintf(ps.fp, "/topmargin   %.3f inch def\n", currState.topMargin);
  fprintf(ps.fp, "/botmargin   %.3f inch def\n", currState.bottomMargin);
  fprintf(ps.fp, "/pagewidth   %.3f inch def\n", currState.pWidth);
  fprintf(ps.fp, "/pageheight  %.3f inch def\n", currState.pHeight);

  /*orient page*/

  if (currState.orientation == P_LANDSCAPE)
     fprintf(ps.fp, "Landscape\n");

  /*Set the clipping path definitions. This is only meaningful for the clip
   *to page method of printing as all other methods print the whole page*/

  if (currState.method == P_CLIP_PAGE)
   {
     fprintf(ps.fp, "newpath\n");
     fprintf(ps.fp, " leftmargin botmargin moveto\n");
     fprintf(ps.fp, " 0 pageheight rlineto\n");
     fprintf(ps.fp, " pagewidth rightmargin sub leftmargin sub 0 rlineto\n");
     fprintf(ps.fp, " 0 pageheight neg rlineto\n");
     fprintf(ps.fp, "closepath clip\n");
   }

  /*translate to starting location*/

  fprintf(ps.fp, "%d %d translate\n", (int)(x * 72), 
                                      (int)(y * 72));

  fprintf(ps.fp, "%%%%EndSetup\n");

} /*setup*/

/******************************************************************************/
static void setupOrientation(orientation, xBounds, yBounds)

P_Orientation orientation;  /*orientation mode*/
short         xBounds;      /*object extents in the x dir*/
short         yBounds;      /*object extents in the x dir*/

/*setsup the margins/width and height for a page depending on the orientation
selected by the user. If the selected orientation is automatic, the driver
will select the best orientation for the page*/

{
  int printHeight;  /*printable height on the page*/
  int printWidth;   /*printable width on the page*/
  
  currState.orientation = orientation;

  switch (orientation)
   {
     case P_PORTRAIT:
        currState.pWidth = ps.pwidth;
        currState.pHeight = ps.pheight;
        currState.physLMargin = ps.physLMargin;
        currState.physRMargin = ps.physRMargin;
        currState.physTMargin = ps.physTMargin;
        currState.physBMargin = ps.physBMargin;
        break;

     case P_LANDSCAPE:
        currState.pWidth = ps.pheight;
        currState.pHeight = ps.pwidth;
        currState.physLMargin = ps.physBMargin;
        currState.physRMargin = ps.physTMargin;
        currState.physTMargin = ps.physLMargin;
        currState.physBMargin = ps.physRMargin;
        break;

     case P_AUTO:

        /*calculate the printable page area in the x and y directions*/

        printHeight = (int)((ps.pheight - (ps.physTMargin + 
                         ((ps.topMargin - ps.physTMargin > 0) ?
                          ps.topMargin - ps.physTMargin : 0)) -
                         (ps.physBMargin +
                         ((ps.bottomMargin - ps.physBMargin > 0) ?
                          ps.bottomMargin - ps.physBMargin : 0))) * 72);

        printWidth = (int)((ps.pwidth - (ps.physLMargin + 
                         ((ps.leftMargin - ps.physLMargin > 0) ?
                          ps.leftMargin - ps.physLMargin : 0)) -
                         (ps.physRMargin +
                         ((ps.rightMargin - ps.physRMargin > 0) ?
                          ps.rightMargin - ps.physRMargin : 0))) * 72);

        /*if the horizontal direction exceeds the printable area AND the
         *ratio of extents to area is greatest in the horizontal direction,
         *then invoke landscape printing, otherwise invoke portrait printing*/

        if (xBounds > printWidth && xBounds / printWidth > 
            yBounds / printHeight)
           setupOrientation(P_LANDSCAPE, 0, 0);
        else
           setupOrientation(P_PORTRAIT, 0, 0);

        break;
   }

  /*calculate user margins and print dimensions, Note that in the case of auto
   *orientation, this code is executed twice, but I thought this was better than
   *having this block of code replicated twice in this function*/

  currState.userLMargin = ps.leftMargin * (currState.scale / 100);
  currState.userRMargin = ps.rightMargin * (currState.scale / 100);
  currState.userTMargin = ps.topMargin * (currState.scale / 100);
  currState.userBMargin = ps.bottomMargin * (currState.scale / 100);
  currState.xPrintDim = (currState.pWidth - (currState.physLMargin +
                         ((currState.userLMargin - currState.physLMargin > 0) ?
                          currState.userLMargin - currState.physLMargin : 0)) -
                         (currState.physRMargin +
                         ((currState.userRMargin - currState.physRMargin > 0) ?
                          currState.userRMargin - currState.physRMargin : 0)));
  currState.yPrintDim = (currState.pHeight - (currState.physTMargin +
                         ((currState.userTMargin - currState.physTMargin > 0) ?
                          currState.userTMargin - currState.physTMargin : 0)) -
                         (currState.physBMargin +
                         ((currState.userBMargin - currState.physBMargin > 0) ?
                          currState.userBMargin - currState.physBMargin : 0)));


} /*setupOrientation*/

/*****************************************************************************/
static void textClipRect()

/*ps function for setting object bounding box clipping boundry*/

{
  fprintf(ps.fp, "/textClipRect %%stack x y width height gray\n");
  fprintf(ps.fp, "{ /c exch def\n");
  fprintf(ps.fp, "  /h exch def\n");
  fprintf(ps.fp, "  /w exch def\n");
  fprintf(ps.fp, "  /y   exch def\n");
  fprintf(ps.fp, "  /x   exch def\n");
  fprintf(ps.fp, "  x y h sub moveto\n");
  fprintf(ps.fp, "  currentpoint translate\n");
  fprintf(ps.fp, "  newpath\n");
  fprintf(ps.fp, "    0 0 moveto\n");
  fprintf(ps.fp, "    w 0 rlineto\n");
  fprintf(ps.fp, "    0 h rlineto\n");
  fprintf(ps.fp, "    w neg 0 rlineto\n");
  fprintf(ps.fp, "  closepath\n");
  fprintf(ps.fp, "  gsave\n");
  fprintf(ps.fp, "    c setgray\n");
  fprintf(ps.fp, "    fill\n");
  fprintf(ps.fp, "  grestore\n");

  if (ps.show_outline)
   {
     fprintf(ps.fp, "  gsave\n");
     fprintf(ps.fp, "    0 setgray\n");
     fprintf(ps.fp, "    stroke\n");
     fprintf(ps.fp, "  grestore\n");
   }

  fprintf(ps.fp, "  clip\n");
  fprintf(ps.fp, "} def\n");
  return;

} /*textClipRect*/

/******************************************************************************/
static void vnscoordtopscoord(x, y, psx, psy)

int x, y;        /*vns x & y coordinates*/
int *psx, *psy;  /*postscript x & ycoordinates*/

/*Converts VNS coordinates to Postscript coordinates. VNS coordinates have their
origin at the top left corner with x increasing +ve to the right and y
increasing positive downwards. Postscript coordinates have their orgin at the
lower left with x and y increasing as in traditional cartesian coordinates*/

{
  /*for each postscript point account for the top margin and the fact that ther
   *72dpi per inch in postscript coordinates*/

  *psy = psYOffset - y - (currState.topMargin * 72);
  *psx = x;

} /*vnscoordtopscoord*/

/*******************************************************************************
Private undefines
*******************************************************************************/

#undef PAGE_TO_PS_C

