/*
*
* posttek - PostScript translator for tektronix 4014 files
*
* A program that can be used to translate tektronix 4014 files into PostScript.
* Most of the code was borrowed from the tektronix 4014 emulator that was written
* for DMDs. Things have been cleaned up some, but there's still plently that
* could be done.
*
* The PostScript prologue is copied from *prologue before any of the input files
* are translated. The program expects that the following PostScript procedures
* are defined in that file:
*
*      setup
*
*        mark ... setup -
*
*          Handles special initialization stuff that depends on how the program
*          was called. Expects to find a mark followed by key/value pairs on the
*          stack. The def operator is applied to each pair up to the mark, then
*          the default state is set up.
*
*      pagesetup
*
*        page pagesetup -
*
*          Does whatever is needed to set things up for the next page. Expects
*          to find the current page number on the stack.
*
*      v
*
*        mark dx1 dy1 ... dxn dyn x y v mark
*
*          Draws the vector described by the numbers on the stack. The top two
*          numbers are the starting point. The rest are relative displacements
*          from the preceeding point. Must make sure we don't put too much on
*          the stack!
*
*      t
*
*        x y string t -
*
*          Prints the string that's on the top of the stack starting at point
*          (x, y).
*
*      p
*
*        x y p -
*
*          Marks the point (x, y) with a circle whose radius varies with the
*          current intensity setting.
*
*      i
*
*        percent focus i -
*
*          Changes the size of the circle used to mark individual points to
*          percent of maximum for focused mode (focus=1) or defocused mode
*          (focus=0). The implementation leaves much to be desired!
*
*      l
*
*        mark array l mark
*
*          Set the line drawing mode according to the description given in array.
*          The arrays that describe the different line styles are declared in
*          STYLES (file posttek.h). The array really belongs in the prologue!
*
*      w
*
*        n w -
*
*          Adjusts the line width for vector drawing. Used to select normal (n=0)
*          or defocused (n=1) mode.
*
*      f
*
*        size f -
*
*          Changes the size of the font that's used to print characters in alpha
*          mode. size is the tektronix character width and is used to choose an
*          appropriate point size in the current font.
*
*      done
*
*        done
*
*          Makes sure the last page is printed. Only needed when we're printing
*          more than one page on each sheet of paper.
*
* The default line width is zero, which forces lines to be one pixel wide. That
* works well on 'write to black' engines but won't be right for 'write to white'
* engines. The line width can be changed using the -w option, or you can change
* the initialization of linewidth in the prologue.
*
* Many default values, like the magnification and orientation, are defined in
* the prologue, which is where they belong. If they're changed (by options), an
* appropriate definition is made after the prologue is added to the output file.
* The -P option passes arbitrary PostScript through to the output file. Among
* other things it can be used to set (or change) values that can't be accessed by
* other options.
*
*/

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>

#include "comments.h"                   /* PostScript file structuring comments */
#include "gen.h"                        /* general purpose definitions */
#include "path.h"                       /* for the prologue */
#include "ext.h"                        /* external variable definitions */
#include "posttek.h"                    /* control codes and other definitions */

char    *optnames = "a:c:f:m:n:o:p:w:x:y:A:C:E:J:L:P:R:DI";

char    *prologue = POSTTEK;            /* default PostScript prologue */
char    *formfile = FORMFILE;           /* stuff for multiple pages per sheet */

int     formsperpage = 1;               /* page images on each piece of paper */
int     copies = 1;                     /* and this many copies of each sheet */

int     charheight[] = CHARHEIGHT;      /* height */
int     charwidth[] = CHARWIDTH;        /* and width arrays for tek characters */
int     tekfont = TEKFONT;              /* index into charheight[] and charwidth[] */

char    intensity[] = INTENSITY;        /* special point intensity array */
char    *styles[] = STYLES;             /* description of line styles */
int     linestyle = 0;                  /* index into styles[] */
int     linetype = 0;                   /* 0 for normal, 1 for defocused */

