/* dvireader.c - functions for reading DVI files
   $Id: dvireader.c,v 0.2 1997/03/28 03:16:30 tjchol01 Exp $
   Authors: Andrew Trevorrow, Ian Dall, Geoffrey Tobin, Tomasz J. Cholewo
 */

#include "dvgt.h"
#include "dvireader.h"
#include "screenio.h"
#include "fontreader.h"
#include "options.h"

/* Variables Exported, via "dvireader.h". */
int DVImag;
int totalpages, totalfonts, totalrules;
int currDVIpage;
TeXcounters currTeXpage;
ruleinfo *rulelist, *ruletail;
fontinfo *fontlist, *currfont;
specialinfo *speciallist;
boolean pageempty;
int minhp, minvp, maxhp, maxvp;

time_t  dvi_time;               /* last mod. time for dvi file */
/*----------------------------------------------------------------------
   DECLARATIONS FOR INTERPRETING A DVI PAGE

   The commands between the bop and eop bytes for a particular page need to be
   translated (based on the method used by DVITYPE) before we can determine the
   the position and shape of all rules on that page, as well as the position
   of all characters and which fonts they belong to.
*/

/* Use symbolic names for the opcodes of DVI commands:                     */

#define setchar0        0
    /* setchar1..setchar127 = 1..127                     */
#define set1            128
    /* set2,set3,set4 = 129,130,131                      */
#define setrule         132
#define put1            133
    /* put2,put3,put4 = 134,135,136                      */
#define putrule         137
#define nop             138
#define bop             139
#define eop             140
#define push            141
#define pop             142
#define right1          143
#define w0              147
#define x0              152
#define down1           157
#define y0_             161
#define z0              166
#define right2          144
#define w1              148
#define x1              153
#define down2           158
#define y1_             162
#define z1              167
#define right3          145
#define w2              149
#define x2              154
#define down3           159
#define y2              163
#define z2              168
#define right4          146
#define w3              150
#define x3              155
#define down4           160
#define y3              164
#define z3              169
#define w4              151
#define x4              156
#define y4              165
#define z4              170
#define fntnum0         171
    /* fntnum1..fntnum63 = 172..234                      */
#define fnt1            235
    /* fnt2,fnt3,fnt4 = 236,237,238                      */
#define xxx1            239
    /* xxx2,xxx3,xxx4 = 240,241,242                      */
#define fntdef1         243
    /* fntdef2,fntdef3,fntdef4 = 244,245,246             */
#define pre             247
#define post            248
#define postpost        249
/* undefined commands = 250..255 */

#define maxstacksize    100	/* maximum stack size for state values   */

/*  According to the DVI Driver Standard, Level 0, version 0.5,
   section 2.6.2, max_drift should depend on the output device:

   2  if a "device unit" <= 0.005 in (0.127 mm),
   1  if a "device unit" >  0.005 in (0.127 mm)
   but <= 0.01 in (0.254 mm),
   0  if a "device unit" >  0.01 in (0.254 mm).

   So, the value given here for max_drift implies a device with
   a resolution of at least 200 dpi.

   Note:  max_drift is a number of pixels.
 */

#define max_drift        2	/* prevent hh & vv from drifting too far */


/*----------------------------------------------------------------------
   DECLARATIONS FOR ACCESSING BYTES IN A DVI FILE

   A DVI file is a stream of 8-bit bytes.  Once opened, we can access any byte
   by setting DVIoffset to the required position and then calling 
GetDVIByte.
*/

static int DVIfile;		/* DVI file descriptor                  */
static int DVIoffset;		/* current byte offset in DVIfile       */
static int currDVIbuff;		/* starting byte offset in buffer       */
static buffer DVIbuffer;	/* input buffer                         */
static int postpostid;		/* offset of postpost's id byte         */

/*----------------------------------------------------------------------
   DECLARATIONS FOR GETTING TO A DVI PAGE

   The user can select a particular page by specifying a DVI page
   number (from 1 to totalpages), or a TeX page number (based on the
   values of \count0,\count1,...,\count9), or simply requesting the next page
   in the DVI file (which depends on whether we are ascending or not).
   We will often need to follow the DVI backpointers to locate the bop byte
   of a selected page.
*/

static int curreop;		/* position of eop byte of current page   */
static int currbop;		/* position of bop byte of current page   */
static int lastbop;		/* position of last bop byte              */
static int prevbop;		/* position of bop byte of previous page;
				   note that prevbop of first page = -1   */

static unsigned char DVIcommand;	/* holds next DVI command     */

static int maxstack;		/* max pushes over pops in DVI file      */
static int num = 0;		/* DVI numerator                         */
static int den = 0;		/* DVI denominator                       */
static double xconv = 0.0;	/* converts DVI units to X pixels        */
static double yconv = 0.0;	/* converts DVI units to Y pixels        */
static int h, v;		/* current pos on page in DVI units      */
static int w, x;		/* horizontal increments in DVI units    */
static int y, z;		/* vertical increments in DVI units      */
static int hh, vv;		/* h and v in pixels (approx)            */
static int hhh, vvv;		/* h and v rounded to nearest pixel      */
static int hstack[maxstacksize], vstack[maxstacksize];
    /* push down stacks for state values     */
static int wstack[maxstacksize], xstack[maxstacksize], ystack[maxstacksize],
  zstack[maxstacksize], hhstack[maxstacksize], vvstack[maxstacksize];
static int stackpos;
/* stacks empty when stackpos = 0, i.e., top of stacks = stackpos - 1 */
static int fontspace;		/* used in DoRight and DoDown            */


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

/* Here are the functions used to get byte/s from DVIfile.
   They are essentially the same as those used in DVITYPE.
 */

static int 
GetDVIByte ()
{
  /* Returns the value (unsigned) of the byte at DVIoffset and
     advances DVIoffset for the next GetDVIByte.
   */

  int Result, buffstart, result;

  /* bufflen is a #define-d constant in "dvgt.h" */

  buffstart = DVIoffset / bufflen * bufflen;	/* 0, bufflen, 2*bufflen... */
  if (buffstart != currDVIbuff)
    {
      currDVIbuff = buffstart;
      result = lseek (DVIfile, buffstart, SEEK_SET);

      if (result != buffstart)
	{
	  FATAL ("lseek failed in GetDVIByte!");
	}
      result = read (DVIfile, DVIbuffer, bufflen);

      if (result == -1)
	{
	  FATAL ("read failed in GetDVIByte!");
	}
    }
  Result = DVIbuffer[DVIoffset - buffstart];
  DVIoffset++;
  return Result;
}
/* GetDVIByte */

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

static int 
SignedDVIByte ()
{
  /* the next byte, signed */
  int b;

  b = GetDVIByte ();
  if (b < 128)
    return b;
  else
    return (b - 256);
}
/* SignedDVIByte */

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

static int 
GetTwoDVIBytes ()
{
  /* the next 2 bytes, unsigned */
  int a, b;

  a = GetDVIByte ();
  b = GetDVIByte ();
  return (a * 256 + b);
}
/* GetTwoDVIBytes */

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

