/* Original Author:         Andrew Trevorrow
   Implementation: Modula-2 under VAX/UNIX 4.2 BSD
   Date Started:   June, 1986 (based on version 1.5 under VAX/VMS 4.2)

   Description:
   DVItoVDU allows pages from a DVI file produced by TeX82 to be viewed on a
   variety of VDU screens.
   See the DVItoVDU USER GUIDE for details on the user interface.
   See the DVItoVDU SYSTEM GUIDE if you wish to install or modify the program.

   Notes:
 - Debugging code is bracketed by  DEBUG ...  GUBED. 
   Most of this code will be commented out in the final working version.
 - System-dependent code is indicated by  SYSDEP ... 
 - Uncertain, unfinished or kludgey code is indicated by the string "???".
 - Procedures are defined in a top-down manner.  That is, each procedure is
   usually defined as soon as possible after its first use.
 - The above notes are also true for all the imported modules used by DVItoVDU.

   This version converted to C and ported to BSD and System V UNIX by
   some chaps at Kernel Technology up to September 1989.  Contact
   mjh@uk.co.kernel (Mark J. Hewitt) with bug fixes etc.

   Involved were:	Mark J. Hewitt
   			Dave Dixon
			Marc Hadley
*/


/* SYSDEP: Since Modula-2 avoids the problem of system dependence by simply
   not providing any input/output routines etc., the following
   importations are highly VAX/UNIX dependent.
*/

#include "def.h"
#include "version.h"

static char *sccsid[] = "@(#)dvitovdu.c	1.2";

#ifdef USG
#include <string.h>
#include <setjmp.h>
#else
#include <strings.h>
#endif /* USG */
#include <fcntl.h>
#include <ctype.h>
#include <math.h>

#include "dvitovdu.h"
#include "dvireader.h"
#include "screenio.h"

extern void OpenDVIFile(),
            CloseDVIFile(),
            MoveToDVIPage(),
            MoveToNextPage(),
            ClosePXLFile(),
	    MoveToPXLByte(),
	    MoveToPXLDirectory(),
            PurgeFonts(),
	    SortFonts(),
            InterpretPage(),
	    SetConversionFactor(),
            Initscreenio();

extern short OpenPXLFile(),
             MoveToTexPage();

extern int GetPXLByte(),
           GetTwoPXLBytes(),
           SignedPXLPair(),
	   SignedPXLQuad(),
           PixelRound();

extern stringvalue dummyfont,
                   helpname,
                   fontdir,
                   vdu,
                   DVIname;

/* DVItoVDU uses the ScreenIO routines to do all terminal i/o. */

/* InitSysInterface() carries out the task of reading the command line
   and extracting the DVI file name, along with any command options.
*/

/* DVItoVDU uses the routines and data structures defined in DVIReader to move
   about randomly in the DVI file and to interpret pages.
*/

/* DVItoVDU needs to move about randomly in PXL files when getting information
   on the character widths and glyph shapes in a particular font.
   Only one PXL file will be open at any one time.
*/

/* DVItoVDU can work efficiently on a variety of VDUs without having to
   know all the nitty gritty details required to drive them.
   Modula-2's procedure variables and separate compilation facilities provide
   a very nice mechanism for achieving the desired terminal independence.
   The generic VDU parameters and routines are initialized in InitVDUInterface()
*/

/* None integer functions from this module defined here to prevent forward
   referancing */

void Toplevel ();
void Initialize ();
Void MyDVIErrorRoutine ();
void PleaseReport ();
void ClearMessageLine ();
void WaitForReturn ();
Void MySpecialRoutine ();
Void MyPixelTableRoutine ();
void BuildFontSpec ();
void UpdateDVIStatusLine ();
void UpdateWindowStatusLine ();
void WriteDimension ();
void NextCommandLine ();
void WindowMove ();
void BadCommandMessage ();
void NewLocation ();
void WindowUpDown ();
void WindowLeftRight ();
void SetWindowWidth ();
void NewWindowWidth ();
void SetWindowHeight ();
void NewWindowHeight ();
void ProcessPage ();
void ChangeUnits ();
void ShowHelp ();
void ShowStatistics ();
void DisplayPage ();
void DisplayPaperEdges ();
void DisplayRules ();
short   RectangleVisible ();
short   PixelVisible ();
void DisplayChars ();
Void TerseChar ();
Void BoxChar ();
Void FullChar1 ();
Void FullChar2 ();
void OpenFontFile ();
void CheckPageEdges ();
void PaperMessage ();
void Finish ();
int   ShrinkVpos ();
int   ShrinkHpos ();
int   ExpandHpos ();
int   ExpandVpos ();
unsigned int  Length ();
short   GetInteger ();
short   DVIPageFound ();
short   GetDimension ();
short   NextPageFound ();
short   TeXPageFound ();
short   ParseTeXpage ();
short   UserHitsReturn ();

/*******************************************************************************
   DECLARATIONS FOR PROCESSING USER COMMANDS

   Most commands consist of one or two characters and can be entered
   in upper or lowercase.  Multiple commands are processed in the order
   given but we only update the window, if necessary, at the end.
   If a bad command is encountered, any further commands are ignored.
   Some commands can have parameters; they are all dimensions in terms of the
   current units.  Spaces before and after commands and parameters are ignored.
*/

/* Possible commands are:                                                  */
/* i                  a positive integer; display ith DVI page             */

#define   TeXpage   '['		/* start of a TeX page specification: [i0. ... .i9] 
				*/
#define   Next      'N'		/* display next DVI page, depending on direction    
				*/
#define   Forwards  '>'		/* process DVI pages in ascending order             
				*/
#define   Backwards '<'		/* process DVI pages in descending order            
				*/
#define   Window    'W'		/* move window's top left corner to given position  
				*/
#define   Up        'U'		/* move window up a given amount                    
				*/
#define   Down      'D'		/* move window down a given amount                  
				*/
#define   Left      'L'		/* move window left a given amount                  
				*/
#define   Right     'R'		/* move window right a given amount                 
				*/
#define   Hsize     'H'		/* set scaledwd: window's horizontal size           
				*/
#define   Vsize     'V'		/* set scaledht: window's vertical size             
				*/
#define   Terse     'T'		/* display quick and nasty chars at reference points 
				*/
#define   Box       'B'		/* display box outlines of glyphs                   
				*/
#define   Full      'F'		/* display all pixels in glyphs                     
				*/
#define   Inch      'I'		/* get/show dimensions in inches                    
				*/

/* SYSDEP: changed In to Inch because compiler got In confused with IN !!! */
#define   Cm        'C'		/* get/show dimensions in centimetres               
				*/
#define   Mm        'M'		/* get/show dimensions in millimetres               
				*/
#define   PcPtPx    'P'		/* get/show dimensions in picas/points/pixels       
				*/
#define   Help      '?'		/* display help on available commands               
				*/
#define   Show      'S'		/* display useful statistics                        
				*/
#define   Quit      'Q'		/* have a guess                                     
				*/
#define   MAXCOMMSTRING  82
#define   COMMPROMPT     "Command:"
#define   EOL       '\012'	/* SYSDEP: newline character         */
#define CARDINAL(i) (unsigned)(i)

char  commstring[MAXCOMMSTRING];/* holds user responses */

unsigned int commpos;		/* current position in commstring         */
int          commlen;		/* length of commstring                   */

char
      command;			/* starting character of command          */
short
        ascending;		/* initially TRUE; changed by the Forwards/Backwards
				   commands to control how to get the next DVI page 
				*/
int
      maxpix;			/* maximum absolute pixel value; depends on
				   resolution                  */
 /* These flags are used to handle multiple commands:                       */
short
        screenjustcleared,	/* has screen just been cleared?          */
        paintDVIStatus,		/* does DVI status line need updating?    */
        paintWindowStatus,	/* does window status line need updating? */
        paintwindow,		/* does window region need updating?      */
        pageoffpaper,		/* is page off paper?                     */
        badcommand;		/* was there a bad command?               */

int   NumParameters;		/* argc */
char **Parameters;		/* argv */


/*******************************************************************************
   DECLARATIONS FOR DISPLAYING A PAGE

   The reference points of characters and rules on a page are stored as
   pairs of horizontal and vertical paper pixel coordinates.
   The paper coordinate scheme is described in detail in DVIReader.
   The screen coordinate scheme is described in detail in VDUInterface.
   To update the window region, DVItoVDU maps visible paper pixels
   to screen pixels using windowh and windowv to help with translation,
   and windowwd and windowht to help with scaling.
   What the user sees depends on the current displaymode, the current size
   of the window region (scaledwd by scaledht are in paper pixels and determine
   the horizontal and vertical scaling factors), and the current paper position
   of the window region's top left corner; i.e., (windowleft,windowtop).

   A NOTE ON THE SCALING METHOD USED BY DVItoVDU:
   We desire the following conditions when scaling paper pixels to
   screen pixels:
   1. Rules/glyphs having the same top/bottom/left/right paper coordinates also
      have the same screen coordinates (e.g., to ensure baselines line up).
      This condition is incompatible with a rule/glyph staying the same
      width and height as the window position changes!  Too bad.
   2. After being scaled, visible pixel positions must not exceed the
      window region's edges.  In our case, only the bottom and right edges are
      a problem because scaling starts at the top left corner of the window.
      For efficiency, we use two different scaling functions depending on
      whether the h/vscalefactors are < 1.0 or not.
   3. Scaled heights and widths must be > 0 even when h/vscalefactors
      approach 0.  If h/vscalefactors are > 1.0 then the width/height of
      paper pixels increase accordingly.
*/

#define   ABORTKEY EOL		/* user aborts display by hitting RETURN  */

unsigned short  displaymode;

#define TERSEMODE 0x00		/* show quick and nasty chars at ref pts  */
#define BOXMODE   0x01		/* show box outlines of glyphs            */
#define FULLMODE  0x02		/* show all pixels in glyphs              */


unsigned short  currentunits;

#define INUNITS   0x00		/* get/show dimensions in inches          */
#define CMUNITS   0x01		/* get/show dimensions in centimetres     */
#define MMUNITS   0x02		/* get/show dimensions in millimetres     */
#define PCUNITS   0x03		/* get/show dimensions in picas           */
#define PTUNITS   0x04		/* get/show dimensions in points          */
#define PXUNITS   0x05		/* get/show dimensions in pixels          */

int
      papertop,
      paperleft,
      paperbottom,
      paperright,		/* these define the edges of the paper    */
      windowtop,
      windowleft,
      windowbottom,
      windowright,		/* these define the current window edges  */
      scaledht,			/* current window height in paper pixels  */
      scaledwd;			/* current window width in paper pixels   */

short
        allpagevisible,		/* is all of page visible in window?      */
        outsidepage;		/* is entire window outside page?         */

double
        vscalefactor,		/* windowht / scaledht                    */
        hscalefactor;		/* windowwd / scaledwd                    */

 /* Expand/ShrinkHpos and Expand/ShrinkVpos are assigned to these procedure
    variables depending on the values of h/vscalefactor.          */

int   (*ScaleVpos) (), (*ScaleHpos) ();

 /* TerseChar() BoxChar()or FullChar1()2 are assigned to DisplayOneChar depending on
    the current displaymode (which the user can change by hitting the Terse/Box/Full
    commands while DisplayChars()is executing).  */

Void (*DisplayOneChar) ();

ruleinfoptr thisruleinfo;	/* current rule info in rulelist          */
fontinfoptr unusedfont;		/* first unused font in sorted fontlist   */
fontinfoptr thisfontinfo;	/* current font info in sorted fontlist   */
charinfoptr thischarinfo;	/* current char info in charlist          */

unsigned int
              thischar;		/* current index into current chartable   */

short
        fontopen,		/* is thisfontinfo^.fontspec open?        */
        useraborted,		/* did user abort page display?           */
        charvisible;		/* was character actually displayed?      */

#ifdef USG
jmp_buf commprompt_env;			/* jump buffer for usgio:handleint() */
#endif /* USG */

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