int     dispmode = ALPHA;               /* current tektronix state */
int     points = 0;                     /* points making up the current vector */
int     characters = 0;                 /* characters waiting to be printed */
int     pen = UP;                       /* just for point plotting */
int     margin = 0;                     /* left edge - ALPHA state */

Point   cursor;                         /* should be current cursor position */

Fontmap fontmap[] = FONTMAP;            /* for translating font names */
char    *fontname = "Courier";          /* use this PostScript font */

int     page = 0;                       /* page we're working on */
int     printed = 0;                    /* printed this many pages */

FILE    *fp_in;                         /* read from this file */
FILE    *fp_out = stdout;               /* and write stuff here */
FILE    *fp_acct = NULL;                /* for accounting data */

/*****************************************************************************/

main(agc, agv)

   int         agc;
   char        *agv[];

{

/*
*
* A simple program that can be used to translate tektronix 4014 files into
* PostScript. Most of the code was taken from the DMD tektronix 4014 emulator,
* although things have been cleaned up some.
*
*/

   argv = agv;                         /* so everyone can use them */
   argc = agc;

   prog_name = argv[0];                /* just for error messages */

   init_signals();                     /* sets up interrupt handling */
   header();                           /* PostScript header comments */
   options();                          /* handle the command line options */
   setup();                            /* for PostScript */
   arguments();                        /* followed by each input file */
   done();                             /* print the last page etc. */
   account();                          /* job accounting data */

   exit(x_stat);                       /* nothing could be wrong */

}   /* End of main */

/*****************************************************************************/

init_signals()

{

/*
*
* Make sure we handle interrupts.
*
*/

   if ( signal(SIGINT, interrupt) == SIG_IGN )  {
       signal(SIGINT, SIG_IGN);
       signal(SIGQUIT, SIG_IGN);
       signal(SIGHUP, SIG_IGN);
   } else {
       signal(SIGHUP, interrupt);
       signal(SIGQUIT, interrupt);
   }   /* End else */

   signal(SIGTERM, interrupt);

}   /* End of init_signals */

/*****************************************************************************/

header()

{

   int         ch;                     /* return value from getopt() */
   int         old_optind = optind;    /* for restoring optind - should be 1 */

/*
*
* Scans the option list looking for things, like the prologue file, that we need
* right away but could be changed from the default. Doing things this way is an
* attempt to conform to Adobe's latest file structuring conventions. In particular
* they now say there should be nothing executed in the prologue, and they have
* added two new comments that delimit global initialization calls. Once we know
* where things really are we write out the job header, follow it by the prologue,
* and then add the ENDPROLOG and BEGINSETUP comments.
*
*/

   while ( (ch = getopt(argc, argv, optnames)) != EOF )
       if ( ch == 'L' )
           prologue = optarg;
       else if ( ch == '?' )
           error(FATAL, "");

   optind = old_optind;                /* get ready for option scanning */

   fprintf(stdout, "%s", CONFORMING);
   fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
   fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
   fprintf(stdout, "%s %s\n", PAGES, ATEND);
   fprintf(stdout, "%s", ENDCOMMENTS);

   if ( cat(prologue) == FALSE )
       error(FATAL, "can't read %s", prologue);

   fprintf(stdout, "%s", ENDPROLOG);
   fprintf(stdout, "%s", BEGINSETUP);
   fprintf(stdout, "mark\n");

}   /* End of header */

/*****************************************************************************/

options()