static int 
SignedDVIPair ()
{
  /* the next 2 bytes, signed */
  int a, b;

  a = GetDVIByte ();
  b = GetDVIByte ();
  if (a < 128)
    return (a * 256 + b);
  else
    return ((a - 256) * 256 + b);
}
/* SignedDVIPair */

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

static int 
GetThreeDVIBytes ()
{
  /* the next 3 bytes, unsigned */
  int a, b, c;

  a = GetDVIByte ();
  b = GetDVIByte ();
  c = GetDVIByte ();
  return ((a * 256 + b) * 256 + c);
}
/* GetThreeDVIBytes */

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

static int 
SignedDVITrio ()
{
  /* the next 3 bytes, signed */
  int a, b, c;

  a = GetDVIByte ();
  b = GetDVIByte ();
  c = GetDVIByte ();
  if (a < 128)
    return ((a * 256 + b) * 256 + c);
  else
    return (((a - 256) * 256 + b) * 256 + c);
}
/* SignedDVIOqTrio */


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

static int 
SignedDVIQuad ()
{
  /* the next 4 bytes, signed */
  int w;
  byte3 (w) = GetDVIByte ();
  byte2 (w) = GetDVIByte ();
  byte1 (w) = GetDVIByte ();
  byte0 (w) = GetDVIByte ();
  return (w);
}
/* SignedDVIQuad */

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

static void 
ProcessPostamble ()
{
  /* Having successfully opened the DVI file, we find the postamble
     and initialize these global variables:
     lastbop, num, den, DVImag, maxstack, totalpages.
     The font definitions are read by ProcessFontDefs.
   */

  int postamblepos, postamble, pagehtplusdp, pagewidth;

  DVIoffset = postpostid - 4;
  postamblepos = SignedDVIQuad ();	/* get post_post's postamble ptr */
  DVIoffset = postamblepos;
  postamble = GetDVIByte ();
  lastbop = SignedDVIQuad ();
  num = SignedDVIQuad ();
  den = SignedDVIQuad ();
  DVImag = SignedDVIQuad ();
  pagehtplusdp = SignedDVIQuad ();
  pagewidth = SignedDVIQuad ();
  maxstack = SignedDVIPair ();
  totalpages = SignedDVIPair ();
  if (maxstack <= maxstacksize)
    return;

  FATAL ("Stack capacity exceeded!");
  /* now we don't need to test for stack overflow in DoPush */
}
/* ProcessPostamble */

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

static void 
ProcessFontDefs ()
{
  /* Read the fntdef commands in the postamble (fntdef commands in the DVI
     pages will be skipped) and store the information in the font list.
     (Note that complete fontspecs are NOT built here because DVIReader does not
     want to know about the format or naming conventions of font files.)
     Since ProcessPostamble ended by reading the totalpages parameter, the
     next GetDVIByte should return nop or first fntdef.
   */

  int f = 0, c, s, d, a, l;	/* hold fntdef parameters */
  int i;
  char ch;			/* for getting farea and fname */
  String farea, fname;		/* a and l bytes long respectively */

  totalfonts = 0;		/* number of nodes in font list */
  fontlist = (fontinfo *) NULL;
  do
    {
      DVIcommand = GetDVIByte ();
      if (DVIcommand >= fntdef1 && DVIcommand <= fntdef1 + 3)
	{
	  switch (DVIcommand - fntdef1)
	    {
	    case 0:
	      f = GetDVIByte ();
	      break;

	    case 1:
	      f = GetTwoDVIBytes ();
	      break;

	    case 2:
	      f = GetThreeDVIBytes ();
	      break;

	    case 3:
	      f = SignedDVIQuad ();
	      break;
	    }
	  c = SignedDVIQuad ();	/* checksum; ignore it */
	  s = SignedDVIQuad ();	/* scaled size */
	  d = SignedDVIQuad ();	/* design size */
	  a = GetDVIByte ();	/* length of font area */
	  l = GetDVIByte ();	/* length of font name */

	  /* read and store font area */
	  for (i = 0; i < a; i++)
	    {
	      ch = GetDVIByte ();
	      if (i < maxfontspec)
		farea[i] = ch;
	    }
	  farea[i <= maxfontspec ? i : maxfontspec] = '\0';

	  /* read and store font name */
	  for (i = 0; i < l; i++)
	    {
	      ch = GetDVIByte ();
	      if (i < maxfontspec)
		fname[i] = ch;
	    }

	  fname[i <= maxfontspec ? i : maxfontspec] = '\0';

	  currfont = (fontinfo *) Malloc (sizeof (fontinfo));
	  currfont->fontused = false;
	  currfont->fontnum = f;
	  currfont->scaledsize = s;
	  currfont->designsize = d;
	  strncpy (currfont->fontarea, farea, maxstring);
	  currfont->fontarea[maxstring] = '\0';		/* to be careful */
	  currfont->fontarealen = a;
	  strncpy (currfont->fontname, fname, maxstring);
	  currfont->fontname[maxstring] = '\0';		/* to be careful */
	  currfont->fontnamelen = l;
	  currfont->fontspec[0] = '\0';
	  currfont->fontspeclen = 0;	/* fontspec is built in FontReader */
	  currfont->fontexists = false;		/* becomes TRUE if fontspec can be opened */
	  currfont->totalchars = 0;

	  /* first node allocated in DoFont */
	  currfont->charlist = (charinfo *) NULL;

	  /* nodes are added to tail of char list */
	  currfont->chartail = (charinfo *) NULL;

	  /* allocated once per font; see DoFont */
	  currfont->pixelptr = (_REC_pixeltable *) NULL;

	  currfont->nextfont = fontlist;
	  fontlist = currfont;	/* add new font to head of list */
	  totalfonts++;
	}
      else if (DVIcommand != nop)
	{
	  if (DVIcommand != postpost)
	    FATAL1 ("Unexpected DVI command in postamble = %d", DVIcommand);
	  /* we have reached end of postamble */
	}
    }
  while (DVIcommand != postpost);

  /* nop commands can occur between DVI commands */
}
/* ProcessFontDefs */

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