main (argc, argv)
int   argc;
char *argv[];
{
  NumParameters = argc;
  Parameters = argv;

  InitReader ();
  Initscreenio ();
  Toplevel ();
  PurgeFonts ();
  exitprog(0);
}

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

int   GetParameter (num, value)
int   num;
char  value[];
{
  (void) strcpy (value, Parameters[num]);
  return (strlen (value));
}

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

void Toplevel ()
{
/* Note that the implementation blocks of all imported modules have already
   been executed by this stage.
*/

  InitSysInterface ();		/* initialize DVIname, resolution, vdu, mag, etc. */
  InitVDUInterface ();		/* initialize generic VDU routines and parameters */
  Initialize ();		/* uses some of the above parameters */
  DVIErrorRoutine = MyDVIErrorRoutine;/* called by DVIReader upon an error */
  OpenDVIFile (DVIname);	/* initialize DVImag, etc.           */
  if (mag == 0)			/* no override given, so use DVImag  */
    mag = DVImag;

/* Having decided on what magnification value to use, we can now help
   DVIReader calculate the number of pixels per DVI unit.
*/
  SetConversionFactor (resolution, mag);
  SpecialRoutine = MySpecialRoutine;/* called by InterpretPage() */
  PixelTableRoutine = MyPixelTableRoutine;/* called by InterpretPage() */
  (*StartText) ();
  (*ClearScreen) ();
  UpdateDVIStatusLine ();
  UpdateWindowStatusLine ();
  do
  {
#ifdef USG
    (void) setjmp(commprompt_env);
#endif /* USG */
    NextCommandLine ();
  }
  while (command != Quit);
  Finish ();
}

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

void Initialize ()
{

/* TeX will not generate dimensions > than about 38 feet, so we
   choose an absolute limit on our dimensions to be 40 feet.
   Should we check for stupid resolution, mag, paperht, paperwd values???
*/
  maxpix = 40 * 12 * resolution;

/* top left corner of paper is fixed at (-1",-1") */
  papertop = -(int) (resolution);
  paperleft = -(int) (resolution);
  paperbottom = papertop + (int) (paperht) - 1;
  paperright = paperleft + (int) (paperwd) - 1;

/* User sees the following status values before requesting the first page.
   Note that DVIReader has already initialized currDVIpage and currTeXpage.
*/
  ascending = TRUE;		/* process DVI pages in ascending order */
  displaymode = TERSEMODE;
  windowtop = 0;		/* window location */
  windowleft = 0;
  scaledht = windowht;		/* window size is initially unscaled */
  scaledwd = windowwd;
  minhp = 0;
  minvp = 0;			/* page location */
  maxhp = 0;
  maxvp = 0;
  currentunits = INUNITS;	/* units are initially inches */

/* initialize the scaling routines */
  vscalefactor = 1.0;
  hscalefactor = 1.0;
  ScaleVpos = ShrinkVpos;	/* use when vscalefactor <= 1.0 */
  ScaleHpos = ShrinkHpos;	/* use when hscalefactor <= 1.0 */
}

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

Void MyDVIErrorRoutine (DVIerror)
DVIerrorcodes DVIerror;
{
/* DVIErrorRoutine for DVIReader which has just detected one of the errors
   described in DVIReader's definition module.
*/

  switch (DVIerror)
  {
/* these errors are detected in OpenDVIFile() they are considered fatal */
    case DVIunopened: 
      (*ResetVDU) ();		/* do before message since it might erase screen! */
      WriteString ("Couldn't open ");
      WriteString (DVIname);
      Write ('!');
      WriteLn ();
      RestoreTerminal ();
      exitprog (1);
      break;

    case DVIempty: 
      (*ResetVDU) ();
      WriteString (DVIname);
      WriteString (" is empty!");
      WriteLn ();
      RestoreTerminal ();
      (void) exitprog (1);
      break;

    case DVIbadid: 
      (*ResetVDU) ();
      WriteString (DVIname);
      WriteString (" is not a valid DVI file!");
      WriteLn ();
      RestoreTerminal ();
      exitprog (1);
      break;

    case DVIstackoverflow: 
      (*ResetVDU) ();
      WriteString ("Stack capacity exceeded!");
      PleaseReport ();
      WriteLn ();
      RestoreTerminal ();
      exitprog (1);
      break;

/* this error is detected in InterpretPage() we warn user but continue */
    case DVIbadchar: 
      ClearMessageLine ();
      WriteString ("Ignoring unknown character from ");
      WriteString (currfont->fontspec);
      Write ('!');
      WaitForReturn ();
      break;

/* this error should never happen */

    case DVIcatastrophe: 
      (*ResetVDU) ();
      WriteLn ();
      WriteString ("Something awful has happened!");
      PleaseReport ();
      WriteLn ();
      RestoreTerminal ();
      exitprog (1);
      break;
    default: 

#ifdef DEBUG
      (*ResetVDU)();
      WriteLn();
      WriteString("Bug in MyDVIErrorRoutine");
      PleaseReport();
      WriteLn();
      RestoreTerminal();
      exitprog (1);
#endif /* DEBUG */
      break;
  }
}

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

void PleaseReport ()
{
  WriteString ("   Please tell your local TeXnician.");
}

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

void ClearMessageLine ()
{
/* Clear message line and move cursor to start of line.
   We don't show any message here; that will usually be done
   immediately after calling this routine.
*/

  (*ClearTextLine) (messagel);
  (*MoveToTextLine) (messagel);
}

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

void WaitForReturn ()
{
/* DVItoVDU has just displayed an important message.
   To ensure message is seen we wait for user to hit the RETURN key.
*/
  char  ch;

  WriteString ("   RETURN:");
  WriteBuffer ();
  do
  {
    Read (&ch);
  }
  while (ch != EOL);
}

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

Void MySpecialRoutine (totalbytes, NextDVIByte)
int   totalbytes;
GetByteFunction NextDVIByte;
{
/* SpecialRoutine for DVIReader which has just seen a \special command while
   interpreting a page.  It passes the number of bytes in the command and a
   function to return their values one at a time.
*/

  int   i, next;

  ClearMessageLine ();

/* SYSDEP: compiler treats \ in a string as special; need \\ to write \ */

  WriteString ("Ignoring \\special command: ");
  for (i = 1; i <= totalbytes; i++)
  {
    next = NextDVIByte ();	/* get next byte */
    if (i <= 20)		/* display up to 1st 20 bytes */
      if ((next >= ' ') && (next <= '~'))
	Write (next);
      else
      {
	Write ('^');
	if (next < ' ')
	  Write (next + 64);
	else
	  Write ('?');
      }
  }
  if (totalbytes > 20)
    WriteString ("...");
  WaitForReturn ();
}

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

Void MyPixelTableRoutine ()
{

/* SYSDEP: PixelTableRoutine for DVIReader which has just allocated a new
   pixeltable for currfont^.  DVIReader calls this routine from InterpretPage
   only ONCE per font (the first time the font is used).
   We get the pixeltable information from the font file given by fontspec.
   (If this is the first time we've seen the font then we build fontspec first.
   Note that the Show command also requires fontspec to be built.)
   If we can't open the PXL file, we return dummyfont values but using the
   current font's scaledsize.
*/
  unsigned int  i;
  int   alpha, beta, b0, b1, b2, b3;/* 4 bytes in fix width */

  if (currfont->fontspeclen == 0)/* need to build fontspec */
    BuildFontSpec (currfont);

  ClearMessageLine ();

  if (OpenPXLFile (currfont->fontspec))
  {
    WriteString ("Loading font data from ");
    WriteString (currfont->fontspec);
    WriteLn ();
  }
  else
    if (OpenPXLFile (dummyfont))
    {
    /* we return a pixeltable with dummyfont values */
      WriteString ("Couldn't open ");
      WriteString (currfont->fontspec);
      WriteString ("!   Loading dummy font.");
      WaitForReturn ();
      ClearMessageLine ();
      WriteBuffer ();		/* user RETURN clears message line immediately */
    }
    else
    {
      (*ResetVDU) ();
      WriteLn ();
      WriteString ("Couldn't open dummy font ");
      WriteString (dummyfont);
      Write ('!');
      WriteLn ();
      RestoreTerminal ();
      (void) exitprog (1);
    }
/* move to first byte of font directory */
  MoveToPXLDirectory ();
  for (i = 0; i <= MAXTEXCHAR; i++)
  {
    currfont->pixelptr[i].wd = GetTwoPXLBytes ();
    currfont->pixelptr[i].ht = GetTwoPXLBytes ();
    currfont->pixelptr[i].xo = SignedPXLPair ();
    currfont->pixelptr[i].yo = SignedPXLPair ();
    currfont->pixelptr[i].mapadr = SignedPXLQuad ();
  /* word (not byte!) offset in PXL file */
    b0 = GetPXLByte ();		/* should be 0 or 255 */
    b1 = GetPXLByte ();
    b2 = GetPXLByte ();
    b3 = GetPXLByte ();
  /* Convert the fix width into the corresponding dwidth and pwidth values using the
     method recommended in DVITYPE. WARNING: DVI translators that read RST files
     will have to use a different method because the widths in such files are NOT
     equivalent to those in a TFM file. */
    alpha = 16 * currfont->scaledsize;
    beta = 16;
    while (currfont->scaledsize >= 040000000)/* 2^23 */
    {
      currfont->scaledsize = currfont->scaledsize / 2;
      beta = beta / 2;
    }
    currfont->pixelptr[i].dwidth = (((((b3 * ((int) currfont->scaledsize)) / 0400) +
	    (b2 * ((int) currfont->scaledsize))) / 0400) +
	(b1 * ((int) currfont->scaledsize))) / beta;
    if (b0 > 0)
      if (b0 == 255)
	currfont->pixelptr[i].dwidth -= alpha;
      else
      {
#ifdef DEBUG
	  (*ResetVDU)();
	  WriteLn();
	  WriteString("Bad fix width! 1st byte=");
	  WriteInt(b0);
	  WriteLn();
	  RestoreTerminal();
	  exitprog (1);
#endif /* DEBUG */
      }
  /* convert DVI units to pixels */
    currfont->pixelptr[i].pwidth = PixelRound (currfont->pixelptr[i].dwidth);
  }
  ClosePXLFile ();
}

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

void BuildFontSpec (fontptr)
fontinfoptr fontptr;
{

/* SYSDEP: Build a complete PXL file specification for the given font.
   This will only be done once per font; fontspeclen will no longer be 0.
   The PXL file resides in fontarea if not empty,
   otherwise within fontdir (set by InitSysInterface().
   WARNING: This routine is also called by ShowStatistics()
*/

  unsigned int  i, next, pxlsize, temp;

  next = 0;

/* Append "fontname.nnnnpxl" to fontspec where nnnn are 4 digits
   representing the pxlsize.
*/

  i = 0;
  while ((i < fontptr->fontnamelen) && (next < MAXFONTSPEC))
    fontptr->fontspec[next++] = fontptr->fontname[i++];/* append fontname */

  if (next + 7 < MAXFONTSPEC)	/* append .nnnnpxl */
  {
    fontptr->fontspec[next] = '.';
    fontptr->fontspec[next + 5] = 'p';
    fontptr->fontspec[next + 6] = 'x';
    fontptr->fontspec[next + 7] = 'l';
    fontptr->fontspeclen = next + 8;
  /* SYSDEP: terminate fontspec with NULL */
    if (fontptr->fontspeclen < MAXFONTSPEC)
      fontptr->fontspec[fontptr->fontspeclen] = NULL;
  }
  else
  {
    fontptr->fontspeclen = MAXFONTSPEC;/* fontspec truncated */
    return;
  }
  next++;
/* next now points to 1st n in "dir/fontname.nnnnpxl" */

/* SYSDEP: Calculate pxlsize (nnnn) and insert into fontspec.  If fontspec
   does not exist, we try pxlsize+1 and pxlsize-1 before giving up.
   This overcomes rounding problems that can occur with magnified fonts.
   e.g., if TeX source contains
                \magnification=\magstep1         % mag = 1200
                \font\abc=cmr10 scaled\magstep4  % s/d = 2.074
   and resolution = 240 then pxlsize = 2987, NOT 2986.
   Is there a better method to avoid all the file open overheads???
*/
  pxlsize = (unsigned int) ((float) mag * ((float) fontptr->scaledsize /
	(float) fontptr->designsize) * ((float) resolution / 200.0) + 0.5);
  if (pxlsize > 9999)
    pxlsize = 9998;		/* allow for adding 1 */
  else
    if (pxlsize == 0)
      pxlsize = 1;		/* allow for subtracting 1 */

  i = 1;
  temp = pxlsize;
  for (;;)
  {
    fontptr->fontspec[next] = '0' + (temp / 1000);
    temp = temp % 1000;
    fontptr->fontspec[next + 1] = '0' + (temp / 100);
    temp = temp % 100;
    fontptr->fontspec[next + 2] = '0' + (temp / 10);
    temp = temp % 10;
    fontptr->fontspec[next + 3] = '0' + temp;
    if (i > 3)			/* pxlsize has been restored */
      return;			/* could not open fontspec */

    if (OpenPXLFile (fontptr->fontspec))
    {
      ClosePXLFile ();
      return;			/* fontspec exists */
    }
    else
      if (i == 1)
	temp = pxlsize - 1;	/* try pxlsize-1 */
      else
	if (i == 2)
	  temp = pxlsize + 1;	/* try pxlsize+1 */
	else
	  temp = pxlsize;	/* restore original pxlsize */
    i++;
  }
}


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