{

   int         ch;                     /* value returned by getopt() */

/*
*
* Reads and processes the command line options. Added the -P option so arbitrary
* PostScript code can be passed through. Expect it could be useful for changing
* definitions in the prologue for which options have not been defined.
*
*/

   while ( (ch = getopt(argc, argv, optnames)) != EOF )  {
       switch ( ch )  {
           case 'a':                   /* aspect ratio */
                   fprintf(stdout, "/aspectratio %s def\n", optarg);
                   break;

           case 'c':                   /* copies */
                   copies = atoi(optarg);
                   fprintf(stdout, "/#copies %s store\n", optarg);
                   break;

           case 'f':                   /* use this PostScript font */
                   fontname = get_font(optarg);
                   fprintf(stdout, "/font /%s def\n", fontname);
                   break;

           case 'm':                   /* magnification */
                   fprintf(stdout, "/magnification %s def\n", optarg);
                   break;

           case 'n':                   /* forms per page */
                   formsperpage = atoi(optarg);
                   fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
                   fprintf(stdout, "/formsperpage %s def\n", optarg);
                   break;

           case 'o':                   /* output page list */
                   out_list(optarg);
                   break;

           case 'p':                   /* landscape or portrait mode */
                   if ( *optarg == 'l' )
                       fprintf(stdout, "/landscape true def\n");
                   else fprintf(stdout, "/landscape false def\n");
                   break;

           case 'w':                   /* line width */
                   fprintf(stdout, "/linewidth %s def\n", optarg);
                   break;

           case 'x':                   /* shift horizontally */
                   fprintf(stdout, "/xoffset %s def\n", optarg);
                   break;

           case 'y':                   /* and vertically on the page */
                   fprintf(stdout, "/yoffset %s def\n", optarg);
                   break;

           case 'A':                   /* force job accounting */
           case 'J':
                   if ( (fp_acct = fopen(optarg, "a")) == NULL )
                       error(FATAL, "can't open accounting file %s", optarg);
                   break;

           case 'C':                   /* copy file straight to output */
                   if ( cat(optarg) == FALSE )
                       error(FATAL, "can't read %s", optarg);
                   break;

           case 'E':                   /* text font encoding */
                   fontencoding = optarg;
                   break;

           case 'L':                   /* PostScript prologue file */
                   prologue = optarg;
                   break;

           case 'P':                   /* PostScript pass through */
                   fprintf(stdout, "%s\n", optarg);
                   break;

           case 'R':                   /* special global or page level request */
                   saverequest(optarg);
                   break;

           case 'D':                   /* debug flag */
                   debug = ON;
                   break;

           case 'I':                   /* ignore FATAL errors */
                   ignore = ON;
                   break;

           case '?':                   /* don't know the option */
                   error(FATAL, "");
                   break;

           default:                    /* don't know what to do for ch */
                   error(FATAL, "missing case for option %c", ch);
                   break;
       }   /* End switch */
   }   /* End while */

   argc -= optind;
   argv += optind;

}   /* End of options */

/*****************************************************************************/

char *get_font(name)

   char        *name;                  /* name the user asked for */

{

   int         i;                      /* for looking through fontmap[] */

/*
*
* Called from options() to map a user's font name into a legal PostScript name.
* If the lookup fails *name is returned to the caller. That should let you choose
* any PostScript font.
*
*/

   for ( i = 0; fontmap[i].name != NULL; i++ )
       if ( strcmp(name, fontmap[i].name) == 0 )
           return(fontmap[i].val);

   return(name);

}   /* End of get_font */

/*****************************************************************************/

setup()

{

/*
*
* Handles things that must be done after the options are read but before the
* input files are processed.
*
*/

   writerequest(0, stdout);            /* global requests eg. manual feed */
   setencoding(fontencoding);
   fprintf(stdout, "setup\n");

   if ( formsperpage > 1 )  {
       if ( cat(formfile) == FALSE )
           error(FATAL, "can't read %s", formfile);
       fprintf(stdout, "%d setupforms\n", formsperpage);
   }   /* End if */

   fprintf(stdout, "%s", ENDSETUP);

}   /* End of setup */

/*****************************************************************************/

arguments()