void 
OpenDVIFile (char *name)
{
  /* If the given file can be opened and is a valid TeX82 DVI file then
     the following global variables are initialized:

     DVImag      := magnification value stored in DVI file (TeX's \mag)
     totalpages  := total number of pages in DVI file
     currDVIpage := 0      (and remains so until a page is selected)
     currTeXpage := ten 0s (ditto)
     totalfonts  := total number of fonts in DVI file (= nodes in font list)
     fontlist->  (nodes are added to head of list)
     fontused   := FALSE
     fontnum    := internal DVI font number
     scaledsize := scaled size of font (in DVI units)
     designsize := design size of font (in DVI units)
     fontarea   := a string of min (fontarealen, maxfontspec) characters
     fontname   := a string of min (fontnamelen, maxfontspec) characters
     fontspec   := a null string (fontspeclen := 0)
     fontexists := FALSE
     totalchars := 0
     charlist   := NIL
     chartail   := NIL
     pixelptr   := NIL
     nextfont   := next node in font list (if not NIL)
   */

#if 0    
  struct stat fstatbuf;
#endif

  currDVIbuff = -1;		/* impossible value for first GetDVIByte */
  DVIfile = open (name, O_RDONLY, 0);
  if (DVIfile >= 0)
    {
      int i;
#if 0
      fstat (DVIfile, &fstatbuf);
      if (S_ISDIR(fstatbuf.st_mode))
          FATAL1 ("`%s' is a directory\n", name);
      dvi_time = fstatbuf.st_mtime;
#endif

      /* get offset of last DVI byte */
      DVIoffset = lseek (DVIfile, 0, SEEK_END);
      if (DVIoffset == -1)
	FATAL ("Failed to find end of file!");
      if (DVIoffset == 0)
	FATAL ("DVI file is empty!");
      DVIoffset--;

      /* In the following, DVIoffset == 1 signifies the start of the file. */

      /* skip any NULs */
      while (GetDVIByte () == 0 && DVIoffset != 1)
	{
	  DVIoffset -= 2;	/* GetDVIByte increments */
	}
      DVIoffset--;

      /* skip 223s */
      while (GetDVIByte () == 223 && DVIoffset != 1)
	{
	  DVIoffset -= 2;	/* GetDVIByte increments */
	}

      DVIoffset--;
      postpostid = DVIoffset;	/* remember offset of id byte */
      if (GetDVIByte () != 2)
	{
	  FATAL ("Not a valid DVI file!");
	  /*NOTREACHED */
	  return;
	}
      ProcessPostamble ();	/* get DVImag, totalpages, etc */
      ProcessFontDefs ();	/* build and initialize font list */
      currDVIpage = 0;		/* we haven't processed a page yet */
      for (i = 0; i <= 9; i++)
	currTeXpage[i] = 0;

      return;
    }
  else
    FATAL1 ("Couldn't open DVI file: `%s'\n", name);
}
/* OpenDVIFile */
/*--------------------------------------------------------------------*/
#if 0
boolean
check_dvi_file (char *DVIname)
{
    struct stat statbuf;
    if (DVIname == NULL || stat (DVIname, &statbuf) != 0 || statbuf.st_mtime != dvi_time) {
        if (DVIname) {
            Fclose(DVIname);
            DVIname = NULL;
            if (list_fonts) Putchar('\n');

        }
    }
}
#endif
/*--------------------------------------------------------------------*/
static void 
SkipFntdef (int which)
{
  /* Read past a fntdef command without interpreting it. */

  int dummy, a, l, i;

  switch (which)
    {				/* which = DVIcommand - fntdef1 */
    case 0:
      dummy = GetDVIByte ();
      break;
    case 1:
      dummy = GetTwoDVIBytes ();
      break;
    case 2:
      dummy = GetThreeDVIBytes ();
      break;
    case 3:
      dummy = SignedDVIQuad ();
      break;
    }
  dummy = SignedDVIQuad ();
  dummy = SignedDVIQuad ();
  dummy = SignedDVIQuad ();

  a = GetDVIByte ();		/* length of directory */
  l = GetDVIByte ();		/* length of font name */
  for (i = 1; i <= l + a; i++)
    dummy = GetDVIByte ();
}
/* SkipFntdef */

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

static void 
ReadFirstBop ()
{

  /* Read first bop by skipping past preamble; update currbop and currDVIpage. */
  int k, i, dummy;

  DVIoffset = 14;		/* position of preamble's k parameter */
  k = GetDVIByte ();		/* length of x parameter */
  for (i = 1; i <= k; i++)	/* skip preamble comment */
    dummy = GetDVIByte ();
  do
    {				/* skip any nops and fntdefs */
      DVIcommand = GetDVIByte ();
      if (DVIcommand != nop && DVIcommand != bop)
	{
	  if (DVIcommand >= fntdef1 && DVIcommand <= fntdef1 + 3)
	    SkipFntdef (DVIcommand - fntdef1);
	  else
	    FATAL1 ("Unexpected DVI command before first bop = %d.",
		    DVIcommand);
	}
      /* do nothing */
    }
  while (DVIcommand != bop);
  currbop = DVIoffset - 1;	/* position in DVI file of first bop */
  currDVIpage = 1;
}
/* ReadFirstBop */

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

static void 
ReadNextBop ()
{
  /* We are currently positioned after an eop byte which we know is not the
     last.  This routine positions us after the next bop byte and updates
     currbop and currDVIpage.
   */

  /* skip any nops and fntdefs */
  do
    {
      DVIcommand = GetDVIByte ();
      if (DVIcommand != nop && DVIcommand != bop)
	{
	  if (DVIcommand >= fntdef1 && DVIcommand <= fntdef1 + 3)
	    SkipFntdef (DVIcommand - fntdef1);
	  else
	    FATAL1 ("Unexpected DVI command between eop and bop = %d.",
		    DVIcommand);
	}
      /* do nothing */
    }
  while (DVIcommand != bop);
  currbop = DVIoffset - 1;	/* position in DVI file of this bop */
  currDVIpage++;
}
/* ReadNextBop */

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

static void 
ReadBopParameters ()
{

  /* We should now be positioned after the bop of desired page, so read
     the 10 TeX counters and update currTeXpage and prevbop.
     At the end of this routine we will be at the byte after currbop's parameters
     and ready to InterpretPage.
   */
  int i;

  for (i = 0; i <= 9; i++)
    currTeXpage[i] = SignedDVIQuad ();
  prevbop = SignedDVIQuad ();	/* position of previous bop in DVI file */
}
/* ReadBopParameters */

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

void 
MoveToNextPage (boolean ascending)
{
  /* MoveToNextPage will select the next page, depending on the current page
     and the specified direction.  If the value of currDVIpage is 0 (set in
     OpenDVIFile), then MoveToNextPage will select the first page if ascending is
     TRUE and the last page if ascending is FALSE.  If currDVIpage is > 0 then
     MoveToNextPage will select currDVIpage+1 if ascending (unless currDVIpage =
     totalpages, in which case it does nothing), or currDVIpage-1 if descending
     (unless currDVIpage = 0).

     Before calling InterpretPage, DVItoVDU must position DVIReader to the
     desired page by calling one of the MoveTo... routines.
     The global variables updated if a page is located by any MoveTo... call are:
     currDVIpage := the current DVI page (1..totalpages)
     currTeXpage := the ten TeX counter values stored with this page
     Note that currDVIpage is initially 0 until one of these routines succeeds.
   */

  /* keep within limits of document */
  if ((currDVIpage != 1 && !ascending)
      || (currDVIpage != totalpages && ascending))
    {
      if (currDVIpage == 0)
	{			/* special value */
	  /* we haven't processed a page yet */
	  if (ascending)	/* get first page */
	    ReadFirstBop ();
	  else
	    {			/* get last page */
	      currbop = lastbop;
	      DVIoffset = currbop + 1;
	      currDVIpage = totalpages;
	    }
	}
      else
	{
	  if (ascending)
	    {
	      /* currently positioned after eop of currDVIpage, so get next bop */
	      ReadNextBop ();
	    }
	  else
	    {
	      /* move to bop pointed to by currbop's backpointer */
	      currbop = prevbop;
	      DVIoffset = currbop + 1;	/* move to byte after previous bop */
	      currDVIpage--;
	    }
	}
      ReadBopParameters ();	/* update currTeXpage and prevbop */
    }
}
/* MoveToNextPage */

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