unsigned int  Length (s)
char  s[];
{
/* SYSDEP: Returns the number of characters in given string, where NULL
   is assumed to terminate the string (if not full).
*/

  return (strlen (s));
}

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

void UpdateDVIStatusLine ()
{
/* Show totalpages, currDVIpage, currTeXpage, direction and displaymode. */

  unsigned int  i;
  int lastnonzero;

  (*ClearTextLine) (DVIstatusl);
  (*MoveToTextLine) (DVIstatusl);
  WriteString ("Total pages=");
  WriteCard (totalpages);
  WriteString ("   DVI page=");
  WriteCard (currDVIpage);
  WriteString ("   TeX page=");
  Write ('[');
  lastnonzero = 9;
  while ((lastnonzero > 0) && (currTeXpage[lastnonzero] == 0))
    lastnonzero--;		/* find last counter with non-zero value */

/* always show \count0 but don't show trailing 0 counters */
  for (i = 0; i <= lastnonzero; i++)
  {
    WriteInt (currTeXpage[i]);
    if (i != lastnonzero)
      Write ('.');
  }
  Write (']');
  WriteString ("   Next=");
  if (ascending)
    Write ('>');
  else
    Write ('<');

  WriteString ("   ");
  switch (displaymode)
  {
    case TERSEMODE: 
      WriteString ("Terse");
      break;
    case BOXMODE: 
      WriteString ("Box");
      break;
    case FULLMODE: 
      WriteString ("Full");
      break;
  }
  WriteLn ();
}

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

void UpdateWindowStatusLine ()
{
/* Show current window location and size, page location and size, and units. */

  (*ClearTextLine) (windowstatusl);
  (*MoveToTextLine) (windowstatusl);
  WriteString ("Window at (");
  WriteDimension (windowleft);
  Write (',');
  WriteDimension (windowtop);
  WriteString (") ");
  WriteDimension (scaledwd);
  WriteString (" by ");
  WriteDimension (scaledht);
  WriteString ("   Page at (");
  WriteDimension (minhp);
  Write (',');
  WriteDimension (minvp);
  WriteString (") ");
  WriteDimension (maxhp - minhp + 1);
  WriteString (" by ");
  WriteDimension (maxvp - minvp + 1);
  WriteString ("   ");
  switch (currentunits)
  {
    case INUNITS: 
      WriteString ("IN");
      break;
    case CMUNITS: 
      WriteString ("CM");
      break;
    case MMUNITS: 
      WriteString ("MM");
      break;
    case PCUNITS: 
      WriteString ("PC");
      break;
    case PTUNITS: 
      WriteString ("PT");
      break;
    case PXUNITS: 
      WriteString ("PX");
      break;
  }
  WriteLn ();
}

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

void WriteDimension (pixels)
int   pixels;
{
/* Show the given pixel dimension in terms of currentunits. */

  double  realdim;
  unsigned int  fracpart;

  switch (currentunits)
  {
    case INUNITS: 
      realdim = (double) pixels / (double) resolution;
      break;
    case CMUNITS: 
      realdim = (double) pixels / (double) resolution * 2.54;
      break;
    case MMUNITS: 
      realdim = (double) pixels / (double) resolution * 25.4;
      break;
    case PCUNITS: 
      realdim = (double) pixels / (double) resolution * 72.27 / 12.0;
      break;
    case PTUNITS: 
      realdim = (double) pixels / (double) resolution * 72.27;
      break;
    case PXUNITS: 
      WriteInt (pixels);
      return;
  }
/* show realdim to an accuracy of 1 decimal place */
  if (ABS (realdim) < 0.05)
    WriteString ("0.0");
  else {
    if (realdim < 0.0)
    {
      Write ('-');
      realdim = ABS (realdim);
    }
  realdim = realdim + 0.05;	/* round up to 1 decimal place */
  WriteCard ((unsigned) realdim);/* whole part */
  Write ('.');
  fracpart = (unsigned) ((realdim - (float) ((int) (realdim))) * 10.0);/* 0..9 */
  WriteCard (fracpart);
  }
}


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

void NextCommandLine ()
{

/* Prompt user for next command line, parse response and call the
   appropriate command handler for each command in the line.
*/

  int   n;			/* returned by GetInteger()call */

  (*ClearTextLine) (commandl);
  (*MoveToTextLine) (commandl);
  WriteString (COMMPROMPT);
  WriteBuffer ();
  ReadString (commstring, MAXCOMMSTRING);/* read new command line */
  ClearMessageLine ();		/* erase message line at this stage */
  commlen = Length (commstring);
  commpos = 0;
  while ((commlen > 0) && (commstring[commlen - 1] == ' '))
    commlen--;			/* ignore any trailing spaces */

/* initialize flags for multiple command processing */
  badcommand = FALSE;
  paintWindowStatus = FALSE;
  paintDVIStatus = FALSE;
  paintwindow = FALSE;
  screenjustcleared = FALSE;
  pageoffpaper = FALSE;
  while ((commpos < commlen) && (!(badcommand)))
  {
  /* next command is defined by the next non-space character in commstring */
    while (commstring[commpos] == ' ')
      commpos++;		/* ignore any spaces */

    command = commstring[commpos];
    if ((command < 'z') && (command > 'a'))
      command = toupper (command);
    switch (command)
    {
      case Window: 
	commpos++;
	WindowMove ();
	if ((currDVIpage != 0) && (!(badcommand)))
	  paintWindowStatus = TRUE;

	break;

      case Up: 
      case Down: 
	commpos++;
	WindowUpDown ();
	if (currDVIpage != 0)
	  paintWindowStatus = TRUE;
	break;

      case Left: 
      case Right: 
	commpos++;
	WindowLeftRight ();
	if (currDVIpage != 0)
	  paintWindowStatus = TRUE;
	break;

      case Hsize: 
	commpos++;
	SetWindowWidth ();
	if (currDVIpage != 0)
	  NewLocation (windowleft, windowtop);

	paintWindowStatus = TRUE;
	break;

      case Vsize: 
	commpos++;
	SetWindowHeight ();
	if (currDVIpage != 0)
	  NewLocation (windowleft, windowtop);

	paintWindowStatus = TRUE;
	break;

      case Next: 
	commpos++;
	if (NextPageFound ())
	  ProcessPage ();
	break;

      case '0': 
      case '1': 
      case '2': 
      case '3': 
      case '4': 
      case '5': 
      case '6': 
      case '7': 
      case '8': 
      case '9': 
	if ((GetInteger (commstring, (unsigned)commlen, &commpos, &n)) &&
	    (DVIPageFound ((unsigned) n)))
	/* must be true, and commpos now after last digit */
	  ProcessPage ();

	break;

      case TeXpage: 
	if (TeXPageFound ())	/* commpos incremented in ParseTeXpage */
	  ProcessPage ();

	break;

      case Forwards: 
	commpos++;
	ascending = TRUE;
	paintDVIStatus = TRUE;
	break;

      case Backwards: 
	commpos++;
	ascending = FALSE;
	paintDVIStatus = TRUE;
	break;

      case Terse: 
	commpos++;
	displaymode = TERSEMODE;
	paintDVIStatus = TRUE;
	if (currDVIpage != 0)
	  paintwindow = TRUE;
	break;

      case Box: 
	commpos++;
	displaymode = BOXMODE;
	paintDVIStatus = TRUE;
	if (currDVIpage != 0)
	  paintwindow = TRUE;
	break;

      case Full: 
	commpos++;
	displaymode = FULLMODE;
	paintDVIStatus = TRUE;
	if (currDVIpage != 0)
	  paintwindow = TRUE;
	break;

      case Inch: 
      case Cm: 
      case Mm: 
      case PcPtPx: 
	commpos++;
	ChangeUnits ();
	if (!badcommand)
	  paintWindowStatus = TRUE;
	break;

      case Help: 
	commpos++;
	ShowHelp ();
	break;

      case Show: 
	commpos++;
	ShowStatistics ();
	(*ClearScreen) ();
	screenjustcleared = TRUE;
	paintDVIStatus = TRUE;
	paintWindowStatus = TRUE;
	if (currDVIpage != 0)
	  paintwindow = TRUE;
	break;

      case Quit: 
	return;

      default: 
	commpos++;
	ClearMessageLine ();
	WriteString ("Unknown command!   Type ");
	Write (Help);
	WriteString (" for help.");
	BadCommandMessage ();
	break;
    }
  }
  if (paintwindow)
    DisplayPage ();		/* only update window after processing all commands 
				*/
  else
  {
    if (paintDVIStatus)
      UpdateDVIStatusLine ();
    if (paintWindowStatus)
      UpdateWindowStatusLine ();
  }
}

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

void WindowMove ()
{

/* Syntax of Window command is  W hpos,vpos  where hpos and vpos are
   dimensions with leading and/or trailing spaces.  If hpos,vpos absent then
   we move to minhp,minvp (top left corner of page rectangle).
*/

  int   hpos, vpos;		/* move window to this new position */

/* commpos is positioned after W */

  if (GetDimension (commstring, (unsigned)commlen, &commpos, &hpos))
  {
    while ((commpos < commlen) && (commstring[commpos] == ' '))
      commpos++;		/* skip any spaces before comma */

    if ((commpos == commlen) || (commstring[commpos] != ','))
    {				/* , vpos is missing , is missing */
      ClearMessageLine ();
      WriteString ("Comma expected!");
      if (commpos < commlen)
	commpos++;

      BadCommandMessage ();
    }
    else
    {
      commpos++;		/* skip over comma */
      if (GetDimension (commstring, (unsigned)commlen, &commpos, &vpos))
	NewLocation (hpos, vpos);
      else
      {
	ClearMessageLine ();
	WriteString ("Vertical coordinate expected!");
	if (commpos < commlen)
	  commpos++;
	BadCommandMessage ();
      }
    }
  }
  else
    NewLocation (minhp, minvp);	/* hpos,vpos absent */
}

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