{

/*
*
* Makes sure all the non-option command line arguments are processed. If we get
* here and there aren't any arguments left, or if '-' is one of the input files
* we'll process stdin.
*
*/

   if ( argc < 1 )
       statemachine(fp_in = stdin);
   else  {                             /* at least one argument is left */
       while ( argc > 0 )  {
           if ( strcmp(*argv, "-") == 0 )
               fp_in = stdin;
           else if ( (fp_in = fopen(*argv, "r")) == NULL )
               error(FATAL, "can't open %s", *argv);
           statemachine(fp_in);
           if ( fp_in != stdin )
               fclose(fp_in);
           argc--;
           argv++;
       }   /* End while */
   }   /* End else */

}   /* End of arguments */

/*****************************************************************************/

done()

{

/*
*
* Finished with all the input files, so mark the end of the pages with a TRAILER
* comment, make sure the last page prints, and add things like the PAGES comment
* that can only be determined after all the input files have been read.
*
*/

   fprintf(stdout, "%s", TRAILER);
   fprintf(stdout, "done\n");
   fprintf(stdout, "%s %s\n", DOCUMENTFONTS, fontname);
   fprintf(stdout, "%s %d\n", PAGES, printed);

}   /* End of done */

/*****************************************************************************/

account()

{

/*
*
* Writes an accounting record to *fp_acct provided it's not NULL. Accounting
* is requested using the -A or -J options.
*
*/

   if ( fp_acct != NULL )
       fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);

}   /* End of account */

/*****************************************************************************/

statemachine(fp)

   FILE        *fp;                    /* used to set fp_in */

{

/*
*
* Controls the translation of the next input file. Tektronix states (dispmode)
* are typically changed in control() and esc().
*
*/

   redirect(-1);                       /* get ready for the first page */
   formfeed();
   dispmode = RESET;

   while ( 1 )
       switch ( dispmode )  {
           case RESET:
                   reset();
                   break;

           case ALPHA:
                   alpha();
                   break;

           case GIN:
                   gin();
                   break;

           case GRAPH:
                   graph();
                   break;

           case POINT:
           case SPECIALPOINT:
                   point();
                   break;

           case INCREMENTAL:
                   incremental();
                   break;

           case EXIT:
                   formfeed();
                   return;
       }   /* End switch */

}   /* End of statemachine */

/*****************************************************************************/

reset()

{

/*
*
* Called to reset things, typically only at the beginning of each input file.
*
*/

   tekfont = -1;
   home();
   setfont(TEKFONT);
   setmode(ALPHA);

}   /* End of reset */

/*****************************************************************************/

alpha()

{

   int         c;                      /* next character */
   int         x, y;                   /* cursor will be here when we're done */

/*
*
* Takes care of printing characters in the current font.
*
*/

   if ( (c = nextchar()) == OUTMODED )
       return;

   if ( (c < 040) && ((c = control(c)) <= 0) )
       return;

   x = cursor.x;                       /* where the cursor is right now */
   y = cursor.y;

   switch ( c )  {
       case DEL:
               return;

       case BS:
               if ((x -= charwidth[tekfont]) < margin)
                   x = TEKXMAX - charwidth[tekfont];
               break;

       case NL:
               y -= charheight[tekfont];
               break;

       case CR:
               x = margin;
               break;

       case VT:
               if ((y += charheight[tekfont]) >= TEKYMAX)
                   y = 0;
               break;

       case HT:
       case ' ':
       default:
               if ( characters++ == 0 )
                   fprintf(fp_out, "%d %d (", cursor.x, cursor.y);
               switch ( c )  {
                   case '(':
                   case ')':
                   case '\\':
                       putc('\\', fp_out);

                   default:
                       putc(c, fp_out);
               }   /* End switch */
               x += charwidth[tekfont];
               move(x, y);
               break;
   }   /* End switch */

   if (x >= TEKXMAX) {
       x = margin;
       y -= charheight[tekfont];
   }   /* End if */

   if (y < 0) {
       y = TEKYMAX - charheight[tekfont];
       x -= margin;
       margin = (TEKXMAX/2) - margin;
       if ((x += margin) > TEKXMAX)
           x -= margin;
   }   /* End if */

   if ( y != cursor.y || x != cursor.x )
       text();

   move(x, y);

}   /* End of alpha */