void 
MoveToDVIPage (int n)
{
  /* Move to nth DVI page; n should be in 1..totalpages. */

  if (n < 1 || n > totalpages)	/* do nothing */
    goto _L999;

  if (n == 1)
    {
      /* Note that this test must come before next test so that we avoid any
         problems when currDVIpage initially = 0. */
      ReadFirstBop ();
    }
  else if (n == currDVIpage + 1)
    {
      ReadNextBop ();
    }
  else
    {
      if (n < currDVIpage)
	{
	  currbop = prevbop;	/* start searching backwards from previous page */
	  currDVIpage--;
	}
      else if (n > currDVIpage)
	{
	  currbop = lastbop;	/* start searching backwards from last page */
	  currDVIpage = totalpages;
	}
      /* if n = currDVIpage we'll just move back to currbop */
      /* n is now <= currDVIpage so search by following backpointers */
      while (true)
	{
	  if (n == currDVIpage)
	    {
	      DVIoffset = currbop + 1;	/* move to byte after currbop */
	      break;		/* escape from "infinite" loop */
	    }
	  DVIoffset = currbop + 41;	/* move to location of backpointer */
	  currbop = SignedDVIQuad ();	/* get location of previous page */
	  currDVIpage--;
	}			/* while */
    }				/* if */
  ReadBopParameters ();		/* update currTeXpage and prevbop */
_L999:;
}
/* MoveToDVIPage */

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

static boolean 
CurrMatchesNew (TeXpageinfo * newTeXpage)
{
  /* Return TRUE iff currTeXpage matches newTeXpage. */

  boolean Result;
  int i, FORLIM;

  Result = true;
  FORLIM = newTeXpage->lastvalue;
  for (i = 0; i <= FORLIM; i++)
    {
      if (newTeXpage->present[i])
	{
	  if (newTeXpage->value[i] != currTeXpage[i])
	    Result = false;
	}
    }
  return Result;
}
/* CurrMatchesNew */

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

boolean 
MoveToTeXPage (TeXpageinfo * newTeXpage)
{
  /* MoveToTeXPage will search for the lowest DVI page matching the given
     TeX page specification.

     (TeX stores the values of \count0,\count1, ..., \count9 with every DVI
     page.  Plain TeX uses \count0 to control page numbering.)

     newTeXpage is a VAR parameter only for efficiency; it won't be changed.
     The value array stores the requested counter values, the present array
     indicates which counters are relevant and lastvalue indicates
     the position (0..9) of the last relevant counter.

     DVItoVDU converts a more friendly representation
     of a TeX page request into the TeXpageinfo format.  For example,
     [2..5] would be converted to:     and [] would be converted to:
     value     = [2,?,5,?,?,?,?,?,?,?]     value     = [?,?,?,?,?,?,?,?,?,?]
     present   = [T,F,T,?,?,?,?,?,?,?]     present   = [F,?,?,?,?,?,?,?,?,?]
     lastvalue =      2                    lastvalue =  0
     MoveToTeXPage returns TRUE iff the requested TeX page is located.
   */

  boolean Result;

  int savecurrbop = 0, savecurrDVIpage, nextbop, i;
  boolean atleastone;

  /* save away current page and DVI position */
  savecurrDVIpage = currDVIpage;
  if (currDVIpage != 0)		/* only if we've processed a page */
    savecurrbop = currbop;
  /* note that curreop is saved in last InterpretPage */
  /* search backwards through all DVI pages for lowest matching page */
  atleastone = false;
  nextbop = lastbop;
  for (i = totalpages; i >= 1; i--)
    {
      DVIoffset = nextbop + 1;
      ReadBopParameters ();	/* update currTeXpage and prevbop */
      if (CurrMatchesNew (newTeXpage))
	{
	  currbop = nextbop;
	  currDVIpage = i;
	  atleastone = true;
	}
      nextbop = prevbop;
    }
  if (!atleastone)
    {				/* no match, so restore currDVIpage */
      currDVIpage = savecurrDVIpage;
      if (currDVIpage != 0)
	{			/* restore page and positioning info */
	  currbop = savecurrbop;
	  DVIoffset = currbop + 1;
	  ReadBopParameters ();	/* restore currTeXpage and prevbop */
	  DVIoffset = curreop + 1;
	  /* we should now be after the eop byte of the original page */
	}
      Result = false;
      goto _L999;
    }
  DVIoffset = currbop + 1;
  ReadBopParameters ();		/* update currTeXpage and prevbop */
  Result = true;
_L999:
  return Result;

  /* we found lowest matching page */
}
/* MoveToTeXPage */

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

void 
SetConversionFactors (double xres, double yres, double magnification)
{
/* Arguments:
   xres:  horizontal resolution of output device in dots per inch.
   yres:  vertical resolution.
   magnification:  magnification of document;  1 means normal size.
 */
  /* This routine must be called before the first InterpretPage call.
     DVIReader needs to know the resolution and magnification values
     before it attempts to convert DVI units into pixel values.
   */

  /* GT - The "254000.0" corresponds to ten thousand units per mm. */
  /* Refer to TUG's DVI Driver standard. */

  xconv = (num / 254000.0 * xres / den * magnification);
  yconv = (num / 254000.0 * yres / den * magnification);
}

/* SetConversionFactors */

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

static int 
Sign (double d)
{
  return (d < 0.0 ? -1 : 1);
}
/* Sign */

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

static void 
InitStateValues ()
{
  /* Initialize state values and stack. */

  /* This is the only place in "dvireader.c" */
  /* where hoffset, voffset appear.          */
  /* The offsets are set in "options.c". */
  /* The other extern variables are static in "dvireader.c". */

  double hox, voy;		/* offsets divided by conversion factors */

  hox = hoffset / xconv;
  voy = voffset / yconv;

  hh = hoffset;			/* 0 if no horizontal shift specified */
  vv = voffset;			/* 0 if no vertical shift specified */
  h = Sign (hox) * (int) ((hox >= 0.0 ? hox : -hox) + 0.5);
  v = Sign (voy) * (int) ((voy >= 0.0 ? voy : -voy) + 0.5);
  w = 0;
  x = 0;
  y = 0;
  z = 0;
  stackpos = 0;
  fontspace = 0;		/* for DoRight and DoDown before a DoFont call */
}

/* InitStateValues */

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