short   GetDimension (str, len, pos, n)
char  str[];
unsigned int  len, *pos;
int  *n;
{

/* Extract a dimension from given str starting at given pos.
   n returns the corresponding number of pixels in the dimension
   (which is an integer or real value in terms of currentunits);
   pos is also used to return the position after the dimension.
   If no dimension is found then set n to 0 and return FALSE (pos will only
   change if leading spaces were skipped).
   If ABS(n) > maxpix then set n to sign * maxpix.
   Valid syntax of a dimension is  integer[.{digit}]  or  .{digit}  where
   an integer is defined by GetInteger()
   Real dimensions are truncated to 4 decimal places.
   Note that a sign or decimal point by itself is valid and sets n to 0.
*/

  int   sign, intdim;
  unsigned int  fracpart, divisor;
  double  absrealdim;
  short   intpresent, dimtoobig;

/* GetInteger()does not remember a sign by itself, so we need to check
   for -ve dimensions like -.5 first.
*/

  while ((*pos < len) && (str[*pos] == ' '))/* skip any spaces */
    (*pos)++;

  sign = 1;
  if ((*pos < len) && (str[*pos] == '-'))
    sign = -1;

  intpresent = GetInteger (str, len, pos, &intdim);
  if ((!intpresent) && ((*pos == len) || (str[*pos] != '.')))
  {
    *n = 0;
    return (FALSE);
  }
/* dimension is valid; if no integer part then intdim will be 0; sign==+|-1 */

  if ((*pos == len) || (str[*pos] != '.'))
  /* no fractional part */
    absrealdim = (float) (ABS (intdim));
  else
  {
  /* extract fractional part */
    (*pos)++;			/* skip over decimal point */
    divisor = 1;
    fracpart = 0;
    while ((*pos < len) && (str[*pos] >= '0') && (str[*pos] <= '9'))
    {
    /* only consider up to 4 decimal places */
      if (divisor < 10000)
      {
	divisor = divisor * 10;
	fracpart = fracpart * 10 + str[*pos] - '0';
      }
      (*pos)++;
    }
    absrealdim = (double) (fabs ((double) intdim)) + ((double) fracpart / (double) divisor);
  }
/* calculate n based on absrealdim, sign and currentunits */
  dimtoobig = FALSE;
  switch (currentunits)
  {
    case INUNITS: 
      if (absrealdim > ((double) maxpix / (double) resolution))
	dimtoobig = TRUE;
      else
	*n = sign * floor (absrealdim * (double) resolution + 0.5);
      break;

    case CMUNITS: 
      if (absrealdim > (((double) maxpix / (double) resolution) * 2.54))
	dimtoobig = TRUE;
      else
	*n = sign * floor ((absrealdim / 2.54) * (double) resolution + 0.5);
      break;

    case MMUNITS: 
      if (absrealdim > (((double) (maxpix) / (double) resolution) * 25.4))
	dimtoobig = TRUE;
      else
	*n = sign * floor ((absrealdim / 25.4) * (double) resolution + 0.5);
      break;

    case PCUNITS: 
      if (absrealdim > (((double) maxpix / (double) resolution) *
	    (72.27 / 12.0)))
	dimtoobig = TRUE;
      else
	*n = sign * floor ((absrealdim / 72.27) * 12.0 *
	    (double) resolution + 0.5);
      break;

    case PTUNITS: 
      if (absrealdim > (((double) maxpix / (double) resolution) * 72.27))
	dimtoobig = TRUE;
      else
	*n = sign * floor ((absrealdim / 72.27) * (double) resolution + 0.5);
      break;

    case PXUNITS: 
      if (absrealdim > (double) maxpix)
	dimtoobig = TRUE;
      else
	*n = sign * floor (absrealdim + 0.5);
      break;
  }
  if (dimtoobig)
    *n = sign * maxpix;
  return (TRUE);
}

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

   Extract an integer from given str starting at given pos.
   pos is also used to return the position after the integer.
   If no integer is found then set n to 0 and return FALSE (pos will only
   change if leading spaces were skipped).
   If ABS(n) > LIMIT then set n to sign * LIMIT.
   Valid syntax is  +{digit}  or  -{digit}  or  digit{digit}.
   Note that a + or - by itself is valid and sets n to 0.                    */

short   GetInteger (str, len, pos, n)
char  str[];
unsigned int  len, *pos;
int  *n;
{
#define LIMIT       2147483647	/* SYSDEP: TeX's limit = 2^31 - 1. Should also be >=
				   maxpix. Note that this also defines the range of
				   page numbers the user can ask for! */

#define THRESHHOLD  LIMIT/10	/* nearing overflow */

  unsigned int  absval, last;
  int   sign;
  short   inttoobig;

  while ((*pos < len) && (str[*pos] == ' '))/* skip any spaces */
    (*pos)++;

  absval = 0;
  sign = 1;
  last = *pos;
  inttoobig = FALSE;
  if (*pos < len)
  {
    if (str[*pos] == '-')
    {
      sign = -1;
      last++;
    }
    else
      if (str[*pos] == '+')
	last++;

    while ((last < len) && (str[last] >= '0') && (str[last] <= '9'))
    {
      if ((absval > THRESHHOLD) || ((absval == THRESHHOLD) && (str[last] > '7')))
	inttoobig = TRUE;
      else
	absval = absval * 10 + str[last] - '0';

      last++;
    }
  }
  if (*pos == last)
  {
    *n = 0;
    return (FALSE);
  }
  else
  {
    *pos = last;
    if (inttoobig)
      absval = LIMIT;
    *n = sign * (int) (absval);
    return (TRUE);
  }
}

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

void BadCommandMessage ()
{
/* A bad command has just been detected and some sort of message displayed.
   Note that commpos is pointing to just after the problem character.
   If there are further commands then we show user what will be ignored.
*/

  unsigned int  i;

  badcommand = TRUE;
  (*ClearTextLine) (commandl);
  (*MoveToTextLine) (commandl);
  WriteString (COMMPROMPT);
  for (i = 0; i <= commpos - 1; i++)
    Write (commstring[i]);
  Write ('!');			/* put ! after the problem character */
  if (commpos < commlen)
  {
    WriteString ("   Ignoring:");
    for (i = commpos; i <= commlen - 1; i++)
      Write (commstring[i]);
  }
  WaitForReturn ();
  ClearMessageLine ();
  (*ClearTextLine) (commandl);
}

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

void NewLocation (newhp, newvp)
int   newhp, newvp;
{

/* Change window location to given position and update window edges.
   If pageempty is TRUE then window moves to (paperleft,papertop).
   If the entire window moves outside the page rectangle then outsidepage
   becomes TRUE and we restrict movement to just beyond the edge(s) so that
   user can easily move window (via Up,Down,Left,Right) to positions
   in which one or more window and page edges coincide.
   Note that allpagevisible is also updated.
*/

  if (currDVIpage == 0)		/* message only seen after W,U,D,L,R commands */
  {
    ClearMessageLine ();
    WriteString ("You haven't selected a page yet!");
    BadCommandMessage ();
    return;
  }
  if (pageempty)
  {
    newvp = papertop;
    newhp = paperleft;
  }
  else
  {
  /* check if new position puts window entirely outside edges; if so then minimize
     the movement needed to keep this true */
    outsidepage = FALSE;
    if (newvp > maxvp)
    {
      outsidepage = TRUE;
      newvp = maxvp + 1;
    }
    else
      if (newvp < (minvp - scaledht + 1))
      {
	outsidepage = TRUE;
	newvp = minvp - scaledht;
      }
    if (newhp > maxhp)
    {
      outsidepage = TRUE;
      newhp = maxhp + 1;
    }
    else
    {
      if (newhp < (minhp - scaledwd + 1))
      {
	outsidepage = TRUE;
	newhp = minhp - scaledwd;
      }
    }
  }
  windowtop = newvp;
  windowleft = newhp;
  windowbottom = windowtop + scaledht - 1;
  windowright = windowleft + scaledwd - 1;

/* allpagevisible will only be sensible if not pageempty */

  allpagevisible = ((minvp >= windowtop) && (maxvp <= windowbottom) &&
      (minhp >= windowleft) && (maxhp <= windowright));

/* even if pageempty or window hasn't moved we must still call DisplayPage()*/

  paintwindow = TRUE;
}

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

void WindowUpDown ()
{
  int   amount;			/* move window up/down this many pixels */

/* commpos is positioned after U or D */
  if (!GetDimension (commstring, (unsigned)commlen, &commpos, &amount))
    amount = scaledht;		/* if amount absent, set to window height */

  if (command == Up)
    amount = -amount;

  NewLocation (windowleft, windowtop + amount);
}

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

void WindowLeftRight ()
{
  int   amount;			/* move window left/right this many pixels */

/* commpos is positioned after L or R */
  if (!GetDimension (commstring, (unsigned)commlen, &commpos, &amount))
    amount = scaledwd;		/* if amount absent, set to window width */

  if (command == Left)
    amount = -amount;

  NewLocation (windowleft + amount, windowtop);
}

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

void SetWindowWidth ()
{
/* Set horizontal size of window region to given dimension; if <= 0 then set
   horizontal size to 1 pixel.
   If no parameter then use the unscaled width represented by windowwd.
*/

  int   wd;

/* commpos is positioned after H */
  if (GetDimension (commstring, (unsigned)commlen, &commpos, &wd))
  {
  /* note that maximum value of wd is restricted to maxpix */
    if (wd <= 0)
      wd = 1;
    NewWindowWidth (wd);
  }
  else
    NewWindowWidth (windowwd);	/* parameter absent */
}

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

void NewWindowWidth (wd)

int   wd;
{
/* Set window width to given value (> 0 and <= max dimension). */

  scaledwd = wd;
  hscalefactor = (double) windowwd / (double) scaledwd;

/* following method avoids testing hscalefactor each time in ScaleHpos */

  if (hscalefactor > 1.0)
    ScaleHpos = ExpandHpos;
  else
    ScaleHpos = ShrinkHpos;
}


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

int   ExpandHpos (h)
int   h;
{
/* Return a scaled value for the given horizontal window coordinate. */

  return ((int) (((float) (h)) * hscalefactor + 0.5));/* hscalefactor > 1.0 */
}

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

int   ShrinkHpos (h)
int   h;
{
/* Return a scaled value for the given horizontal window coordinate. */

  return ((int) (((float) (h) + 0.5) * hscalefactor));/* hscalefactor <= 1.0 */
}

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

void SetWindowHeight ()
{

/* Set vertical size of window region to given dimension; if <= 0 then set
   vertical size to 1 pixel.
   If no parameter then use the unscaled height represented by windowht.
*/

  int   ht;

/* commpos is positioned after V */
  if (GetDimension (commstring, (unsigned)commlen, &commpos, &ht))
  {
  /* note that maximum value of ht is restricted to maxpix */
    if (ht <= 0)
      ht = 1;
    NewWindowHeight (ht);
  }
  else
    NewWindowHeight (windowht);	/* parameter absent */
}

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

void NewWindowHeight (ht)
int   ht;
{
/* Set window height to given value (> 0 and <= max dimension). */

  scaledht = ht;
  vscalefactor = ((double) windowht) / ((double) scaledht);

/* following method avoids testing vscalefactor each time in ScaleVpos */

  if (vscalefactor > 1.0)
    ScaleVpos = ExpandVpos;
  else
    ScaleVpos = ShrinkVpos;
}

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

int   ExpandVpos (v)
int   v;
{
/* Return a scaled value for the given vertical window coordinate. */

  return ((int) ((float) (v) * vscalefactor + 0.5));/* vscalefactor > 1.0 */
}

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

int   ShrinkVpos (v)
int   v;
{
/* Return a scaled value for the given vertical window coordinate. */

  return ((int) (((float) v + 0.5) * vscalefactor));/* vscalefactor <= 1.0 */
}

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

short   NextPageFound ()
{
/* User has selected next page in DVI file; what they get will depend on
   the current DVI page and whether we are ascending or not.
   Return TRUE iff we can move to next page.
*/

  if((currDVIpage == 1) && (!ascending))
  {
      ClearMessageLine ();
      WriteString ("You are looking at first DVI page!");
      BadCommandMessage ();
      return (FALSE);
  }
  else
  {
      if ((currDVIpage == totalpages) && (ascending))
      {
	  ClearMessageLine ();
	  WriteString ("You are looking at last DVI page!");
	  BadCommandMessage ();
	  return (FALSE);
      }
      else
      {
	  MoveToNextPage (ascending);	/* position to next DVI page */
	  return (TRUE);
      }
  }
}

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