/*****************************************************************************/

graph()

{

   int                 c;              /* next character */
   int                 b;              /* for figuring out loy */
   int                 x, y;           /* next point in the vector */
   static int          hix, hiy;       /* upper */
   static int          lox, loy;       /* and lower part of the address */
   static int          extra;          /* for extended addressing */

/*
*
* Handles things when we're in GRAPH, POINT, or SPECIALPOINT mode.
*
*/

   if ((c = nextchar()) < 040) {
       control(c);
       return;
   }   /* End if */

   if ((c & 0140) == 040) {            /* new hiy */
       hiy = c & 037;
       do
           if (((c = nextchar()) < 040) && ((c = control(c)) == OUTMODED))
               return;
       while (c == 0);
   }   /* End if */

   if ((c & 0140) == 0140) {           /* new loy */
       b = c & 037;
       do
           if (((c = nextchar()) < 040) && ((c = control(c)) == OUTMODED))
               return;
       while (c == 0);
       if ((c & 0140) == 0140) {       /* no, it was extra */
           extra = b;
           loy = c & 037;
           do
               if (((c = nextchar()) < 040) && ((c = control(c)) == OUTMODED))
                   return;
           while (c == 0);
       } else loy = b;
   }   /* End if */

   if ((c & 0140) == 040) {            /* new hix */
       hix = c & 037;
       do
           if (((c = nextchar()) < 040) && ((c = control(c)) == OUTMODED))
               return;
       while (c == 0);
   }   /* End if */

   lox = c & 037;                      /* this should be lox */
   if (extra & 020)
       margin = TEKXMAX/2;

   x = (hix<<7) | (lox<<2) | (extra & 03);
   y = (hiy<<7) | (loy<<2) | ((extra & 014)>>2);

   if ( points > 100 )  {              /* don't put too much on the stack */
       draw();
       points = 1;
   }   /* End if */

   if ( points++ )
       fprintf(fp_out, "%d %d\n", cursor.x - x, cursor.y - y);

   move(x, y);                         /* adjust the cursor */

}   /* End of graph */

/*****************************************************************************/

point()

{

   int         c;                      /* next input character */

/*
*
* Special point mode permits gray scaling by varying the size of the stored
* point, which is controlled by an intensity character that preceeds each point
* address.
*
*/

   if ( dispmode == SPECIALPOINT )  {
       if ( (c = nextchar()) < 040 || c > 0175 )
           return(control(c));

       fprintf(fp_out, "%d %d i\n", intensity[c - ' '], c & 0100);
   }   /* End if */

   graph();
   draw();

}   /* End of point */

/*****************************************************************************/

incremental()

{

   int         c;                      /* for the next few characters */
   int         x, y;                   /* cursor position when we're done */

/*
*
* Handles incremental plot mode. It's entered after the RS control code and is
* used to mark points relative to our current position. It's typically followed
* by one or two bytes that set the pen state and are used to increment the
* current position.
*
*/

   if ( (c = nextchar()) == OUTMODED )
       return;

   if ( (c < 040) && ((c = control(c)) <= 0) )
       return;

   x = cursor.x;                       /* where we are right now */
   y = cursor.y;

   if ( c & 060 )
       pen = ( c & 040 ) ? UP : DOWN;

   if ( c & 04 ) y++;
   if ( c & 010 ) y--;
   if ( c & 01 ) x++;
   if ( c & 02 ) x--;

   move(x, y);

   if ( pen == DOWN )  {
       points = 1;
       draw();
   }   /* End if */

}   /* End of incremental */

/*****************************************************************************/

gin()