static void 
InitPage ()
{
  /* Initialize page so that there are no fonts, chars, rules, specials. */

  pageempty = true;		/* gt - assumed until a char or rule is found */

  /* page edges will change if there is at least one char or rule on page */

  /* gt - these shouldn't be necessary, but to be on the safe side ... */
  minhp = 0;
  minvp = 0;
  maxhp = 0;
  maxvp = 0;

  currfont = fontlist;
  while (currfont != (fontinfo *) NULL)
    {
      if (currfont->fontused)
	{			/* only reset those fonts used in last page */
	  currfont->fontused = false;

	  /* deallocate char list completely; DoFont will allocate first node */
	  currfont->totalchars = 0;
	  while (currfont->charlist != (charinfo *) NULL)
	    {
	      charinfo *thischar;	/* temporary pointer to node in charlist */
	      thischar = currfont->charlist;
	      currfont->charlist = thischar->nextchar;
	      Free (thischar, charinfo);
	    }
	  currfont->chartail = (charinfo *) NULL;
	  /* pixel table remains allocated */
	}
      currfont = currfont->nextfont;
    }
  currfont = (fontinfo *) NULL;	/* current font's undefined at start of page */

  /* deallocate rule information except for one node (for DoSet/PutRule) */
  totalrules = 0;
  while (rulelist != ruletail)
    {
      ruleinfo *thisrule;	/* temporary pointer to node in rulelist */
      thisrule = rulelist;
      rulelist = thisrule->nextrule;
      Free (thisrule, ruleinfo);
    }
  rulelist->rulecount = 0;	/* no rules in this node */
  rulelist->nextrule = (ruleinfo *) NULL;

  /* deallocate \special information */
  while (speciallist != (specialinfo *) NULL)
    {
      specialinfo *thisspecial;	/* temporary ptr to node in speciallist */
      thisspecial = speciallist;
      speciallist = speciallist->nextspecial;
      Free (thisspecial, specialinfo);
    }
}
/* InitPage */

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

int 
XPixelRound (int DVIunits)
{
  /* Return the nearest number of X pixels in the given DVI X dimension. */

  /* According to the DVI Driver Standard, Level 0, draft 0.05, */
  /* section 2.6.2, "Changes in position due to characters and  */
  /* rules",                                                    */
  /* pixel_round (n) = sign (Kn) * floor (abs (Kn) + 0.5),      */
  /* where sign (i) = -1 if i < 0 and = 1 if i >= 0,            */
  /* and K is xconv for horizontal and yconv for vertical       */
  /* dimensions.                                                */

  double Kn = xconv * (double) DVIunits;
  return (Sign (Kn) * (int) ((Kn >= 0.0 ? Kn : -Kn) + 0.5));
}

/* XPixelRound */

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

int 
YPixelRound (int DVIunits)
{
  /* Return the nearest number of Y pixels in the given DVI Y dimension. */

  /* According to the DVI Driver Standard, Level 0, draft 0.05, */
  /* section 2.6.2, "Changes in position due to characters and  */
  /* rules",                                                    */
  /* pixel_round (n) = sign (Kn) * floor (abs (Kn) + 0.5),      */
  /* where sign (i) = -1 if i < 0 and = 1 if i >= 0,            */
  /* and K is xconv for horizontal and yconv for vertical       */
  /* dimensions.                                                */

  double Kn = yconv * (double) DVIunits;
  return (Sign (Kn) * (int) ((Kn >= 0.0 ? Kn : -Kn) + 0.5));
}

/* YPixelRound */

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

static void 
DoSetPutChar (int ch, boolean set)
{
  /* Add char info to current chartable, update our horizontal
     position on the page and check the page edges.
   */
  /* putchar: Exactly the same as setchar, but we DON'T update the horizontal
     position on the page.  (We still have to check page edges.)
   */

  if (ch > maxTeXchar)
    {				/* ignore ch */
      String codestr;
      sprintf (codestr, "%d", ch);
      MesgString ("Unknown character (code ");
      MesgString (codestr);
      MesgString (") from ");
      MesgString (currfont->fontspec);
    }
  else
    {				/* ch is valid */
      charinfo *char_info = currfont->chartail;		/* may be new chartable */
      _REC_chartable *char_tab = (_REC_chartable *) NULL;
      _REC_pixeltable *pix_tab = (_REC_pixeltable *) NULL;

      if (char_info->charcount == chartablesize)
	{
	  char_info->nextchar = (charinfo *) Malloc (sizeof (charinfo));
	  /* add new node to end of char list */
	  char_info->nextchar->charcount = 0;	/* reset charcount */
	  char_info->nextchar->nextchar = (charinfo *) NULL;
	  currfont->chartail = char_info->nextchar;
	}
      /* allocate a new chartable */

      char_info = currfont->chartail;
      char_tab = &char_info->chartable[char_info->charcount];
      char_tab->hp = hh;
      char_tab->vp = vv;
      char_tab->code = ch;

      pix_tab = &currfont->pixelptr[ch];

      /* do page edges increase? */

      if (pageempty || hh - pix_tab->xo < minhp)
	minhp = hh - pix_tab->xo;
      if (pageempty || vv - pix_tab->yo < minvp)
	minvp = vv - pix_tab->yo;
      if (pageempty || hh + pix_tab->wd - pix_tab->xo - 1 > maxhp)
	maxhp = hh + pix_tab->wd - pix_tab->xo - 1;
      if (pageempty || vv + pix_tab->ht - pix_tab->yo - 1 > maxvp)
	maxvp = vv + pix_tab->ht - pix_tab->yo - 1;

      /* the above checks ensure that page edges include all black
         pixels in glyph, but we also want to include reference point */

      if (pageempty || hh < minhp)
	minhp = hh;
      if (pageempty || vv < minvp)
	minvp = vv;
      if (pageempty || hh > maxhp)
	maxhp = hh;
      if (pageempty || vv > maxvp)
	maxvp = vv;

      if (set)
	{
	  /* add DVI width calculated in PixelTableRoutine */
	  h += pix_tab->dwidth;

	  /* add pixel width calculated in PixelTableRoutine */
	  hh += pix_tab->pwidth;

	  /* use hhh and max_drift to prevent hh drifting too far from h */

	  hhh = XPixelRound (h);

	  if (abs (hhh - hh) > max_drift)
	    {
	      /* Adjust hh to be just within the allowed tolerance from h.  */
	      hh = hhh + (hhh > hh ? -max_drift : max_drift);
	    }
	}
      currfont->totalchars++;
      char_info->charcount++;
      pageempty = false;
    }
}
/* DoSetChar */


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

static void 
DoPush ()
{
  /* Push state values onto stack.
     No need to test for stack overflow since we compare maxstack and
     maxstacksize in ProcessPostamble.
   */

  hstack[stackpos] = h;
  vstack[stackpos] = v;
  wstack[stackpos] = w;
  xstack[stackpos] = x;
  ystack[stackpos] = y;
  zstack[stackpos] = z;
  hhstack[stackpos] = hh;
  vvstack[stackpos] = vv;
  stackpos++;
}

/* DoPush */

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

static void 
DoPop ()
{
  /* Pop state values from top of stack. */

  if (stackpos == 0)
    {
      FATAL ("Stack empty!");
    }
  stackpos--;
  h = hstack[stackpos];
  v = vstack[stackpos];
  w = wstack[stackpos];
  x = xstack[stackpos];
  y = ystack[stackpos];
  z = zstack[stackpos];
  hh = hhstack[stackpos];
  vv = vvstack[stackpos];
}
/* DoPop */

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