short   DVIPageFound (n)
unsigned int  n;
{
/* User has selected a particular DVI page number.
   Move to page n and return TRUE iff n is in 1..totalpages.
*/

  if ((n < 1) || (n > totalpages))
  {
    ClearMessageLine ();
    if (totalpages > 1)
    {
      WriteString ("You can only request DVI pages 1 to ");
      WriteCard (totalpages);
      Write ('!');
    }
    else
      WriteString ("You can only request DVI page 1!");

    BadCommandMessage ();
    return (FALSE);
  }
  else
  {
    MoveToDVIPage (n);		/* position to given DVI page */
    return (TRUE);
  }
}

/******************************************************************************
   Return TRUE iff TeX page specification is valid and exists.
   If so then position to lowest matching page.                               */

short   TeXPageFound ()
{
  struct TeXpageinfo  newTeXpage;

  if (ParseTeXpage (&newTeXpage))
  {
    if (MoveToTeXPage (&newTeXpage))
      return (TRUE);		/* we found lowest matching page */
    else
    {
      ClearMessageLine ();
      WriteString ("No TeX page matches your request!");
      BadCommandMessage ();
      return (FALSE);
    }
  }
  else
    return (FALSE);		/* invalid TeX page specification */
}


/******************************************************************************
   Return TRUE iff TeX page specification in commstring is valid.  If so then
   newTeXpage will contain the appropriate information for MoveToTeXPage.
   The syntax of a TeX page specification is [n{.n}] where n is any integer as
   defined by GetInteger()  Up to 10 integers may be given and are separated by
   periods, even if absent.  Trailing periods may be omitted.  Spaces before
   and after integers and periods are skipped.  The 10 positions correspond to
   the \count0, \count1, ... ,\count9 values that TeX stores with every page.
   commpos is initially pointing at [.                                        */

short   ParseTeXpage (newTeXpage)/* out */
struct TeXpageinfo  *newTeXpage;
{
  newTeXpage->lastvalue = 0;
  for (;;)
  {
    commpos++;
    newTeXpage->present[newTeXpage->lastvalue] =
	GetInteger (commstring, (unsigned)commlen,
	&commpos, &(newTeXpage->value[newTeXpage->lastvalue]));
  /* commpos now at commlen, space, period, non-digit or ']' */
    while ((commpos < commlen) && (commstring[commpos] == ' '))
      commpos++;		/* skip any spaces */

    if (commpos == commlen)	/* check this first! */
    {
      ClearMessageLine ();
      WriteString ("] expected!");
      BadCommandMessage ();	/* commpos at commlen */
      return (FALSE);
    }
    if (commstring[commpos] == ']')/* end of TeX page specification */
    {
      commpos++;		/* possibly further commands */
      break;;
    }
    if (newTeXpage->lastvalue < 9)
      (newTeXpage->lastvalue)++;
    else
    {
      ClearMessageLine ();
      WriteString ("] expected after 10 integers!");
      commpos++;
      BadCommandMessage ();
      return (FALSE);
    }
    if (commstring[commpos] != '.')
    {
      ClearMessageLine ();
      WriteString ("Period, integer or ] expected!");
      commpos++;
      BadCommandMessage ();
      return (FALSE);
    }
  }
  while ((newTeXpage->lastvalue > 0) && (!newTeXpage->present[newTeXpage->lastvalue]))
    (newTeXpage->lastvalue)--;

  return (TRUE);
}

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

void ProcessPage ()
{
/* We are ready to interpret the current DVI page and fill in the various data
   structures imported from DVIReader.
   This routine will also:
   set the window size and location to useful values (depending on the relative
   sizes of the paper and unscaled window region, as well as the page location),
   update pageoffpaper (after checking to see if it was TRUE for the previous
   page processed as part of a multiple command string),
   set screenjustcleared, paintwindow and paintWindowStatus to TRUE,
   set paintDVIStatus to FALSE.
*/

  int   halfht, halfwd;

/* We check pageoffpaper here so user can type "NNNNNNNNNNNNN..." and note ALL
   the pages that are off the paper, not just the last one processed.
*/

  if (pageoffpaper)
  {
    ClearMessageLine ();
    WriteString ("Page off paper!");/* the previous page */
    WaitForReturn ();
  }
  (*ClearScreen) ();
  screenjustcleared = TRUE;
  UpdateDVIStatusLine ();	/* a MoveTo... routine has updated currDVI/TeXpage 
				*/
  paintDVIStatus = FALSE;
  InterpretPage ();		/* fill in DVIReader's page data structures */
  SortFonts (&unusedfont);	/* sort fonts in order of least chars and return
				   pointer to first unused font */
  ClearMessageLine ();		/* clear any message */
  if (pageempty)
  {
    minhp = 0;
    maxhp = 0;
    minvp = 0;
    maxvp = 0;			/* for window status */
  }

/* Try viewing as much of paper as possible and without too much distortion: */
  if (((paperwd < paperht)) && ((windowwd >= windowht)) ||
      ((paperwd == paperht)) && ((windowwd > windowht)))
  {
    halfht = paperht / 2;
    if ((paperht % 2) != 0)
      halfht++;			/* ensure bottom outline visible */
    NewWindowHeight (halfht);	/* try top half of paper */
    NewWindowWidth ((int) paperwd);
    NewLocation (paperleft, papertop);/* top left corner of paper */
    if ((!pageempty) && (outsidepage))
      NewLocation (paperleft, papertop + halfht);/* try moving down */
  }
  else
    if (((paperwd > paperht) && (windowwd <= windowht)) ||
	((paperwd == paperht) && (windowwd < windowht)))
    {
      halfwd = paperwd / 2;
      if ((paperwd % 2) != 0)
	halfwd++;		/* ensure right outline visible */
      NewWindowHeight ((int) paperht);
      NewWindowWidth (halfwd);	/* try left half of paper */
      NewLocation (paperleft, papertop);/* top left corner of paper */
      if ((!pageempty) && (outsidepage))
	NewLocation (paperleft + halfwd, papertop);/* try moving right */
    }
    else
    {
    /* paper shape matches unscaled window shape */
      NewWindowHeight ((int) paperht);/* try all of paper */
      NewWindowWidth ((int) paperwd);
      NewLocation (paperleft, papertop);/* top left corner of paper */
    }
/* If part/all of page is off paper then we set window size and location so
   user can just see ALL of paper ANDALL of page.
*/
  if ((!pageempty) && ((minhp < paperleft) || (minvp < papertop) ||
	(maxhp > paperright) || (maxvp > paperbottom)))
  {
    NewWindowHeight (Max (maxvp, paperbottom) - Min (minvp, papertop) + 1);
    NewWindowWidth (Max (maxhp, paperright) - Min (minhp, paperleft) + 1);
    NewLocation (Min (minhp, paperleft), Min (minvp, papertop));
    pageoffpaper = TRUE;
  }
  else
    pageoffpaper = FALSE;	/* page is empty or fits on paper */

  paintWindowStatus = TRUE;
  paintwindow = TRUE;
}

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

int   Min (a, b)
int   a, b;
{
/* Return the minimum value of a and b. */

  if (a < b)
    return (a);
  else
    return (b);
}

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

int   Max (a, b)
int   a, b;
{
/* Return the maximum value of a and b. */

  if (a > b)
    return (a);
  else
    return (b);
}

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

void ChangeUnits ()
{
/* Parse the rest of an Inch, Cm, Mm or PcPtPx command.
   commpos is pointing to next position in commandstr.
*/

  char  nextch;

  if (commpos < commlen)
  {
    if ((commstring[commpos] >= 'a') && (commstring[commpos] <= 'z'))
      nextch = toupper (commstring[commpos]);
  /* required to beat the overly literal standard macro */
    else
      nextch = commstring[commpos];
    commpos++;
  }
  else
    nextch = ' ';

  if ((command == Inch) && (nextch == 'N'))
    currentunits = INUNITS;
  else
    if ((command == Cm) && (nextch == 'M'))
      currentunits = CMUNITS;
    else
      if ((command == Mm) && (nextch == 'M'))
	currentunits = MMUNITS;
      else
	if ((command == PcPtPx) && (nextch == 'C'))
	  currentunits = PCUNITS;
	else
	  if ((command == PcPtPx) && (nextch == 'T'))
	    currentunits = PTUNITS;
	  else
	    if ((command == PcPtPx) && (nextch == 'X'))
	      currentunits = PXUNITS;
	    else
	    {
	      ClearMessageLine ();
	      WriteString ("Unknown units!   ");
	      switch (command)
	      {
		case Inch: 
		  WriteString ("IN");
		  break;
		case Cm: 
		  WriteString ("CM");
		  break;
		case Mm: 
		  WriteString ("MM");
		  break;
		case PcPtPx: 
		  WriteString ("PC, PT or PX");
		  break;
	      }
	      WriteString (" expected.");
	      BadCommandMessage ();
	    }
}

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

void ShowHelp ()
{
/* Help information is displayed in lines 1 to bottoml-2.
   We assume that bottoml is at least 3 and that VDU screen is at least
   MAXLINE characters wide.
*/

#define MAXLINE 82		/* SYSDEP: helpname should have <= MAXLINE */
				/* chars/line */
  FILE *helpfile;
  char  outline[MAXLINE];
  unsigned int  i;
  int   ch, lines;
  char  answer;
  
  if ((helpfile = fopen (helpname, "r")) == NULL)/* SYSDEP: read only */
  {
      ClearMessageLine ();
      WriteString ("Couldn't open help file ");
      WriteString (helpname);
      Write ('!');
      WaitForReturn ();
      ClearMessageLine ();
  }
  else
  {
      (*ClearScreen) ();
      (*MoveToTextLine) (1);
      lines = 0;
      for (;;)
      {
	  if ((ch = getc (helpfile)) == EOF)/* SYSDEP: end of file */
	  {
	      (*ClearTextLine) (bottoml);
	      (*MoveToTextLine) (bottoml);
	      WriteString ("Hit RETURN key to resume page display:");
	      WriteBuffer ();
	      do
	      {
		  Read (&answer);
	      }
	      while (answer != EOL);
	      break;
	  }
	  else if (lines >= (bottoml - 2))  /* blank line before prompt */
	  {
	      (*ClearTextLine) (bottoml);
	      (*MoveToTextLine) (bottoml);
	      WriteString ("Hit RETURN key to resume page display,");
	      WriteString (" or any other key for more help:");
	      WriteBuffer ();
	      Read (&answer);
	      if (answer == EOL)
		  break;
	      (*ClearScreen) ();
	      (*MoveToTextLine) (1);
	      lines = 0;		/* reset line count */
	  }
	  (void) strcpy (outline, "");
	  i = 0;
	  while (ch != EOL)
	  {
	      if (i < MAXLINE)
		  outline[i] = ch;
	      if ((ch = getc (helpfile)) == EOF)
		  ch = '\0';
	      /* SYSDEP: this should only happen if EOF occurs before EOL */
	      i++;
	  }

	  if (i < MAXLINE)
	      outline[i] = '\0';

	  WriteString (outline);
	  WriteLn ();
	  lines++;
      }
      (void) fclose (helpfile);
      (*ClearScreen) ();
      screenjustcleared = TRUE;
      paintDVIStatus = TRUE;
      paintWindowStatus = TRUE;
      if (currDVIpage != 0)
	  paintwindow = TRUE;
  }
}

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