{

/*
*
* All we really have to do for GIN mode is make sure it's properly ended.
*
*/

   control(nextchar());

}   /* End of gin */

/*****************************************************************************/

control(c)

   int         c;                      /* check this control character */

{

/*
*
* Checks character c and does special things, like mode changes, that depend
* not only on the character, but also on the current state. If the mode changed
* becuase of c, OUTMODED is returned to the caller. In all other cases the
* return value is c or 0, if c doesn't make sense in the current mode.
*
*/

   switch ( c )  {
       case BEL:
               return(0);

       case BS:
       case HT:
       case VT:
               return(dispmode == ALPHA ? c : 0);

       case CR:
               if ( dispmode != ALPHA )  {
                   setmode(ALPHA);
                   ungetc(c, fp_in);
                   return(OUTMODED);
               } else return(c);

       case FS:
               if ( (dispmode == ALPHA) || (dispmode == GRAPH) )  {
                   setmode(POINT);
                   return(OUTMODED);
               }   /* End if */
               return(0);

       case GS:
               if ( (dispmode == ALPHA) || (dispmode == GRAPH) )  {
                   setmode(GRAPH);
                   return(OUTMODED);
               }   /* End if */
               return(0);

       case NL:
               ungetc(CR, fp_in);
               return(dispmode == ALPHA ? c : 0);

       case RS:
               if ( dispmode != GIN )  {
                   setmode(INCREMENTAL);
                   return(OUTMODED);
               }   /* End if */
               return(0);

       case US:
               if ( dispmode == ALPHA )
                   return(0);
               setmode(ALPHA);
               return(OUTMODED);

       case ESC:
               return(esc());

       case OUTMODED:
               return(c);

       default:
               return(c < 040 ? 0 : c);
   }   /* End switch */

}   /* End of control */

/*****************************************************************************/

esc()

{

   int         c;                      /* next input character */
   int         ignore;                 /* skip it if nonzero */

/*
*
* Handles tektronix escape code. Called from control() whenever an ESC character
* is found in the input file.
*
*/

   do  {
       c = nextchar();
       ignore = 0;
       switch ( c )  {
           case CAN:
                   return(0);

           case CR:
                   ignore = 1;
                   break;

           case ENQ:
                   setmode(ALPHA);
                   return(OUTMODED);

           case ETB:
                   return(0);

           case FF:
                   formfeed();
                   setmode(ALPHA);
                   return(OUTMODED);

           case FS:
                   if ( (dispmode == INCREMENTAL) || ( dispmode == GIN) )
                       return(0);
                   setmode(SPECIALPOINT);
                   return(OUTMODED);

           case SI:
           case SO:
                   return(0);

           case SUB:
                   setmode(GIN);
                   return(OUTMODED);

           case OUTMODED:
                   return(OUTMODED);

           case '8':
           case '9':
           case ':':
           case ';':
                   setfont(c - '8');
                   return(0);

           default:
                   if ( c == '?' && dispmode == GRAPH )
                       return(DEL);
                   if ( (c<'`') || (c>'w') )
                       break;
                   c -= '`';
                   if ( (c & 010) != linetype )
                       fprintf(fp_out, "%d w\n", (linetype = (c & 010))/010);
                   if ( ((c + 1) & 7) >= 6 )
                       break;
                   if ( (c + 1) & 7 )
                       if ( (c & 7) != linestyle )  {
                           linestyle = c & 7;
                           setmode(dispmode);
                           fprintf(fp_out, "%s l\n", styles[linestyle]);
                       }   /* End if */
                   return(0);
       }   /* End switch */

   } while (ignore);

   return(0);

}   /* End of esc */

/*****************************************************************************/

move(x, y)

   int         x, y;                   /* move the cursor here */

{

/*
*
* Moves the cursor to the point (x, y).
*
*/

   cursor.x = x;
   cursor.y = y;

}   /* End of move */

/*****************************************************************************/