static void 
DoRight (int amount)
{
  /* Move the reference point horizontally by given amount (usually +ve).
     When the amount is small, like a kern, hh changes by rounding
     the amount; but when the amount is large, hh changes by rounding
     the true position h so that accumulated rounding errors disappear.
   */

  h += amount;

  /* GT - Wrong?  DVI Driver Standard specifies:
     "if (processor uses TFM)
     word_space = space - space_shrink;
     else
     quad = magnification * (design size of font);
     word_space = 0.2 * quad;
     fi;

     back_space = 0.9 * quad;

     if (amount < word_space && amount > -back_space)".

     So,     TFM -> space, space_shrink;
     else -> design size.

     Now, how do we learn those quantities?
   */

  if (amount < fontspace && amount > -fontspace * 4)
    {
      hh += XPixelRound (amount);

      /* use hhh and max_drift to prevent hh drifting too far from h */

      hhh = XPixelRound (h);
      if (abs (hhh - hh) > max_drift)
	{
	  /* Adjust hh to be just within the allowed tolerance from h.  */
	  hh = hhh + (hhh > hh ? -max_drift : max_drift);
	}
    }
  else
    {
      hh = XPixelRound (h);
    }
}
/* DoRight */

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

static void 
DoDown (int amount)
{
  /* Move the reference point vertically by given amount (usually +ve).
     Rounding is done similarly to DoRight but with the threshold between
     small and large amounts increased by a factor of 5.
   */

  v += amount;

  /* GT - Wrong?  DVI Driver Standard specifies:
     "if (abs (y) < 0.8 * quad)".
   */

  if (abs (amount) < fontspace * 5)
    {
      vv += YPixelRound (amount);

      /* use vvv and max_drift to prevent vv drifting too far from v */

      vvv = YPixelRound (v);
      if (abs (vvv - vv) > max_drift)
	{
	  /* Adjust vv to be just within the allowed tolerance from v.  */
	  vv = vvv + (vvv > vv ? -max_drift : max_drift);
	}
    }
  else
    {
      vv = YPixelRound (v);
    }
}
/* DoDown */

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

static int 
RulePixels (double conv, int DVIunits)
{
  /* Return the number of pixels in the given height or width of a rule
     using the method recommended in DVITYPE.
   */

  int n = (int) (conv * DVIunits);
  if (n < conv * DVIunits)
    return (n + 1);
  else
    return n;
}
/* RulePixels */

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

static void 
DoPutSetRule (int height, int width, unsigned char DVIcommand)
{
  /* Exactly the same as DoSetRule, but we DON'T update the horizontal
     position on the page.  (We still have to check page edges.)
   */
  _REC_ruletable *ruletab;

  if (height > 0 && width > 0)
    {

      /* may be new ruletable */
      if (ruletail->rulecount == ruletablesize)
	{
	  ruletail->nextrule = (ruleinfo *) Malloc (sizeof (ruleinfo));
	  /* add new node to end of rule list */
	  ruletail->nextrule->rulecount = 0;	/* reset rulecount */
	  ruletail->nextrule->nextrule = (ruleinfo *) NULL;
	  ruletail = ruletail->nextrule;
	}
      /* allocate a new ruletable */
      ruletab = &ruletail->ruletable[ruletail->rulecount];
      ruletab->hp = hh;
      ruletab->vp = vv;
      ruletab->wd = RulePixels (xconv, width);
      ruletab->ht = RulePixels (yconv, height);

      /* do page edges increase? */

      /* ref pt of rule is bottom left black pixel */

      if (pageempty || hh < minhp)
	minhp = hh;
      if (pageempty || vv > maxvp)
	maxvp = vv;

      if (pageempty || hh + ruletab->wd - 1 > maxhp)
	maxhp = hh + ruletab->wd - 1;
      if (pageempty || vv - ruletab->ht + 1 < minvp)
	minvp = vv - ruletab->ht + 1;

      totalrules++;
      ruletail->rulecount++;
      pageempty = false;
    }

  if (DVIcommand == setrule)
    {
      hh += RulePixels (xconv, width);
      h += width;

      /* use hhh and max_drift to prevent hh drifting too far from h */

      hhh = XPixelRound (h);

      if (abs (hhh - hh) > max_drift)
	{
	  /* Adjust hh to be just within the allowed tolerance from h.  */
	  hh = hhh + (hhh > hh ? -max_drift : max_drift);
	}
    }
}				/* DoPutSetRule */

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

static void 
DoFont (int externf)
{
  /* Search font list for externf, setting currfont and fontspace.
     If this is the first time we've seen this font (on current page) then
     we need to allocate the first chartable.
     If this is the first time we've seen this font used at all then we
     allocate a pixeltable and call routine to fill it in.
   */

  charinfo *char_info = (charinfo *) NULL;

  currfont = fontlist;
  while (currfont != (fontinfo *) NULL && currfont->fontnum != externf)
    {
      currfont = currfont->nextfont;
    }

  if (currfont == NULL)
    FATAL1 ("Failed to find font #%d.", externf);
  if (!currfont->fontused)
    {
      currfont->fontused = true;
      currfont->charlist = (charinfo *) Malloc (sizeof (charinfo));
      /* allocate first chartable */
      char_info = currfont->charlist;
      char_info->charcount = 0;	/* for DoSet/PutChar */
      char_info->nextchar = (charinfo *) NULL;	/* this node is also last */
      currfont->chartail = currfont->charlist;
      if (currfont->pixelptr == (_REC_pixeltable *) NULL)
	{			/* first time we've seen this font */
	  currfont->pixelptr = (_REC_pixeltable *) Malloc (sizeof (pixeltable));
	  PixelTableRoutine (currfont);
	}
    }
  /* do nothing since we've already used this font on this page */

  fontspace = currfont->scaledsize / 6;

  /* See DVITYPE; a 3-unit thin space.
     Note that a thin space is 1/6 of a quad, where a quad is
     1 em in the current font and usually equals the design size.
   */

}
/* DoFont */

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

static void 
DoSpecial (int hpos, int vpos, int totalbytes)
{
  /* in */

  /* DVIReader has seen a \special command while interpreting the current page.
     It will pass the current page position and number of bytes in the command.
     We save the info away in speciallist for later use by the main program.
   */
  int i;
  specialinfo *temp;

  temp = (specialinfo *) Malloc (sizeof (specialinfo));
  for (i = 0; i < totalbytes; i++)
    {
      int temp1;
      temp1 = GetDVIByte ();
      if (i < maxspeciallen)
	temp->special[i] = temp1;
    }
  temp->special[i <= maxspeciallen ? i : maxspeciallen] = '\0';

  temp->hp = hpos;
  temp->vp = vpos;
  temp->nextspecial = speciallist;
  speciallist = temp;		/* add new info to head of list */
}
/* DoSpecial */

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