void ShowStatistics ()
{
/* Show command options and rule/font/character statistics.
   Note that UserHitsReturn controls pagination and takes the place of WriteLn()
*/

  int   linecount, fontcount;
  char  ch;

  (*ClearScreen) ();
  (*MoveToTextLine) (1);
  linecount = 1;
  WriteString (Version);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("DVI file         = ");
  WriteString (DVIname);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("VDU              = ");
  WriteString (vdu);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Resolution       = ");
  WriteCard ((unsigned) resolution);
  WriteString (" pixels per inch");
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Magnification    = ");
  WriteCard ((unsigned) mag);
  if (mag != DVImag)
  {
    WriteString (" (DVI mag of ");
    WriteCard (DVImag);
    WriteString (" was overridden)");
  }
  else
    WriteString (" (DVI mag)");

  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Font directories = ");
  WriteString (fontdir);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Dummy font       = ");
  WriteString (dummyfont);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Help file        = ");
  WriteString (helpname);
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Paper wd by ht   = ");
  WriteDimension ((int) paperwd);
  WriteString (" by ");
  WriteDimension ((int) paperht);
  switch (currentunits)
  {
    case INUNITS: 
      WriteString (" IN");
      break;
    case CMUNITS: 
      WriteString (" CM");
      break;
    case MMUNITS: 
      WriteString (" MM");
      break;
    case PCUNITS: 
      WriteString (" PC");
      break;
    case PTUNITS: 
      WriteString (" PT");
      break;
    case PXUNITS: 
      WriteString (" PX");
      break;
  }
  if (UserHitsReturn (&linecount))
    return;
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Total rules on current page = ");
  WriteCard (totalrules);
  if (UserHitsReturn (&linecount))
    return;
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Total fonts on ALL pages = ");
  WriteCard (totalfonts);
  if (UserHitsReturn (&linecount))
    return;
  if (UserHitsReturn (&linecount))
    return;
  WriteString ("Fonts: (if used on current page then total chars given)");
  if (UserHitsReturn (&linecount))
    return;
  fontcount = 0;
  thisfontinfo = fontlist;
  while (thisfontinfo != NULL)
  {
    if (thisfontinfo->fontspeclen == 0)/* need to build fontspec */
      BuildFontSpec (thisfontinfo);

    WriteString (thisfontinfo->fontspec);
    if (OpenPXLFile (thisfontinfo->fontspec))
      ClosePXLFile ();
    else
      WriteString (" does not exist!");/* will use dummyfont */

    if (thisfontinfo->fontused)
    {
      fontcount++;
      WriteString ("   (total chars = ");
      WriteCard (thisfontinfo->totalchars);
      Write (')');
    }
    if (UserHitsReturn (&linecount))
      return;
    thisfontinfo = thisfontinfo->nextfont;
  }
  if (currDVIpage == 0)
    WriteString ("You haven't selected a page yet.");
  else
  {
    WriteString ("Total fonts on current page = ");
    WriteInt (fontcount);
  }
  if (UserHitsReturn (&linecount))
    return;
  WriteLn ();
  (*MoveToTextLine) (bottoml);
  WriteString ("Hit RETURN key to resume page display:");
  WriteBuffer ();
  do
  {
    Read (&ch);
  }
  while (ch != EOL);
}

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

short   UserHitsReturn (linecount)
int  *linecount;
{

/* Do a WriteLn()and return TRUE iff linecount = bottoml-2))&&( AND (user hits EOL.
   If linecount < bottoml-2 then return FALSE; if not, and user hits
   something other than EOL, then prepare a new screen before returning FALSE.
*/

  char  ch;

  WriteLn ();
  if (*linecount == (bottoml - 2))/* prompt for next screen */
  {
    (*MoveToTextLine) (bottoml);
    WriteString ("Hit RETURN key to resume page display,");
    WriteString (" or any other key for more:");
    WriteBuffer ();
    Read (&ch);
    if (ch == EOL)
      return (TRUE);
    (*ClearScreen) ();
    (*MoveToTextLine) (1);
    *linecount = 1;
  }
  else
    (*linecount)++;

  return (FALSE);
}

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

void DisplayPage ()
{
/* Display page in window region based on window location and size,
   and displaymode.  This routine is only called if paintwindow is TRUE
   after all commands have been processed.
*/

  if (screenjustcleared)	/* avoid doing it again */
  {
    if (paintDVIStatus)
      UpdateDVIStatusLine ();
    if (paintWindowStatus)
      UpdateWindowStatusLine ();
  }
  else
  {
    (*ClearScreen) ();		/* would prefer ClearWindow but some VDUs have
				   trouble */
    UpdateDVIStatusLine ();
    UpdateWindowStatusLine ();
  }
  (*StartGraphics) ();
  DisplayPaperEdges ();
  (*StartText) ();
  if (pageempty)
  {
    ClearMessageLine ();
    WriteString ("Page is empty.");
  }
  else
    if (outsidepage)
    {
      if (pageoffpaper)
	CheckPageEdges ();
      ClearMessageLine ();
      WriteString ("Window is ");
      if (windowtop > maxvp)
      {
	WriteString ("below ");
	if ((windowleft > maxhp) || (windowleft < minhp - scaledwd + 1))
	  WriteString ("and ");
      }
      else
	if (windowtop < (minvp - scaledht + 1))
	{
	  WriteString ("above ");
	  if ((windowleft > maxhp) || (windowleft < minhp - scaledwd + 1))
	    WriteString ("and ");

	}
      if (windowleft > maxhp)
	WriteString ("to the right of ");
      else
	if (windowleft < (minhp - scaledwd + 1))
	  WriteString ("to the left of ");

      WriteString ("page.");
    }
    else
    {
    /* Page is not empty and part or all of it is visible. */
      (*StartGraphics) ();
      useraborted = FALSE;
      DisplayRules ();
      if (!useraborted)
	DisplayChars ();

      (*StartText) ();
      if (pageoffpaper)
	CheckPageEdges ();
      if (allpagevisible)
      {
	ClearMessageLine ();
	WriteString ("Entire page is visible.");
      }
    }
}

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

void DisplayPaperEdges ()
{
/* Display visible outlines of the imaginary sheet of paper.
   Thickness of outlines = 1 screen pixel no matter what the h and v scaling.
*/

#define EDGEPIXEL  '.'		/* black pixel for outlines on non-graphic VDUs;
				   note that VDUInterface sets TeXtoASCII['.'] = '.' 
				*/

  int
        top, bot, left, right,	/* visible edges of paper in paper pixels */
        scaledtop, scaledleft,	/* scaled visible edges in screen pixels */
        scaledbot, scaledright,
        scaledheight, scaledwidth;/* scaled width and height */

/* first check if any part of paper is visible */
  if (papertop > windowbottom)
    return;
  if (paperbottom < windowtop)
    return;
  if (paperleft > windowright)
    return;
  if (paperright < windowleft)
    return;
/* part or all of paper is visible, so return visible region */
  top = Max (papertop, windowtop);
  bot = Min (paperbottom, windowbottom);
  left = Max (paperleft, windowleft);
  right = Min (paperright, windowright);
  scaledtop = (*ScaleVpos) (top - windowtop) + windowv;
  scaledleft = (*ScaleHpos) (left - windowleft) + windowh;
  if (vscalefactor > 1.0)
    scaledbot = (*ScaleVpos) (bot + 1 - windowtop) - 1 + windowv;
  else
    scaledbot = (*ScaleVpos) (bot - windowtop) + windowv;

  if (hscalefactor > 1.0)
    scaledright = (*ScaleHpos) (right + 1 - windowleft) - 1 + windowh;
  else
    scaledright = (*ScaleHpos) (right - windowleft) + windowh;

  scaledheight = scaledbot - scaledtop + 1;
  scaledwidth = scaledright - scaledleft + 1;
/* Only show visible edges if they are also paper outlines! */
  if (left == paperleft)	/* left outline visible */
    (*ShowRectangle) (scaledleft, scaledtop, 1, scaledheight, EDGEPIXEL);

  if (bot == paperbottom)	/* bottom outline visible */
    (*ShowRectangle) (scaledleft, scaledbot, scaledwidth, 1, EDGEPIXEL);

  if (top == papertop)		/* top outline visible */
    (*ShowRectangle) (scaledleft, scaledtop, scaledwidth, 1, EDGEPIXEL);

  if (right == paperright)	/* right outline visible */
    (*ShowRectangle) (scaledright, scaledtop, 1, scaledheight, EDGEPIXEL);

}

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

void DisplayRules ()
{
/* Display all pixels in rules, regardless of current displaymode.
   Rules will be displayed in the same order as in the DVI page (essentially
   top-down and left-right) because of the way DVIReader builds a rulelist.
*/

#define RULEPIXEL  '*'		/* black pixel for rules on non-graphic VDUs; note
				   that VDUInterface sets TeXtoASCII['*'] = '*'     
				*/
  int
        top, bottom, left, right,/* visible edges of rule */
        scaledtop, scaledleft,	/* scaled visible edges */
        scaledbot, scaledright,
        scaledwidth, scaledheight;/* scaled width and height */
  unsigned int
                thisrule;
  char
        keyhit;			/* returned by BusyRead()if TRUE */
  struct ruletab *ri_rt_ptr;

  thisruleinfo = rulelist;
  while (thisruleinfo != NULL)
  {
    thisrule = 0;
    while (thisrule < thisruleinfo->rulecount)
    {
      int   diff1, diff2;
      ri_rt_ptr = &(thisruleinfo->ruletable[thisrule]);
      diff1 = (ri_rt_ptr->vp) - (ri_rt_ptr->ht) + 1;
      diff2 = (ri_rt_ptr->hp) + (ri_rt_ptr->wd) - 1;

    /* check if any part of rule is visible */
    /* vp,hp is bottom left corner of rule on page */
      if (RectangleVisible (diff1,
	    (ri_rt_ptr->vp), (ri_rt_ptr->hp), diff2,
	    &top, &bottom, &left, &right))/* visible rectangle */
      {
      /* show all pixels in this rectangle */
	scaledtop = (*ScaleVpos) (top - windowtop) + windowv;
	scaledleft = (*ScaleHpos) (left - windowleft) + windowh;
	if (vscalefactor > 1.0)
	  scaledbot = (*ScaleVpos) (bottom + 1 - windowtop) - 1 + windowv;
	else
	  scaledbot = (*ScaleVpos) (bottom - windowtop) + windowv;

	if (hscalefactor > 1.0)
	  scaledright = (*ScaleHpos) (right + 1 - windowleft) - 1 + windowh;
	else
	  scaledright = (*ScaleHpos) (right - windowleft) + windowh;

	scaledheight = scaledbot - scaledtop + 1;
	scaledwidth = scaledright - scaledleft + 1;
	(*ShowRectangle)
	  (scaledleft,		/* h coord of top left cnr */
	    scaledtop,		/* v coord of top left cnr */
	    scaledwidth,
	    scaledheight,
	    RULEPIXEL);

      /* SYSDEP: we check keyboard after every visible rule */

	if (BusyRead (&keyhit))
	{
	  if (islower (keyhit))
	    keyhit = toupper (keyhit);
	  if ((keyhit == Terse) && (displaymode != TERSEMODE))
	  {
	    displaymode = TERSEMODE;
	    (*StartText) ();
	    UpdateDVIStatusLine ();
	    (*StartGraphics) ();
	  }
	  else
	    if ((keyhit == Box) && (displaymode != BOXMODE))
	    {
	      displaymode = BOXMODE;
	      (*StartText) ();
	      UpdateDVIStatusLine ();
	      (*StartGraphics) ();
	    }
	    else
	      if ((keyhit == Full) && (displaymode != FULLMODE))
	      {
		displaymode = FULLMODE;
		(*StartText) ();
		UpdateDVIStatusLine ();
		(*StartGraphics) ();
	      }
	      else
		if (keyhit == ABORTKEY)
		{
		  useraborted = TRUE;/* checked in DisplayPage() */
		  return;
		}
	}
      }
      thisrule++;
    }
    thisruleinfo = thisruleinfo->nextrule;
  }
}


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

   Return TRUE iff part or all of given rectangle would be visible
   in the current window.  Iff so, then we also return the visible
   region; the input and possible output rectangles are defined by their
   top, bottom, left and right edges in paper pixel coordinates.              */