setmode(mode)

   int         mode;                   /* this should be the new mode */

{

/*
*
* Makes sure the current mode is properly ended and then sets dispmode to mode.
*
*/

   switch ( dispmode )  {
       case ALPHA:
               text();
               break;

       case GRAPH:
               draw();
               break;

       case INCREMENTAL:
               pen = UP;
               break;
   }   /* End switch */

   dispmode = mode;

}   /* End of setmode */

/*****************************************************************************/

home()

{

/*
*
* Makes sure the cursor is positioned at the upper left corner of the page.
*
*/

   margin = 0;
   move(0, TEKYMAX);

}   /* End of home */

/*****************************************************************************/

setfont(newfont)

   int         newfont;                /* use this font next */

{

/*
*
* Generates the call to the procedure that's responsible for changing the
* tektronix font (really just the size).
*
*/

   if ( newfont != tekfont )  {
       setmode(dispmode);
       fprintf(fp_out, "%d f\n", charwidth[newfont]);
   }   /* End if */

   tekfont = newfont;

}   /* End of setfont */

/*****************************************************************************/

text()

{

/*
*
* Makes sure any text we've put on the stack is printed.
*
*/

   if ( dispmode == ALPHA && characters > 0 )
       fprintf(fp_out, ") t\n");

   characters = 0;

}   /* End of text */

/*****************************************************************************/

draw()

{

/*
*
* Called whenever we need to draw a vector or plot a point. Nothing will be
* done if points is 0 or if it's 1 and we're in GRAPH mode.
*
*/

   if ( points > 1 )                   /* it's a vector */
       fprintf(fp_out, "%d %d v\n", cursor.x, cursor.y);
   else if ( points == 1 && dispmode != GRAPH )
       fprintf(fp_out, "%d %d p\n", cursor.x, cursor.y);

   points = 0;

}   /* End of draw */

/*****************************************************************************/

formfeed()

{

/*
*
* Usually called when we've finished the last page and want to get ready for the
* next one. Also used at the beginning and end of each input file, so we have to
* be careful about exactly what's done.
*
*/

   setmode(dispmode);                  /* end any outstanding text or graphics */

   if ( fp_out == stdout )             /* count the last page */
       printed++;

   fprintf(fp_out, "cleartomark\n");
   fprintf(fp_out, "showpage\n");
   fprintf(fp_out, "saveobj restore\n");
   fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);

   if ( ungetc(getc(fp_in), fp_in) == EOF )
       redirect(-1);
   else redirect(++page);

   fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
   fprintf(fp_out, "/saveobj save def\n");
   fprintf(fp_out, "mark\n");
   writerequest(printed+1, fp_out);
   fprintf(fp_out, "%d pagesetup\n", printed+1);
   fprintf(fp_out, "%d f\n", charwidth[tekfont]);
   fprintf(fp_out, "%s l\n", styles[linestyle]);

   home();

}   /* End of formfeed */

/*****************************************************************************/

nextchar()

{

   int         ch;                     /* next input character */

/*
*
* Reads the next character from the current input file and returns it to the
* caller. When we're finished with the file dispmode is set to EXIT and OUTMODED
* is returned to the caller.
*
*/

   if ( (ch = getc(fp_in)) == EOF )  {
       setmode(EXIT);
       ch = OUTMODED;
   }   /* End if */

   return(ch);

}   /* End of nextchar */

/*****************************************************************************/

redirect(pg)

   int         pg;                     /* next page we're printing */

{

   static FILE *fp_null = NULL;        /* if output is turned off */

/*
*
* If we're not supposed to print page pg, fp_out will be directed to /dev/null,
* otherwise output goes to stdout.
*
*/

   if ( pg >= 0 && in_olist(pg) == ON )
       fp_out = stdout;
   else if ( (fp_out = fp_null) == NULL )
       fp_out = fp_null = fopen("/dev/null", "w");

}   /* End of redirect */

/*****************************************************************************/