void 
InterpretPage ()
{
  /* When this routine is called we are positioned after the bytes of a bop
     command (i.e., at currbop + 45).  At the end of this routine we will be
     positioned after the eop byte for the current page.  In between we carry
     out the important task of translating the DVI description of this page
     and filling in the following global data structures:

     totalrules := number of rules on page
     rulelist-> (nodes are added to tail of rule list)
     rulecount  := number of rules in this ruletable
     ruletable[0..rulecount-1].
     hp, vp  := reference point of a rule
     wd, ht  := pixel dimensions of a rule (both > 0)
     nextrule   := next node in rule list (if not NIL)
     ruletail   := pointer to last node in rule list

     speciallist-> (nodes are added to head of this list)
     hp, vp      := reference point on page
     special     := \special bytes
     nextspecial := next node in list (if not NIL)

     fontlist->
     (the following fontinfo is relevant only if fontused is TRUE)
     totalchars := number of chars on page from this font
     charlist-> (nodes are added to tail of char list)
     charcount  := number of chars in this chartable
     chartable[0..charcount-1].
     hp, vp  := reference point of a character
     code    := TeX character code (and index into pixel table)
     nextchar   := next node in char list (if not NIL)
     chartail   := pointer to last node in char list
     pixelptr^[0..maxTeXchar].
     (filled in by FontReader's PixelTableRoutine)
     wd, ht  := glyph width and height in pixels
     xo, yo  := offsets from the character's reference point
     dwidth  := advance width in DVI units
     pwidth  := advance width in pixels
     mapadr  := offset in fontspec of the glyph's bitmap info
     bitmap  := NIL
     nextfont   := next node in font list (if not NIL)

     pageempty  := TRUE iff the page has no rules and no characters
     minhp, minvp, maxhp, maxvp
     := the edges of the page (undefined if pageempty is TRUE)
     They define the smallest rectangle containing all black
     pixels on the page;(minhp,minvp) is the top left corner.

     Reference points for rules and characters are stored as a pair of
     horizontal and vertical pixel coordinates.  The point (0,0) is assumed
     to be the pixel 1 inch in from the top and left edges of an imaginary sheet
     of paper.  Horizontal coordinates increase to the right and vertical
     coordinates increase down the paper.
     The numbers of pixels per inch in the X and Y directions are defined by
     the xres and yres resolution parameters given to SetConversionFactors.
   */

  int param = 0, ht, wd;

  InitStateValues ();
  InitPage ();
  do
    {
      DVIcommand = GetDVIByte ();
      /* For efficiency reasons the most frequent commands should be tested 1st.
         The following order is the result of frequency testing on typical
         DVI files.  Note that the most frequent commands in the DVI file
         generated by TeX 1.3 for the Dec. 1983 LaTeX manual were:
         <set1, w0, right3, push/pop, x0, w3, y0, fntnum25, right2, fntnum31,
         down3, x2, right4, w2, x3, down4, z0, fntnum8, setrule, y3, etc.
       */
      if (DVIcommand < set1)	/* 0..127 */
	DoSetPutChar (DVIcommand, true);
      else if (DVIcommand == w0)
	DoRight (w);
      else if (DVIcommand == right3)
	DoRight (SignedDVITrio ());
      else if (DVIcommand == push)
	DoPush ();
      else if (DVIcommand == pop)
	DoPop ();
      else if (DVIcommand == x0)
	DoRight (x);
      else if (DVIcommand == w3)
	{
	  w = SignedDVITrio ();
	  DoRight (w);
	}
      else if (DVIcommand == y0_)
	DoDown (y);
      else if (DVIcommand > z4 && DVIcommand < fnt1)
	{
	  /* catch all the remaining movement commands */
	  DoFont (DVIcommand - fntnum0);
	}
      else if (DVIcommand > pop && DVIcommand < fntnum0)
	{
	  if (DVIcommand == right2)
	    DoRight (SignedDVIPair ());
	  else if (DVIcommand == right4)
	    DoRight (SignedDVIQuad ());
	  else if (DVIcommand == x2)
	    {
	      x = SignedDVIPair ();
	      DoRight (x);
	    }
	  else if (DVIcommand == x3)
	    {
	      x = SignedDVITrio ();
	      DoRight (x);
	    }
	  else if (DVIcommand == down3)
	    DoDown (SignedDVITrio ());
	  else if (DVIcommand == down4)
	    DoDown (SignedDVIQuad ());
	  else if (DVIcommand == w2)
	    {
	      w = SignedDVIPair ();
	      DoRight (w);
	    }
	  else if (DVIcommand == z0)
	    DoDown (z);
	  else if (DVIcommand == y3)
	    {
	      y = SignedDVITrio ();
	      DoDown (y);
	    }
	  else if (DVIcommand == z3)
	    {
	      z = SignedDVITrio ();
	      DoDown (z);
	    }
	  else if (DVIcommand == down2)
	    {
	      /* the next DVI commands are used very rarely (by TeX 1.3 at least) */
	      DoDown (SignedDVIPair ());
	    }
	  else if (DVIcommand == w1)
	    {
	      w = SignedDVIByte ();
	      DoRight (w);
	    }
	  else if (DVIcommand == w4)
	    {
	      w = SignedDVIQuad ();
	      DoRight (w);
	    }
	  else if (DVIcommand == x1)
	    {
	      x = SignedDVIByte ();
	      DoRight (x);
	    }
	  else if (DVIcommand == x4)
	    {
	      x = SignedDVIQuad ();
	      DoRight (x);
	    }
	  else if (DVIcommand == y1_)
	    {
	      y = SignedDVIByte ();
	      DoDown (y);
	    }
	  else if (DVIcommand == y2)
	    {
	      y = SignedDVIPair ();
	      DoDown (y);
	    }
	  else if (DVIcommand == y4)
	    {
	      y = SignedDVIQuad ();
	      DoDown (y);
	    }
	  else if (DVIcommand == z1)
	    {
	      z = SignedDVIByte ();
	      DoDown (z);
	    }
	  else if (DVIcommand == z2)
	    {
	      z = SignedDVIPair ();
	      DoDown (z);
	    }
	  else if (DVIcommand == z4)
	    {
	      z = SignedDVIQuad ();
	      DoDown (z);
	    }
	  else if (DVIcommand == right1)
	    DoRight (SignedDVIByte ());
	  else if (DVIcommand == down1)
	    DoDown (SignedDVIByte ());
	  else
	    FATAL ("Bug in InterpretPage!");
	}
      else if (DVIcommand == setrule || DVIcommand == putrule)
	{
	  ht = SignedDVIQuad ();
	  wd = SignedDVIQuad ();
	  DoPutSetRule (ht, wd, DVIcommand);
	}
      else if (DVIcommand >= put1 && DVIcommand <= put1 + 3)
	{
	  switch (DVIcommand - put1)
	    {
	    case 0:
	      DoSetPutChar (GetDVIByte (), false);
	      break;
	    case 1:
	      DoSetPutChar (GetTwoDVIBytes (), false);
	      break;
	    case 2:
	      DoSetPutChar (GetThreeDVIBytes (), false);
	      break;
	    case 3:
	      DoSetPutChar (SignedDVIQuad (), false);
	      break;
	    }

	}
      else if (DVIcommand >= set1 && DVIcommand <= set1 + 3)
	{
	  switch (DVIcommand - set1)
	    {
	    case 0:
	      DoSetPutChar (GetDVIByte (), true);
	      break;
	    case 1:
	      DoSetPutChar (GetTwoDVIBytes (), true);
	      break;
	    case 2:
	      DoSetPutChar (GetThreeDVIBytes (), true);
	      break;
	    case 3:
	      DoSetPutChar (SignedDVIQuad (), true);
	      break;
	    }

	}
      else if (DVIcommand >= fnt1 && DVIcommand <= fnt1 + 3)
	{
	  switch (DVIcommand - fnt1)
	    {
	    case 0:
	      DoFont (GetDVIByte ());
	      break;
	    case 1:
	      DoFont (GetTwoDVIBytes ());
	      break;
	    case 2:
	      DoFont (GetThreeDVIBytes ());
	      break;
	    case 3:
	      DoFont (SignedDVIQuad ());
	      break;
	    }

	}
      else if (DVIcommand >= xxx1 && DVIcommand <= xxx1 + 3)
	{
	  switch (DVIcommand - xxx1)
	    {
	    case 0:
	      param = GetDVIByte ();
	      break;
	    case 1:
	      param = GetTwoDVIBytes ();
	      break;
	    case 2:
	      param = GetThreeDVIBytes ();
	      break;
	    case 3:
	      param = SignedDVIQuad ();
	      break;
	    }
	  /* pass current pixel position and number of bytes */
	  DoSpecial (hh, vv, param);
	}
      else if (DVIcommand >= fntdef1 && DVIcommand <= fntdef1 + 3)
	SkipFntdef (DVIcommand - fntdef1);
      else if (DVIcommand != nop)
	{
	  if (DVIcommand != eop)	/* do nothing */
	    FATAL1 ("Unexpected DVI command while interpreting page = %d.",
		    DVIcommand);
	}
    }
  while (DVIcommand != eop);

  /* save position of eop byte for use in MoveToTeXPage */
  curreop = DVIoffset - 1;

  if (stackpos != 0)
    FATAL ("Stack not empty at eop!");
  /* InitPage values */


  /* fntnum0..fntnum63 */


  /* skip fntdef command since we've got this info from postamble */

  /* do nothing */
}
/* InterpretPage */

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