short   RectangleVisible (intop, inbot, inleft, inright, outtop, outbot,
          outleft, outright)
int   intop, inbot, inleft, inright, *outtop, *outbot, *outleft, *outright;
{
  if (allpagevisible)		/* all of rectangle must be visible */
  {
    *outtop = intop;
    *outbot = inbot;
    *outleft = inleft;
    *outright = inright;
    return (TRUE);
  }
  if (intop > windowbottom)
    return (FALSE);
  if (inbot < windowtop)
    return (FALSE);
  if (inleft > windowright)
    return (FALSE);
  if (inright < windowleft)
    return (FALSE);

/* part or all of rectangle is visible, so return visible region */

  *outtop = Max (intop, windowtop);
  *outbot = Min (inbot, windowbottom);
  *outleft = Max (inleft, windowleft);
  *outright = Min (inright, windowright);
  return (TRUE);
}

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

void DisplayChars ()
{
/* Display all characters on a font by font basis.  How characters will be
   represented depends on the current displaymode (which the user can change
   while the window is being updated by typing the Terse/Box/Full commands).
   Fonts will be displayed in order of ascending totalchars (due to SortFonts().
   Characters in a font will be displayed in a top-down, left-right manner
   because of the way DVIReader builds a charlist.
*/

  char  keyhit;			/* check for abort or mode change */

  switch (displaymode)
  {
    case TERSEMODE: 
      DisplayOneChar = TerseChar;
      break;
    case BOXMODE: 
      DisplayOneChar = BoxChar;
      break;
    case FULLMODE: 
      if ((vscalefactor < 1.0) || (hscalefactor < 1.0))
	DisplayOneChar = FullChar2;
      else
	DisplayOneChar = FullChar1;
  }
  thisfontinfo = fontlist;
  while (thisfontinfo != unusedfont)
  {
  /* SortFont makes sure we only consider used fonts */
    {
      fontopen = FALSE;		/* needed for FullChar */

    /* Some VDUs may be able to simulate the given font, or even better, be able to
       download the glyph maps from the PXL file. To help the VDU select
       appropriately sized characters, we need to pass the scaledsize of the font
       (converted to unscaled paper pixels), the overall mag, and the current
       h/vscalefactors. */
      (*LoadFont) (thisfontinfo->fontspec,
	  PixelRound ((int) (thisfontinfo->scaledsize)),
	  (float) (mag) / 1000.0,
	  hscalefactor,
	  vscalefactor);

      thischarinfo = thisfontinfo->charlist;
      while (thischarinfo != NULL)/* display chars in chartable */
      {
	thischar = 0;
	while (thischar < thischarinfo->charcount)
	{
	  (*DisplayOneChar) ();

	/* SYSDEP: We can check for abort or mode change after every visible char
	   because BusyRead()overheads are not too high under VAX/UNIX. */
	  if ((charvisible) && (BusyRead (&keyhit)))
	  {
	    if (islower (keyhit))
	      keyhit = toupper (keyhit);
	    if ((keyhit == Terse) && (displaymode != TERSEMODE))
	    {
	      DisplayOneChar = TerseChar;
	      displaymode = TERSEMODE;
	      (*StartText) ();
	      UpdateDVIStatusLine ();
	      (*StartGraphics) ();
	    }
	    else
	      if ((keyhit == Box) && (displaymode != BOXMODE))
	      {
		DisplayOneChar = BoxChar;
		displaymode = BOXMODE;
		(*StartText) ();
		UpdateDVIStatusLine ();
		(*StartGraphics) ();
	      }
	      else
		if ((keyhit == Full) && (displaymode != FULLMODE))
		{
		  if ((vscalefactor < 1.0) || (hscalefactor < 1.0))
		    DisplayOneChar = FullChar2;
		  else
		    DisplayOneChar = FullChar1;

		  displaymode = FULLMODE;
		  (*StartText) ();
		  UpdateDVIStatusLine ();
		  (*StartGraphics) ();
		}
		else
		  if (keyhit == ABORTKEY)
		  {
		    if (fontopen)/* must have been in FullChar */
		    {
		      ClosePXLFile ();
		      (*StartText) ();
		      ClearMessageLine ();/* clear font opening message */
		    }
		  /* no need to set useraborted; DisplayRules()done first */
		    return;
		  }
	  }
	  thischar++;
	}
	thischarinfo = thischarinfo->nextchar;
      }
    }
    if (fontopen)		/* must have been in FullChar */
    {
      ClosePXLFile ();
      (*StartText) ();
      ClearMessageLine ();	/* clear font opening message */
      (*StartGraphics) ();	/* might be more fonts */
    }
    thisfontinfo = thisfontinfo->nextfont;
  }
}

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

Void TerseChar ()
{
/* Display a quick and nasty representation of character only if ref pt visible.
   Just how good the representation is depends on the capabilities of the VDU.
   Some VDUs may be able to download a font (via previous LoadFont call)
   and produce similar results to a FullChar (but much faster!).
   We don't bother checking if glyph is actually all white or non-existent.
*/

  {
    {
      if (PixelVisible (thischarinfo->chartable[thischar].hp, thischarinfo->chartable[thischar].vp))
      /* ref pt of char is visible */
      {
	(*ShowChar) ((*ScaleHpos) (thischarinfo->chartable[thischar].hp - windowleft) + windowh,
	    (*ScaleVpos) (thischarinfo->chartable[thischar].vp - windowtop) + windowv,
	    (char) (thischarinfo->chartable[thischar].code));
	charvisible = TRUE;
      }
      else
	charvisible = FALSE;	/* checked in DisplayChars() */

    }
  }
}

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

short   PixelVisible (hpos, vpos)
int   hpos, vpos;
{
/* Return TRUE iff given paper pixel would be visible in current window. */

  if (allpagevisible)
    return (TRUE);
  if (vpos < windowtop)
    return (FALSE);
  if (vpos > windowbottom)
    return (FALSE);
  if (hpos < windowleft)
    return (FALSE);
  if (hpos > windowright)
    return (FALSE);
  return (TRUE);
}

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

Void BoxChar ()
{
/* Display visible box outlines of glyph.
   Thickness of outlines = 1 screen pixel no matter what the h and v scaling.
*/
  int
        vpmyo, hpmxo,		/* vp-yo, hp-xo: glyph's top and left edges */
        top, bottom, left, right,/* visible edges of glyph */
        scaledtop, scaledleft,	/* scaled visible edges */
        scaledbot, scaledright,
        scaledheight, scaledwidth;/* scaled width and height */
  char
        ch;

  {
    {
      struct chartab *cptr = &thischarinfo->chartable[thischar];
      {
	pixeltableptr pptr = thisfontinfo->pixelptr;
	if (pptr[cptr->code].mapadr == 0)
	  return;		/* glyph all white or absent */
      /* check if any part of glyph is visible */
	vpmyo = cptr->vp - pptr[cptr->code].yo;
	hpmxo = thischarinfo->chartable[thischar].hp - pptr[cptr->code].xo;
	if (RectangleVisible
	    (vpmyo, vpmyo + pptr[cptr->code].ht - 1, hpmxo, hpmxo + pptr[cptr->code].wd - 1,
      /* glyph edges */
	      &top, &bottom, &left, &right)/* visible part */
	  )
	{
	  scaledtop = (*ScaleVpos) (top - windowtop) + windowv;
	  scaledleft = (*ScaleHpos) (left - windowleft) + windowh;
	  if (vscalefactor > 1.0)
	    scaledbot = (*ScaleVpos) (bottom + 1 - windowtop) - 1 + windowv;
	  else
	    scaledbot = (*ScaleVpos) (bottom - windowtop) + windowv;

	  if (hscalefactor > 1.0)
	    scaledright = (*ScaleHpos) (right + 1 - windowleft) - 1 + windowh;
	  else
	    scaledright = (*ScaleHpos) (right - windowleft) + windowh;

	  scaledheight = scaledbot - scaledtop + 1;
	  scaledwidth = scaledright - scaledleft + 1;
	  ch = (char) (cptr->code);
	/* Only show edges that are also glyph outlines! Following method reduces
	   the number of ShowRectangle calls needed for very small boxes. */
	  if (((scaledheight < 3) && (top == vpmyo) && (bottom == vpmyo + pptr[cptr->code].ht - 1)) ||
	      ((scaledwidth < 3)) && ((left == hpmxo) && (right == hpmxo + pptr[cptr->code].wd - 1)))
	    (*ShowRectangle) (scaledleft, scaledtop, scaledwidth, scaledheight, ch);
	  else
	  {
	    if (left == hpmxo)	/* left outline visible */
	      (*ShowRectangle) (scaledleft, scaledtop, 1, scaledheight, ch);

	    if (bottom == vpmyo + pptr[cptr->code].ht - 1)
	    /* bottom outline visible */
	      (*ShowRectangle) (scaledleft, scaledbot, scaledwidth, 1, ch);

	    if (top == vpmyo)	/* top outline visible */
	      (*ShowRectangle) (scaledleft, scaledtop, scaledwidth, 1, ch);

	    if (right == hpmxo + pptr[cptr->code].wd - 1)
	    /* right outline visible */
	      (*ShowRectangle) (scaledright, scaledtop, 1, scaledheight, ch);

	  }
	  charvisible = TRUE;
	}
	else
	  charvisible = FALSE;	/* checked in DisplayChars() */

      }
    }
  }
}

/******************************************************************************/
Void FullChar1 ()
{
/* Display all pixels in a glyph using bitmap from PXL font file.
   This procedure is assigned to DisplayOneChar when h))&&( AND (vscalefactors are
   >= 1.0, so we don't have to worry about scaledheights/widths being 0.
*/
  int
        vpmyo, hpmxo,		/* vp-yo, hp-xo: glyph's top and left edges */
        top, bottom, left, right,/* visible edges of glyph */
        scaledv, scalednextv,	/* scaled vertical positions for rows */
        scaledh,		/* scaled h coord of start of run within row */
        scaledwidth, scaledheight,/* scaled width and height of row */
        thisrow, thisbit;	/* in paper coordinates */
  unsigned int
                wordsperrow,	/* rows of PXL glyph are word aligned */
                firstbit,       /* 0..wordsperrow*32 - 1 */
                firstword,	/* 0..wordsperrow-1 */
                bitpos;		/* 0..31 */
  int   glyphword;		/* current word in bitmap, must be 32 bits */
  short
          inrun;		/* are we in a run of black pixels in row? */

  {
    {
      struct chartab *cptr = &thischarinfo->chartable[thischar];
      {
	pixeltableptr pptr = thisfontinfo->pixelptr;
	if (pptr[cptr->code].mapadr == 0)
	  return;		/* glyph all white or absent */
      /* check if any part of glyph is visible */
	vpmyo = (cptr->vp) - (pptr[cptr->code].yo);
	hpmxo = (cptr->hp) - (pptr[cptr->code].xo);
	if (RectangleVisible
	    (vpmyo, vpmyo + pptr[cptr->code].ht - 1, hpmxo, hpmxo + pptr[cptr->code].wd - 1,
      /* glyph edges */
	      &top, &bottom, &left, &right)/* visible part */
	  )
	{
	  if (!fontopen)	/* only open once */
	  {
	    OpenFontFile ();
	    fontopen = TRUE;
	  }
	  wordsperrow = (pptr[cptr->code].wd + 31) / 32;
	/* words in one row of bitmap */
	  firstbit = CARDINAL ((left - hpmxo));/* first visible bit in row */

	  firstword = firstbit / 32;/* first visible word */
	/* calculate scaled v coord of first visible row */
	  scaledv = (*ScaleVpos) (top - windowtop) + windowv;

	/* only consider visible rows; thisrow = top to bottom */
	  thisrow = top;
	  for (;;)
	  {
	  /* calculate scaled v coord of next row */
	    scalednextv = (*ScaleVpos) (thisrow + 1 - windowtop) + windowv;
	    scaledheight = scalednextv - scaledv;/* can't be 0 */
	  /* move to first byte of first visible word in this row */
	    MoveToPXLByte (4 * (pptr[cptr->code].mapadr + (CARDINAL (thisrow - vpmyo) * wordsperrow) + firstword));
	    glyphword = SignedPXLQuad ();

	    bitpos = 31 - (firstbit % 32);/* 31..0 */
	    inrun = FALSE;

	  /* display black pixel runs in row, doing any h/v expansion */
	  /* only consider visible bits; thisbit = left to right */
	    thisbit = left;
	    for (;;)
	    {
	      if ((1 << bitpos) & glyphword)/* start/continue run */
	      {
		if (!inrun)
		{
		  inrun = TRUE;
		/* remember start of run */
		  scaledh = (*ScaleHpos) (thisbit - windowleft) + windowh;
		}
	      }
	      else
		if (inrun)	/* 0 bit has ended run */
		{
		  inrun = FALSE;
		  scaledwidth = (*ScaleHpos) (thisbit - windowleft) + windowh
		    - scaledh;
		  ShowRectangle
		    (scaledh, scaledv, scaledwidth, scaledheight, (char) (cptr->code));
		}
	      if (thisbit == right)
		break; /* bit loop */
	      if (bitpos == 0)	/* look at first bit in next word of row */
	      {
		glyphword = SignedPXLQuad ();
		bitpos = 31;
	      }
	      else		/* look at next bit in word */
		bitpos--;

	      thisbit++;
	    }			/* bit loop */

	    if (inrun)		/* show run at end of row; INC thisbit */
	    {
	      scaledwidth = (*ScaleHpos) (thisbit + 1 - windowleft) + windowh
		- scaledh;
	      ShowRectangle
		(scaledh, scaledv, scaledwidth, scaledheight, (char) (cptr->code));
	    }
	    if (thisrow == bottom)
	      break; /* row loop */
	    scaledv = scalednextv;
	    thisrow++;
	  }			/* row loop */

	  charvisible = TRUE;
	}
	else
	  charvisible = FALSE;	/* checked in DisplayChars() */

      }
    }
  }
}