void 
SortFonts (fontinfo ** unusedlist)
{
  /* out */

  /* Sort fontlist in order of ascending totalchars.
     Fonts with least characters can then be accessed first.
     Since the number of fonts used on a typical page is quite small, a simple
     sorting algorithm should be good enough.  Note that unused fonts are moved
     to the end of the list and unusedlist points to the first such node.
     DVItoVDU need only process fonts up to (but excluding) unusedlist
     and does not have to worry about checking the fontused flag.
     If unusedlist is NIL then either 1) all fonts are used on the current page
     or 2) fontlist is also NIL (totalfonts = 0).
   */
  fontinfo *newfontlist, *prevfont, *largest, *prevlargest;
  int mostchars;

  newfontlist = (fontinfo *) NULL;
  /* go thru fontlist once and move all unused fonts to head of newfontlist */
  prevfont = (fontinfo *) NULL;
  currfont = fontlist;
  while (currfont != (fontinfo *) NULL)
    {
      if (currfont->fontused)
	{
	  prevfont = currfont;	/* remember previous node */
	  currfont = currfont->nextfont;
	  continue;
	}
      /* move node from fontlist to head of newfontlist
         and don't change prevfont
       */
      if (prevfont == (fontinfo *) NULL)
	{
	  fontlist = currfont->nextfont;	/* remove first node in fontlist */
	  currfont->nextfont = newfontlist;
	  newfontlist = currfont;
	  currfont = fontlist;
	}
      else
	{
	  prevfont->nextfont = currfont->nextfont;
	  currfont->nextfont = newfontlist;
	  newfontlist = currfont;
	  currfont = prevfont->nextfont;
	}
    }

  /* unusedlist will be last unused font moved to newfontlist.  It will be NIL
     if either fontlist is NIL or all fonts are used.
   */

  *unusedlist = newfontlist;

  /* Now go thru fontlist repeatedly moving node with max totalchars to
     head of newfontlist until fontlist is exhausted.
   */

  while (fontlist != (fontinfo *) NULL)
    {
      prevfont = (fontinfo *) NULL;
      currfont = fontlist;
      prevlargest = (fontinfo *) NULL;
      largest = fontlist;
      mostchars = 0;

      /* search for largest totalchars */
      while (currfont != (fontinfo *) NULL)
	{
	  if (currfont->totalchars > mostchars)
	    {
	      prevlargest = prevfont;
	      largest = currfont;
	      mostchars = currfont->totalchars;
	    }
	  prevfont = currfont;
	  currfont = currfont->nextfont;
	}
      /* move largest node from fontlist to head of newfontlist */
      if (prevlargest == (fontinfo *) NULL)
	fontlist = largest->nextfont;	/* remove first node in fontlist */
      else
	prevlargest->nextfont = largest->nextfont;
      largest->nextfont = newfontlist;
      newfontlist = largest;
    }
  fontlist = newfontlist;
  /* used fonts now sorted and unused fonts at end */
}
/* SortFonts */

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

void 
CloseDVIFile ()
{
  /* Close the currently open DVI file,
     and deallocate dynamic data structures. */

  int result;

  result = close (DVIfile);
  while (fontlist != (fontinfo *) NULL)
    {
      currfont = fontlist;
      while (currfont->charlist != (charinfo *) NULL)
	{
	  charinfo *thischar;	/* temporary pointer to node in charlist */
	  thischar = currfont->charlist;
	  currfont->charlist = thischar->nextchar;
	  Free (thischar, charinfo);	/* deallocate char list */
	}
      if (currfont->pixelptr != (_REC_pixeltable *) NULL)
	Free (currfont->pixelptr, _REC_pixeltable);	/* deallocate pixel table */
      fontlist = currfont->nextfont;
      Free (currfont, fontinfo);	/* deallocate font information */
    }
  /* Deallocate rule information except for one node
     (in case DVItoVDU ever opens another DVI file).
   */
  while (rulelist != ruletail)
    {
      ruleinfo *thisrule;	/* temporary pointer to node in rulelist */
      thisrule = rulelist;
      rulelist = thisrule->nextrule;
      Free (thisrule, ruleinfo);
    }
  while (speciallist != (specialinfo *) NULL)
    {
      specialinfo *thisspecial;	/* temporary ptr to node in speciallist */
      thisspecial = speciallist;
      speciallist = speciallist->nextspecial;
      Free (thisspecial, specialinfo);
    }
}
/* CloseDVIFile */

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

void 
InitDVIReader ()
{
  totalrules = 0;
  rulelist = (ruleinfo *) Malloc (sizeof (ruleinfo));	/* for first InitPage */
  ruletail = rulelist;		/* ditto */
  speciallist = (specialinfo *) NULL;	/* ditto */
  fontlist = (fontinfo *) NULL;	/* safer for CloseDVIFile */
}
/* InitDVIReader */

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

/* end dvireader.c */