/******************************************************************************/
Void FullChar2 ()
{
/* Display all pixels in a glyph using bitmap from PXL font file.
   This procedure is assigned to DisplayOneChar when h/vscalefactor < 1.0.
   The algorithm avoids overlapping rows when vscalefactor < 1.0.
   When hscalefactor < 1.0, it is not worth the extra code to avoid overlapping
   runs of 1 bits because the majority of character glyphs have only one or two
   runs per row.
*/

#define MAXVISWORDS  30

 /* SYSDEP: 30 * 32 = 960 bits wide. Some sites may have very wide glyphs (such as a
    logo). 960 bits represents 3.2in on a 300 dpi device. */

  typedef int glyphrow[MAXVISWORDS - 1];
 /* SYSDEP: BITSET is 32 bit word with elements 31,30,29,...,0 */

  int
        vpmyo, hpmxo,		/* vp-yo, hp-xo: glyph's top and left edges */
        top, bottom, left, right,/* visible edges of glyph */
        scaledv, scalednextv,	/* scaled vertical positions for rows */
        scaledh,		/* scaled horizontal positions within row */
        scaledwidth, scaledheight,/* scaled width and height of row */
        thisrow, thisbit;	/* in paper coordinates */

  glyphrow row;			/* holds VISIBLE bits in one row of glyph; possibly
				   > one row if vscalefactor < 1.0 */
  unsigned char
                wordsperrow,	/* rows of PXL glyph are word aligned */
                firstbit, lastbit,/* somewhere in 0 .. wordsperrow*32-1 */
                firstword, lastword,/* somewhere in 0 .. wordsperrow-1 */
                endword,	/* = visible words in row, - 1 */
                wordpos,	/* 0 .. endword */
                bitpos,		/* 31 .. 0 */
                i;

  short
          inrun;		/* are we in a run of black pixels in row? */

  {
    {
      struct chartab *cptr = &thischarinfo->chartable[thischar];
      {
	pixeltableptr pptr = thisfontinfo->pixelptr;
	if (pptr[cptr->code].mapadr == 0)
	  return;		/* glyph all white or absent */
      /* check if any part of glyph is visible */
	vpmyo = cptr->vp - pptr[cptr->code].yo;
	hpmxo = cptr->hp - pptr[cptr->code].xo;
	if (RectangleVisible (vpmyo, vpmyo + pptr[cptr->code].ht - 1, hpmxo, hpmxo + pptr[cptr->code].wd - 1,
      /* glyph edges */
	      &top, &bottom, &left, &right))/* visible part */
	{
	  if (!fontopen)	/* only open once */
	  {
	    OpenFontFile ();
	    fontopen = TRUE;
	  }
	  wordsperrow = (pptr[cptr->code].wd + 31) / 32;
	/* words in one row of bitmap */
	  firstbit = CARDINAL (left - hpmxo);/* first visible bit */
	  lastbit = CARDINAL (right - hpmxo);/* last visible bit */
	  firstword = firstbit / 32;/* first visible word */
	  lastword = lastbit / 32;/* last visible word */
	  endword = lastword - firstword;

/* #ifdef DEBUG				/* Was #if 1 !! */
	  {
	  /* we impose a limit on width of glyph (unlikely to be exceeded) */
	    if (endword > MAXVISWORDS - 1)
	    {
	      (*StartText) ();
	      ClearMessageLine ();

	      WriteString ("Glyph ");
	      WriteCard ((unsigned)cptr->code);
	      WriteString (" too wide!");
	      WaitForReturn ();
	      (*StartGraphics) ();
	    }
	  }
/* #endif /* DEBUG */

	/* set the visible words in row to 0 */
	  for (i = 0; i <= endword; i++)
	    row[i] = 0;
	/* calculate scaled v coord of first visible row */
	  scaledv = (*ScaleVpos) (top - windowtop) + windowv;

	/* only consider visible rows; thisrow = top to bottom */
	  thisrow = top;
	  for (;;)
	  {
	  /* move to first byte of first visible word in this row */
	    MoveToPXLByte (4 * (pptr[cptr->code].mapadr + (CARDINAL (thisrow - vpmyo) * wordsperrow)
		  + firstword));
	  /* get row of visible words from PXL file and OR with row array */
	    for (wordpos = 0; wordpos <= endword; wordpos++)
	    {
	      row[wordpos] = (int) (SignedPXLQuad ()) | row[wordpos];
	    /* set union */
	    }
	  /* calculate scaled v coord of next row */
	    scalednextv = (*ScaleVpos) (thisrow + 1 - windowtop) + windowv;
	    scaledheight = scalednextv - scaledv;
	    if ((scaledheight > 0) || (thisrow == bottom))
	    {
	    /* display black pixels in row, doing any h/v expansion */
	      if (scaledheight < 1)
		scaledheight = 1;/* avoid 0 */
	      inrun = FALSE;
	      bitpos = 31 - (firstbit % 32);/* 31..0 */
	      wordpos = 0;

	    /* only consider visible bits; thisbit = left to right */
	      thisbit = left;
	      for (;;)
	      {
		if ((1 << bitpos) & row[wordpos])/* start/continue run */
		{
		  if (!inrun)	/* remember start of run */
		  {
		    inrun = TRUE;
		    scaledh = (*ScaleHpos) (thisbit - windowleft) + windowh;
		  }
		}
		else
		  if (inrun)	/* 0 bit has ended run */
		  {
		    inrun = FALSE;
		    scaledwidth = (*ScaleHpos) (thisbit - windowleft) + windowh
		      - scaledh;
		    if (scaledwidth < 1)
		      scaledwidth = 1;/* avoid 0 */
		    ShowRectangle
		      (scaledh, scaledv, scaledwidth, scaledheight, (char) (cptr->code));
		  }
		if (thisbit == right)
		  break; /* bit loop */ ;
		if (bitpos == 0)/* look at first bit in next word of row */
		{
		  wordpos++;
		  bitpos = 31;
		}
		else		/* look at next bit in word */
		{
		  bitpos--;
		}
		thisbit++;
	      }			/* bit loop */
	      if (inrun)	/* show run at end of row; INC thisbit */
	      {
		scaledwidth = (*ScaleHpos) (thisbit + 1 - windowleft) + windowh
		  - scaledh;
		if (scaledwidth < 1)
		  scaledwidth = 1;/* avoid 0 */
		ShowRectangle
		  (scaledh, scaledv, scaledwidth, scaledheight, (char) (cptr->code));
	      }

	      if (thisrow == bottom)
		break; /* row loop */
	    /* else reset the visible words in row to 0 */
	      for (i = 0; i <= endword; i++)
		row[i] = 0;
	    }
	    scaledv = scalednextv;
	    thisrow++;
	  }			/* row loop */

	  charvisible = TRUE;
	}
	else
	{
	  charvisible = FALSE;	/* checked in DisplayChars() */
	}
      }
    }
  }
}

/******************************************************************************/
void OpenFontFile ()
{
/* If thisfontinfo^.fontspec can't be opened then pixel table has been
   loaded with dummyfont values (either the user requested an invalid font
   magnification or the fontspec was truncated in BuildFontSpec().
*/

  (*StartText) ();
  ClearMessageLine ();
  WriteString ("Drawing characters from ");
  {
    if (OpenPXLFile (thisfontinfo->fontspec))
    {
      WriteString (thisfontinfo->fontspec);
      WriteLn ();
    }
    else
      if (OpenPXLFile (dummyfont))
      {
	WriteString ("dummy font!");
	WriteLn ();
      }
      else
      {
#ifdef DEBUG
      /* should never happen since MyPixelTableRoutine()will detect 1st */
	  (*ResetVDU)();
	  WriteLn();
	  WriteString("Bug in OpenFontFile()");
	  WriteLn();
	  RestoreTerminal();
	  exitprog (1);
#endif /* DEBUG */
      }
  }
  (*StartGraphics) ();
}

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

void CheckPageEdges ()
{
/* One or more page edges do not fall within the paper edges.
   This routine is called after the page & paper have been displayed so
   user can see how bad the problem is.
*/

  if (minhp < paperleft)
  {
    ClearMessageLine ();
    WriteString ("Page beyond left edge by ");
    WriteDimension (paperleft - minhp);
    PaperMessage ();
  }
  if (maxhp > paperright)
  {
    ClearMessageLine ();
    WriteString ("Page beyond right edge by ");
    WriteDimension (maxhp - paperright);
    PaperMessage ();
  }
  if (minvp < papertop)
  {
    ClearMessageLine ();
    WriteString ("Page above top edge by ");
    WriteDimension (papertop - minvp);
    PaperMessage ();
  }
  if (maxvp > paperbottom)
  {
    ClearMessageLine ();
    WriteString ("Page below bottom edge by ");
    WriteDimension (maxvp - paperbottom);
    PaperMessage ();
  }
}

/******************************************************************************/
void PaperMessage ()
{
/* Called by CheckPageEdges()to remind user of the paper size. */

  switch (currentunits)
  {
    case INUNITS: 
      WriteString ("in");
      break;
    case CMUNITS: 
      WriteString ("cm");
      break;
    case MMUNITS: 
      WriteString ("mm");
      break;
    case PCUNITS: 
      WriteString ("pc");
      break;
    case PTUNITS: 
      WriteString ("pt");
      break;
    case PXUNITS: 
      WriteString ("px");
      break;
  }
  WriteString ("!   (Paper is ");
  WriteDimension ((int) paperwd);
  WriteString (" by ");
  WriteDimension ((int) paperht);
  Write (')');
  WaitForReturn ();
  ClearMessageLine ();
}

/******************************************************************************/
void Finish ()
{
  CloseDVIFile ();
  (*ClearScreen) ();
  (*MoveToTextLine) (1);
  WriteLn ();
  (*ResetVDU) ();
  RestoreTerminal ();
/* And HALT.
   Note that ResetVDU and RestoreTerminal()should be called before each HALT.
   ResetVDU is called before any final message since it might erase the screen.
*/
}

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



