/* dt2dv - convert human-readable "DTL" file to DVI format
         - this is intended to invert dv2dt version 0.6.0
   - version 0.6.1 - 14:38 GMT +11  Thu 9 March 1995
   - Geoffrey Tobin    G.Tobin@ee.latrobe.edu.au
   - fixes:  Michal Tomczak-Jaegermann    ntomczak@vm.ucs.ualberta.ca
             Nelson H. F. Beebe    beebe@math.utah.edu
   - Reference:  "The DVI Driver Standard, Level 0",
                 by  The TUG DVI Driver Standards Committee.
                 Appendix A, "Device-Independent File Format".
*/

/* unix version; read from stdin, write to stdout, by default. */

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dtl.h"

/* by default, read and write regular files */
int rd_stdin = 0;
int wr_stdout = 0;

/* maximum number of characters in a DTL input line */
#define MAXLINE  1024

/* input line */
typedef struct
{
  COUNT num;    /* current line number */
  size_t max;   /* capacity of buf */
  S4 wrote;     /* number of characters written into buf */
  size_t read;  /* position in buf of next character to read from buf */
  char * buf;   /* line buffer */
} Line;

char linebuf[MAXLINE+1];

Line dtl_line = {0, 0, 0, MAXLINE, linebuf};

/* a DTL token either is:
     a quoted string (admitting an escape character),
     or BCOM (if that is a nonempty string),
     or ECOM (if that is a nonempty string),
     or a string _not_ including ECOM_CHAR or space.
*/

/* maximum expected length of a DTL token */
#define MAXTOKLEN 255
typedef char Token[MAXTOKLEN+1];

typedef unsigned char Byte;
typedef char Boolean;

#define true  1
#define false 0

/* command prefixes */
typedef struct
{
    Byte first_code;
    char * name;
    Boolean has_suffix;
    Byte first_suffix, last_suffix;
} CmdPrefix;

CmdPrefix  cmd_prefixes [] =
{
  {0,   SETCHAR, true, 0, 127},
  {128, SET, true, 1, 4},
  {132, SETRULE, false, 0, 0},
  {133, PUT, true, 1, 4},
  {137, PUTRULE, false, 0, 0},
  {138, NOP, false, 0, 0},
  {139, BOP, false, 0, 0},
  {140, EOP, false, 0, 0},
  {141, PUSH, false, 0, 0},
  {142, POP, false, 0, 0},
  {143, RIGHT, true, 1, 4},
  {147, W, true, 0, 4},
  {152, X, true, 0, 4},
  {157, DOWN, true, 1, 4},
  {161, Y, true, 0, 4},
  {166, Z, true, 0, 4},
  {171, FONTNUM, true, 0, 63},
  {235, FONT, true, 1, 4},
  {239, SPECIAL, true, 1, 4},
  {243, FONTDEF, true, 1, 4},
  {247, PRE, false, 0, 0},
  {248, POST, false, 0, 0},
  {249, POSTPOST, false, 0, 0},
  {250, OPCODE, true, 250, 255}
};
/* cmd_prefixes[] */

/* Number of DVI commands, including those officially undefined */
#define NCMDS 256

/* table of command name (string) pointers */
typedef char * CmdTable [NCMDS];

/* initially all command name pointers are NULL */
CmdTable cmd_table;

/* operation's opcode, name, number of args, string of arguments. */
typedef struct
{
  int code;
  char * name;
  int nargs;
  char * args;
} op_info;

/* name of table, first opcode, last opcode, pointer to opcode info. */
typedef struct
{
  char * name;
  int first;
  int last;
  op_info * list;
} op_table;

/* Table for opcodes 128 to 170 inclusive. */

op_info  op_info_128_170 [] =
{
  {128, SET1, 1, "1"},
  {129, SET2, 1, "2"},
  {130, SET3, 1, "3"},
  {131, SET4, 1, "-4"},
  {132, SETRULE, 2, "-4 -4"},
  {133, PUT1, 1, "1"},
  {134, PUT2, 1, "2"},
  {135, PUT3, 1, "3"},
  {136, PUT4, 1, "-4"},
  {137, PUTRULE, 2, "-4 -4"},
  {138, NOP, 0, ""},
  /* bop:  not counting last argument, a signed address: */
  {139, BOP, 10, "-4 -4 -4 -4 -4 -4 -4 -4 -4 -4"},
  {140, EOP, 0, ""},
  {141, PUSH, 0, ""},
  {142, POP, 0, ""},
  {143, RIGHT1, 1, "-1"},
  {144, RIGHT2, 1, "-2"},
  {145, RIGHT3, 1, "-3"},
  {146, RIGHT4, 1, "-4"},
  {147, W0, 0, ""},
  {148, W1, 1, "-1"},
  {149, W2, 1, "-2"},
  {150, W3, 1, "-3"},
  {151, W4, 1, "-4"},
  {152, X0, 0, ""},
  {153, X1, 1, "-1"},
  {154, X2, 1, "-2"},
  {155, X3, 1, "-3"},
  {156, X4, 1, "-4"},
  {157, DOWN1, 1, "-1"},
  {158, DOWN2, 1, "-2"},
  {159, DOWN3, 1, "-3"},
  {160, DOWN4, 1, "-4"},
  {161, Y0, 0, ""},
  {162, Y1, 1, "-1"},
  {163, Y2, 1, "-2"},
  {164, Y3, 1, "-3"},
  {165, Y4, 1, "-4"},
  {166, Z0, 0, ""},
  {167, Z1, 1, "-1"},
  {168, Z2, 1, "-2"},
  {169, Z3, 1, "-3"},
  {170, Z4, 1, "-4"}
};
/* op_info  op_info_128_170 [] */

op_table  op_128_170  =  {"op_128_170", 128, 170, op_info_128_170};

/* Table for fnt1 to fnt4 (opcodes 235 to 238) inclusive. */

op_info  fnt_n [] =
{
  {235, FONT1, 1, "1"},
  {236, FONT2, 1, "2"},
  {237, FONT3, 1, "3"},
  {238, FONT4, 1, "-4"}
};
/* op_info  fnt_n [] */

op_table  fnt  =  {FONT, 235, 238, fnt_n};


/* Function prototypes */

Void mem_viol ARGS((int sig));
Void give_help (VOID);
int parse ARGS((char * s));
Void process ARGS((char * s));

Void no_op (VOID);
Void dtl_stdin (VOID);
Void dvi_stdout (VOID);

int open_dtl ARGS((char * dtl_file, FILE ** pdtl));
int open_dvi ARGS((char * dvi_file, FILE ** pdvi));

int dt2dv ARGS((FILE * dtl, FILE * dvi));

Void * gmalloc ARGS((long int size));

Void dinfo (VOID);
Void dexit ARGS((int n));

int cons_cmds ARGS((int nprefixes, CmdPrefix * prefix, CmdTable cmds));
Void free_cmds ARGS((CmdTable cmd_table));

int get_line ARGS((FILE * fp, Line * line, int max));
int read_line_char ARGS((FILE * fp, int * ch));
int read_char ARGS((FILE * fp, int * ch));
int unread_char (VOID);
int read_string_char ARGS((FILE * fp, int * ch));

COUNT read_variety ARGS((FILE * dtl));
COUNT read_token ARGS((FILE * dtl, char * token));
COUNT skip_space ARGS((FILE * fp, int * ch));
COUNT read_misc ARGS((FILE * fp, Token token));
COUNT read_mes ARGS((FILE * fp, char * token));

int find_command ARGS((char * command, int * opcode));
int xfer_args ARGS((FILE * dtl, FILE * dvi, int opcode));

int set_seq ARGS((FILE * dtl, FILE * dvi));

int check_byte ARGS((int byte));
int put_byte ARGS((int onebyte, FILE * dvi));

U4 xfer_hex ARGS((int n,  FILE * dtl,  FILE * dvi));
U4 xfer_oct ARGS((int n,  FILE * dtl,  FILE * dvi));
U4 xfer_unsigned ARGS((int n,  FILE * dtl,  FILE * dvi));
S4 xfer_signed   ARGS((int n,  FILE * dtl,  FILE * dvi));

int check_bmes ARGS((FILE * dtl));
int check_emes ARGS((FILE * dtl));

Void init_Lstring ARGS((Lstring * lsp, long int n));
Void de_init_Lstring ARGS((Lstring * lsp));
Lstring * alloc_Lstring ARGS((long int n));
Void free_Lstring ARGS((Lstring * lstr));
Void ls_putb ARGS((int ch, Lstring * lstr));

S4 get_Lstring ARGS((FILE * dtl, Lstring * lstr));
Void put_Lstring ARGS((const Lstring * lstr, FILE * dvi));
U4 xfer_len_string ARGS((int n, FILE * dtl, FILE * dvi));

U4 get_unsigned ARGS((FILE * dtl));
S4 get_signed   ARGS((FILE * dtl));

int put_unsigned ARGS((int n, U4 unum, FILE * dvi));
int put_signed   ARGS((int n, S4 snum, FILE * dvi));

S4 xfer_bop_address ARGS((FILE * dtl,  FILE * dvi));
S4 xfer_postamble_address ARGS((FILE * dtl,  FILE * dvi));

int put_table ARGS((op_table table, int opcode, FILE * dtl, FILE * dvi));

U4 special ARGS((FILE * dtl,  FILE * dvi,  int n));
int fontdef ARGS((FILE * dtl,  FILE * dvi,  int n));

U4 preamble ARGS((FILE * dtl,  FILE * dvi));
int postamble ARGS((FILE * dtl,  FILE * dvi));
int post_post ARGS((FILE * dtl,  FILE * dvi));


typedef struct
{
  char * keyword;  /* command line option keyword */
  int * p_var;     /* pointer to option variable */
  char * desc;     /* description of keyword and value */
  Void (* p_fn) (VOID);  /* pointer to function called when option is set */
} Options;

Options opts[] =
{
  {"-debug", &debug, "detailed debugging", no_op},
  {"-group", &group, "each DTL command is in parentheses", no_op},
  {"-si", &rd_stdin, "read all DTL commands from standard input", dtl_stdin},
  {"-so", &wr_stdout, "write all DVI commands to standard output", dvi_stdout},
  {NULL, NULL, NULL, NULL}
};
/* opts[] */

char * progname = "";  /* intended for name of this program */
int nfile = 0;  /* number of filename arguments on the command line */

#define PRINT_PROGNAME  fprintf (stderr, "%s ", progname)

/* Harbison & Steele (1991) warn that some C implementations */
/* of free() do not treat NULL pointers correctly. */
#define gfree(p) {if (p) free (p);}


FILE * dtl_fp = NULL;
FILE * dvi_fp = NULL;

char * dtl_filename = "";
char * dvi_filename = "";


int
main
#ifdef STDC
  (int argc,  char * argv[])
#else
  (argc,  argv)
  int argc;
  char * argv[];
#endif
{
  Void (*handler) ARGS((int));  /* Previous signal handler */
  int i;

  progname = argv[0];  /* name of this program */

  /* memory violation signal handler */

  handler = (Void (*) ARGS((int))) signal (SIGSEGV, mem_viol);

#ifndef __DATE__
#define __DATE__ ""
#endif

#ifndef __TIME__
#define __TIME__ ""
#endif

#if STDC
#define C_LEVEL ""
#else /* NOT STDC */
#define C_LEVEL "non-"
#endif /* NOT STDC */

  /* message about program and compiler */
  /* NB:  LTU EE's Sun/OS library is BSD, even though gcc 2.2.2 is ANSI */

  fprintf (stderr, "\n");
  fprintf (stderr,
    "Program \"%s\" version %s compiled %s %s in %sstandard C.\n",
    progname, VERSION, __DATE__, __TIME__, C_LEVEL);

  /* interpret command line arguments */

  nfile = 0;
  dtl_fp = dvi_fp = NULL;
  dtl_filename = dvi_filename = "";

  for (i=1; i < argc; i++)
  {
    /* parse options, followed by any explicit filenames */
    parse (argv[i]);
  }

  if (nfile != 2)  /* not exactly two files specified, so give help */
    give_help();
  else
    /* the real works */
    dt2dv (dtl_fp, dvi_fp);

  return 0;  /* OK */
}
/* end main */


Void
mem_viol
#ifdef STDC
  (int sig)
#else
  (sig)
  int sig;
#endif
{
  Void (* handler) ARGS((int));
  handler = (Void (*) ARGS((int))) signal (SIGSEGV, mem_viol);
  if (sig != SIGSEGV)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(mem_viol) : called with wrong signal!\n");
  }
  PRINT_PROGNAME;
  fprintf (stderr, "(mem_viol) : RUNTIME MEMORY ERROR : memory violation, ");
  fprintf (stderr, "dtl line >= ");
  fprintf (stderr, WF, dtl_line.num);
  fprintf (stderr, "\n");
  dexit (1);
}
/* mem_viol */


Void
give_help (VOID)
{
  int i;
  char * keyword;
  fprintf (stderr, "usage:   ");
  PRINT_PROGNAME;
  fprintf (stderr, "[options]  dtl_file  dvi_file");
  fprintf (stderr, "\n");
  for (i=0; (keyword = opts[i].keyword) != NULL; i++)
  {
    fprintf (stderr, "    ");
    fprintf (stderr, "[%s]", keyword);
    fprintf (stderr, "    ");
    fprintf (stderr, "%s", opts[i].desc);
    fprintf (stderr, "\n");
  }
  fprintf (stderr, "Messages, like this one, go to stderr.\n");
}
/* give_help */


Void no_op (VOID)
/* do nothing */
{
}

Void dtl_stdin (VOID)
{
  extern FILE * dtl_fp;
  extern int nfile;

  dtl_fp = stdin;
  dtl_filename = "Standard Input";
  ++ nfile;
}

Void dvi_stdout (VOID)
{
  extern FILE * dvi_fp;
  extern int nfile;

  /* ! Perilous to monitors! */
  dvi_fp = stdout;
  dvi_filename = "Standard Output";
  ++ nfile;
}


int
parse
#ifdef STDC
  (char * s)
#else
  (s)
  char * s;
#endif
/* parse one command-line argument, `s' */
{
  int i;
  char * keyword;
  for (i=0; (keyword = opts[i].keyword) != NULL; i++)
  {
    if (strncmp (s, keyword, strlen (keyword)) == 0)
    {
      Void (*pfn) (VOID);
      (*(opts[i].p_var)) = 1;  /* turn option on */
      if ((pfn = opts[i].p_fn) != NULL)
        (*pfn) ();    /* call option function */
      return i;
    }
  }
  /* reached here, so not an option: assume it's a filename */
  process (s);
  return i;
}
/* parse */


int
open_dtl
#ifdef STDC
  (char * dtl_file, FILE ** pdtl)
#else
  (dtl_file, pdtl)
  char * dtl_file;
  FILE ** pdtl;
#endif
/* I:  dtl_file;  I:  pdtl;  O:  *pdtl. */
{
  extern char * dtl_filename;

  dtl_filename = dtl_file;

  if (dtl_filename == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(open_dtl) : INTERNAL ERROR : dtl file's name is NULL.\n");
    dexit (1);
  }

  if (pdtl == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(open_dtl) : INTERNAL ERROR : address of dtl variable is NULL.\n");
    dexit (1);
  }

  *pdtl = fopen (dtl_file, "r");

  if (*pdtl == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(open_dtl) : DTL FILE ERROR : Cannot open \"%s\" for text reading.\n",
      dtl_file);
    dexit (1);
  }

  return 1;  /* OK */
}
/* open_dtl */


int
open_dvi
#ifdef STDC
  (char * dvi_file, FILE ** pdvi)
#else
  (dvi_file, pdvi)
  char * dvi_file;
  FILE ** pdvi;
#endif
/* I:  dvi_file;  I:  pdvi;  O:  *pdvi. */
{
  extern char * dvi_filename;

  dvi_filename = dvi_file;

  if (dvi_filename == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
    "(open_dvi) : INTERNAL ERROR : dvi file's name is NULL.\n");
    dexit (1);
  }

  if (pdvi == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
    "(open_dvi) : INTERNAL ERROR : address of dvi variable is NULL.\n");
    dexit (1);
  }

  *pdvi = fopen (dvi_file, "wb");

  if (*pdvi == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(open_dvi) : DVI FILE ERROR : Cannot open \"%s\" for binary writing.\n",
      dvi_file);
    dexit (1);
  }

  return 1;  /* OK */
}
/* open_dvi */


Void
process
#ifdef STDC
  (char * s)
#else
  (s)
  char * s;
#endif
{
  extern FILE * dtl_fp, * dvi_fp;
  extern int nfile;
  if (dtl_fp == NULL)  /* first filename assumed to be DTL input */
  {
    open_dtl (s, &dtl_fp);
  }
  else if (dvi_fp == NULL)  /* second filename assumed to be DVI output */
  {
    open_dvi (s, &dvi_fp);
  }
  else
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(process) : at most two filenames allowed.\n");
    exit (1);
  }
  ++ nfile;
}
/* process */


COUNT dtl_read = 0;  /* bytes read from dtl file */
COUNT dvi_written = 0;  /* bytes written to dvi file */
word_t last_bop_address = -1;  /* byte address of last bop; first bop uses -1 */
word_t postamble_address = -1;  /* byte address of postamble */
COUNT ncom = 0;  /* commands successfully read and interpreted from dtl file */
COUNT com_read = 0;  /* bytes read in current (command and arguments), */
                      /* since and including the opening BCOM_CHAR, if any */


int
put_byte
#ifdef STDC
  (int byte, FILE * dvi)
#else
  (byte, dvi)
  int byte;
  FILE * dvi;
#endif
/* write byte into dvi file */
{
  check_byte (byte);
/*  if (fprintf (dvi, "%c", byte) != 1) */
  if (fprintf (dvi, "%c", byte) < 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_byte) : DVI FILE ERROR (%s) : cannot write to dvi file.\n",
      dtl_filename);
    dexit (1);
  }
  ++ dvi_written;
  return 1;  /* OK */
}
/* put_byte */


int
dt2dv
#ifdef STDC
  (FILE * dtl, FILE * dvi)
#else
  (dtl, dvi)
  FILE * dtl;
  FILE * dvi;
#endif
{
  int nprefixes = 0;  /* number of prefixes in cmd_prefixes[] list. */
  static Token dtl_cmd = "";  /* DTL command name */
  COUNT nread = 0;  /* number of bytes read by a function from dtl file. */

  nprefixes = sizeof (cmd_prefixes) / sizeof (CmdPrefix);

  /* Construct array of all NCMDS == 256 DTL commands */

  (Void) cons_cmds (nprefixes, cmd_prefixes, cmd_table);

  /* DTL commands have the form "[ ]*command arg ... arg[ ]*", */
  /* possibly enclosed in a BCOM, ECOM pair, */
  /* and are separated by optional whitespace, typically newlines. */
  /* That is, each command and its arguments are parenthesised, */
  /* with optional spaces after the BCOM and before the ECOM, if any. */

  /* dt2dv is now at the very start of the DTL file */

  dtl_line.num = 0;
  dtl_read = 0;

  /* The very first thing should be the "variety" signature */

  nread = read_variety (dtl);

  /* while not end of dtl file or reading error, */
  /*   read, interpret, and write commands */

  while (!feof (dtl))
  {
    int opcode;

    com_read = 0;

    if (group)
    {
      /* BCOM check */
      static Token token = "";  /* DTL token */
      nread = read_token (dtl, token);
      /* test for end of input, or reading error */
      if (strlen (token) == 0)
      {
	if (debug)
	{
          PRINT_PROGNAME;
	  fprintf (stderr, "(dt2dv) : end of input, or reading error.\n");
	}
	break;
      }
      /* test whether this command begins correctly */
      else if (strcmp (token, BCOM) != 0)
      {
        PRINT_PROGNAME;
	fprintf (stderr, "(dt2dv) : DTL FILE ERROR (%s) : ", dtl_filename);
        fprintf (stderr, "command must begin with \"%s\", ", BCOM);
        fprintf (stderr, "not `%c' (char %d).\n", token[0], token[0]);
	dexit (1);
      }
      /* end BCOM check */
    }

    /* read the command name */
    nread = read_token (dtl, dtl_cmd);
    /* test for end of input, or reading error */
    if (strlen (dtl_cmd) == 0)
    {
      if (debug)
      {
        PRINT_PROGNAME;
	fprintf (stderr,
	  "(dt2dv) : end of input, or reading error.\n");
      }
      break;
    }
    else
    {
      if (debug)
      {
        PRINT_PROGNAME;
	fprintf (stderr, "(dt2dv) : command ");
        fprintf (stderr, WF, ncom);
        fprintf (stderr, " = \"%s\".\n", dtl_cmd);
      }

      /* find opcode for this command */
      if (find_command (dtl_cmd, &opcode) == 1)
      {
	/* write the opcode, if we can */
	put_byte (opcode, dvi);

	/* treat the arguments, if any */
	xfer_args (dtl, dvi, opcode);
      }
      else if (dtl_cmd[0] == BSEQ_CHAR)
      {
	/* sequence of font characters for SETCHAR */
	set_seq (dtl, dvi);
      }
      else
      {
        PRINT_PROGNAME;
	fprintf (stderr,
	  "(dt2dv) : DTL FILE ERROR (%s) : unknown command \"%s\".\n",
	  dtl_filename, dtl_cmd);
	dexit (1);
      }
    }

    if (group)
    {
      /* seek ECOM after command's last argument and optional whitespace */
      static Token token = "";  /* DTL token */
      nread = read_token (dtl, token);
      /* test for end of input, or reading error */
      if (strlen (token) == 0)
      {
	if (debug)
	{
          PRINT_PROGNAME;
	  fprintf (stderr,
	    "(dt2dv) : end of input, or reading error.\n");
	}
	break;
      }
      if (strcmp (token, ECOM) != 0)
      {
        PRINT_PROGNAME;
	fprintf (stderr, "(dt2dv) : DTL FILE ERROR (%s) : ", dtl_filename);
        fprintf (stderr, "ECOM (\"%s\") expected, not `%c' (char %d).\n",
	  ECOM, token[0], token[0]);
	dexit (1);
      }
      /* end ECOM check */
    }

    ++ ncom;  /* one more command successfully read and interpreted */
  }
  /* end while */

  PRINT_PROGNAME;
  fprintf (stderr, "(dt2dv) :\n");
  fprintf (stderr, "Read (from file \"%s\") ", dtl_filename);
  fprintf (stderr, WF, dtl_read);
  fprintf (stderr, " DTL bytes (");
  fprintf (stderr, UF4, dtl_line.num);
  fprintf (stderr, " lines);\n");
  fprintf (stderr, "wrote (to file \"%s\") ", dvi_filename);
  fprintf (stderr, WF, dvi_written);
  fprintf (stderr, " DVI bytes;\n");
  fprintf (stderr, "completely interpreted ");
  fprintf (stderr, WF, ncom);
  fprintf (stderr, " DVI command%s.\n", (ncom == 1 ? "" : "s"));
  fprintf (stderr, "\n");

  (Void) free_cmds (cmd_table);

  return 1;  /* OK */
}
/* dt2dv */


Void *
gmalloc
#ifdef STDC
  (long int size)
#else
  (size)
  long int size;
#endif
{
  Void * p = NULL;
  if (size < 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(gmalloc) : INTERNAL ERROR : ");
    fprintf (stderr,
      "unreasonable request to malloc %ld bytes\n",
      size);
    dexit (1);
  }
  p = (Void *) malloc ((size_t) size);
  if (p == NULL)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(gmalloc) : MEMORY ALLOCATION ERROR : ");
    fprintf (stderr,
      "operating system failed to malloc %ld bytes\n",
      size);
    dexit (1);
  }
  return (p);
}
/* gmalloc */


Void
dinfo (VOID)
{
  PRINT_PROGNAME;
  fprintf (stderr, "(dinfo) : ");
  fprintf (stderr, "Current DTL input line ");
  fprintf (stderr, UF4, dtl_line.num);
  fprintf (stderr, " :\n");
  fprintf (stderr, "\"%s\"\n", dtl_line.buf);
  fprintf (stderr, "Read ");
  fprintf (stderr, WF, dtl_read);
  fprintf (stderr, " DTL bytes (");
  fprintf (stderr, WF, com_read);
  fprintf (stderr, " in current command), wrote ");
  fprintf (stderr, WF, dvi_written);
  fprintf (stderr, " DVI bytes.\n");
  fprintf (stderr, "Successfully interpreted ");
  fprintf (stderr, WF, ncom);
  fprintf (stderr, " DVI command%s.\n", (ncom == 1 ? "" : "s"));
}
/* dinfo */


Void
dexit
#ifdef STDC
  (int n)
#else
  (n)
  int n;
#endif
{
  dinfo();
  PRINT_PROGNAME;
  fprintf (stderr, "(dexit) : exiting with status %d.\n", n);
  exit (n);
}
/* dexit */


int
cons_cmds
#ifdef STDC
  (int nprefixes, CmdPrefix * prefix, CmdTable cmds)
#else
  (nprefixes, prefix, cmds)
  int nprefixes;
  CmdPrefix * prefix;
  CmdTable cmds;
#endif
{
  int code;  /* first opcode for a given command prefix */
  int opcode;  /* command's opcode */
  int nsuffixes;  /* number of commands with a given prefix */
  int isuffix;  /**** integer suffix, of at most three digits ****/
  String suffix;  /* suffix string generated from integer suffix */
  size_t plen = 0;  /* prefix length */
  size_t slen;  /* suffix length */
  size_t clen;  /* whole command name length */
  int i, j;  /* loop indices */

  for (i=0; i < nprefixes; prefix++, i++)
  {
    code = prefix->first_code;
    if (code < 0 || code > 255)
    {
      PRINT_PROGNAME;
      fprintf (stderr, "(cons_cmds) : INTERNAL ERROR : ");
      fprintf (stderr,
        "prefix listed internally with code = %d, must be 0 to 255\n",
        code);
      dexit (1);
    }
    if (prefix->has_suffix)
    {
      plen = strlen (prefix->name);
      /**** Suffixes in DTL are Integers, in Sequence */
      if (prefix->last_suffix < prefix->first_suffix)
      {
        PRINT_PROGNAME;
        fprintf (stderr, "(cons_cmds) : INTERNAL ERROR : ");
        fprintf (stderr,
          "prefix's last suffix %d < first suffix (%d)\n",
          prefix->last_suffix, prefix->first_suffix);
        dexit (1);
      }
      nsuffixes = prefix->last_suffix - prefix->first_suffix + 1;
      opcode = prefix->first_code;
      for (j=0; j < nsuffixes; j++, opcode++)
      {
        isuffix = prefix->first_suffix + j;
        if (0 <= code && code <= 127)  /* SETCHAR */
        {
          /* SETCHAR's suffix is written in uppercase hexadecimal */
          sprintf (suffix, "%02X", isuffix);
        }
        else  /* 128 <= code && code <= 255 */  /* other DTL commands */
        {
          /* other commands' suffices are written in decimal */
          sprintf (suffix, "%d", isuffix);
        }
        slen = strlen (suffix);
        clen = plen + slen;
        cmds[opcode] = (char *) gmalloc (clen+1);
        strcpy (cmds[opcode], prefix->name);
        strcat (cmds[opcode], suffix);
      }
    }
    else /* command name = prefix */
    {
      plen = strlen (prefix->name);
      clen = plen;
      opcode = prefix->first_code;
      cmds[opcode] = (char *) gmalloc (clen+1);
      strcpy (cmds[opcode], prefix->name);
    }
  }

  return 1;  /* OK */
}
/* cons_cmds */


Void
free_cmds
#ifdef STDC
  (CmdTable cmd_table)
#else
  (cmd_table)
  CmdTable cmd_table;
#endif
{
  int i;
  for (i=0; i < NCMDS; i++)
    gfree (cmd_table[i]);
}
/* free_cmds */


int
get_line
#ifdef STDC
  (FILE * fp, Line * line, int max)
#else
  (fp, line, max)
  FILE * fp;
  Line * line;
  int max;
#endif
/* read a (Line *) line from fp, return length */
/* adapted from K&R (second, alias ANSI C, edition, 1988), page 165 */
{
  if (fgets (line->buf, max, fp) == NULL)
    return 0;
  else
  {
    ++ line->num;
    line->wrote = strlen (line->buf);
    line->read = 0;
    return 1;
  }
}
/* get_line */


int
read_line_char
#ifdef STDC
  (FILE * fp, int * ch)
#else
  (fp, ch)
  FILE * fp;
  int * ch;
#endif
/* read one character from dtl_line if possible, */
/* otherwise read another dtl_line from fp */
/* return 1 if a character is read, 0 if at end of fp file */
{
  extern Line dtl_line;
  if (dtl_line.wrote == 0 || dtl_line.read >= dtl_line.wrote)
  {
    int line_status;
    /* refill line buffer */
    line_status = get_line (fp, &dtl_line, MAXLINE);
    if (line_status == 0)
    {
      /* at end of DTL file */
      if (debug)
      {
        PRINT_PROGNAME;
        fprintf (stderr, "(read_line_char) : end of DTL file\n");
        dinfo();
      }
      return 0;
    }
    else
    {
      /* new DTL line was read */
      if (debug)
      {
        PRINT_PROGNAME;
        fprintf (stderr, "(read_line_char) : new DTL input line:\n");
        fprintf (stderr, "\"%s\"\n", dtl_line.buf);
      }
    }
  }
  *ch = dtl_line.buf [dtl_line.read ++];
  ++ dtl_read;
  ++ com_read;  /* count another DTL command character */
  return 1;
}
/* read_line_char */


int
read_char
#ifdef STDC
  (FILE * fp, int * ch)
#else
  (fp, ch)
  FILE * fp;
  int * ch;
#endif
/* Read next character, if any, from file fp. */
/* Write it into *ch. */
/* If no character is read, then *ch value < 0. */
/* Return 1 if OK, 0 if EOF or error. */
{
  int status = 1;
  int c;  /* in case ch points awry, we still have something in c. */

  c = EOF;
  if (read_line_char (fp, &c) == 0)
  {
    /* end of fp file, or error reading it */
    status = 0;
  }
  else
  {
    if (c > 255)
    {
      PRINT_PROGNAME;
      fprintf (stderr,
        "(read_char) : character %d not in range 0 to 255\n",
        c);
      dinfo();
      status = 0;
    }
    else if ( ! isprint (c) && ! isspace (c))
    {
      PRINT_PROGNAME;
      fprintf (stderr,
        "(read_char) : character %d %s.\n",
        c,
        "not printable and not white space");
      dinfo();
      status = 0;
    }
  }
  *ch = c;

  return status;
}
/* read_char */


COUNT
read_variety
#ifdef STDC
  (FILE * dtl)
#else
  (dtl)
  FILE * dtl;
#endif
/* read and check DTL variety signature */
/* return number of DTL bytes written */
/* DTL variety is _NEVER_ grouped by BCOM and ECOM. */
/* Uniformity here enables the program easily to modify its behavior. */
{
  COUNT vread = 0;  /* number of DTL bytes read by read_variety */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  static Token token = "";

  /* read the DTL VARIETY keyword */
  nread = read_token (dtl, token);
  vread += nread;
  /* test whether signature begins correctly */
  if (strcmp (token, "variety") != 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(read_variety) : DTL FILE ERROR (%s) : ", dtl_filename);
    fprintf (stderr, "DTL signature must begin with \"%s\", not \"%s\".\n",
      "variety", token);
    dexit (1);
  }

  /* read the DTL variety */
  nread = read_token (dtl, token);
  vread += nread;
  /* test whether variety is correct */
  if (strcmp (token, VARIETY) != 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(read_variety) : DTL FILE ERROR (%s) : ", dtl_filename);
    fprintf (stderr, "DTL variety must be \"%s\", not \"%s\".\n",
      VARIETY, token);
    dexit (1);
  }

  PRINT_PROGNAME;
  fprintf (stderr, "(read_variety) : DTL variety %s is OK.\n", VARIETY);

  return vread;  /* OK */
}
/* read_variety */


COUNT
skip_space
#ifdef STDC
  (FILE * fp, int * ch)
#else
  (fp, ch)
  FILE * fp;
  int * ch;
#endif
/* Skip whitespace characters in file fp. */
/* Write in *ch the last character read from fp, */
/*   or < 0 if fp could not be read. */
/* Return number of characters read from fp. */
{
  int c;  /* character read (if any) */
  COUNT count;  /* number (0 or more) of whitespace characters read */
  int nchar;  /* number (0 or 1) of characters read by read_char */

  /* loop ends at:  end of fp file, or reading error, or not a white space */
  for (count=0;  ((nchar = read_char (fp, &c)) == 1 && isspace (c));  ++count)
  {
    /* otherwise, more white spaces to skip */
    if (debug)
    {
      /* report when each DTL end of line is reached */
      if (c == '\n')
      {
        PRINT_PROGNAME;
        fprintf (stderr, "(skip_space) : ");
        fprintf (stderr, "end of DTL line (at least) ");
        fprintf (stderr, WF, dtl_line.num);
        fprintf (stderr, "\n");
      }
    }
  }

  if (nchar == 0)
  {
    c = -1;
  }

  *ch = c;  /* c will be < 0 if read_char could not read fp */
  return (count + nchar);
}
/* skip_space */


COUNT
read_token
#ifdef STDC
  (FILE * dtl, char * token)
#else
  (dtl, token)
  FILE * dtl;
  char * token;
#endif
/* read next token from dtl file. */
/* return number of DTL bytes read. */
/* A token is one of:
     a string from BMES_CHAR to the next unescaped EMES_CHAR, inclusive;
     BCOM or ECOM, unless these are empty strings;
     BSEQ or ESEQ;
     any other sequence of non-whitespace characters.
*/
{
  COUNT nread;  /* number of DTL bytes read by read_token */
  int ch;  /* most recent character read */

  nread = 0;

  /* obtain first non-space character */
  /* add to nread the number of characters read from dtl by skip_space */
  nread += skip_space (dtl, &ch);

  if (ch < 0)
  {
    /* end of dtl file */
    /* write an empty token */
    strcpy (token, "");
    if (debug)
    {
      PRINT_PROGNAME;
      fprintf (stderr, "(read_token) : end of dtl file.\n");
    }
  }
  else if (group && ch == BCOM_CHAR)
  {
    strcpy (token, BCOM);
  }
  else if (group && ch == ECOM_CHAR)
  {
    strcpy (token, ECOM);
  }
  else
  {
    token[0] = ch;
    token[1] = '\0';
    if (ch == BMES_CHAR)  /* string token; read until unescaped EMES_CHAR */
    {
      nread += read_mes (dtl, token+1);
    }
    else if (ch == BSEQ_CHAR || ch == ESEQ_CHAR)
    {
      /* token is complete */
    }
    else  /* any other string not containing (ECOM_CHAR or) whitespace */
    {
      nread += read_misc (dtl, token+1);
    }
  }

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(read_token) : token = \"%s\"\n", token);
  }

  return (nread);  /* number of bytes read from dtl file */
}
/* read_token */


#define CHAR_OK  1
#define CHAR_FAIL  0
#define CHAR_EOS  (-1)

int
read_string_char
#ifdef STDC
  (FILE * fp, int * ch)
#else
  (fp, ch)
  FILE * fp;
  int * ch;
#endif
{
  int status = CHAR_OK;  /* OK so far */
  int c;

  if (read_char (fp, &c) == 0)
    status = CHAR_FAIL;  /* fail */

  if (c == EMES_CHAR)  /* end-of-string char */
  {
    status = CHAR_EOS;  /* end of string */
  }
  else if (c == ESC_CHAR)  /* escape character */
  {
    /* accept the next character literally, even ESC_CHAR and EMES_CHAR */
    if (read_char (fp, &c) == 0)
      status = CHAR_FAIL;  /* fail */
  }

  *ch = c;
  return status;
}
/* read_string_char */


COUNT
read_misc
#ifdef STDC
  (FILE * fp, Token token)
#else
  (fp, token)
  FILE * fp;
  Token token;
#endif
{
  int c;
  int count;
 /* loop ends at:  end of fp file, or reading error, or a space */
  for (count=0;  count <= MAXTOKLEN;  ++count)
  {
    if (read_char (fp, &c) == 0  ||  isspace (c))
      break;
    if (group && c == ECOM_CHAR)
    {
      (Void) unread_char ();
      break;
    }

    token[count] = c;
  }
  token[count] = '\0';
  return count;
}
/* read_misc */


COUNT
read_mes
#ifdef STDC
  (FILE * fp, char * token)
#else
  (fp, token)
  FILE * fp;
  char * token;
#endif
/* called **AFTER** a BMES_CHAR has been read */
/* read file fp for characters up to next unescaped EMES_CHAR */
/* this is called a "string token" */
/* write string, including EMES_CHAR, into token[] */
/* return number of characters read from fp */
{
  COUNT dtl_count;  /* number of DTL characters read by read_mes from fp */
  int more;  /* flag more == 0 to terminate loop */
  int escape;  /* flag escape == 1 if previous character was ESC_CHAR */
  int ch;  /* current DTL character */

  escape = 0;
  more = 1;
  dtl_count = 0;
  while (more)
  {
    if (read_char (fp, &ch) == 0)
    {
      /* end of fp file, or reading error */
      more = 0;
    }
    else  /* error checking passed */
    {
      ++ dtl_count;
      if (ch == EMES_CHAR && escape == 0)  /* end of string */
      {
        /* include final EMES_CHAR */
        * token ++ = ch;
        more = 0;
      }
      else if (ch == ESC_CHAR && escape == 0)
      {
        /* next character is not end of string */
        escape = 1;
      }
      else
      {
        /* store any other character, */
        /* including escaped EMES_CHAR and ESC_CHAR*/
        * token ++ = ch;
        escape = 0;
      }
    }
  }
  * token = '\0';
  return dtl_count;
}
/* read_mes */


int
unread_char (VOID)
/* wind input back, to allow rereading of one character */
/* return 1 if this works, 0 on error */
{
  extern Line dtl_line;
  int status;
  if (dtl_line.read > 0)
  {
    -- dtl_line.read;  /* back up one character in dtl_line */
    -- dtl_read;  /* correct the count of DTL characters */
    -- com_read;  /* count another DTL command character */
    status = 1;  /* OK */
  }
  else /* current DTL line is empty */
  {
    status = 0;  /* error */
  }
  return status;
}
/* unread_char */


int
find_command
#ifdef STDC
  (char * command, int * opcode)
#else
  (command, opcode)
  char * command;
  int * opcode;
#endif
{
  int found;
  int i;

  found = 0;
  for (i=0; i < NCMDS; i++)
  {
    if ((cmd_table[i] != 0) && (strcmp (command, cmd_table[i]) == 0))
    {
      found = 1;
      break;
    }
  }

  *opcode = i;

  return found;
}
/* find_command */


int
check_byte
#ifdef STDC
  (int byte)
#else
  (byte)
  int byte;
#endif
{
  if (byte < 0 || byte > 255)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(check_byte) : INTERNAL ERROR : ");
    fprintf (stderr, "byte %d not in the range of 0 to 255.\n", byte);
    dexit (1);
  }
  return 1;  /* OK */
}
/* check_byte */


int
xfer_args
#ifdef STDC
  (FILE * dtl, FILE * dvi, int opcode)
#else
  (dtl, dvi, opcode)
  FILE * dtl;
  FILE * dvi;
  int opcode;
#endif
{
  int n;

  if (opcode >= 0 && opcode <= 127)
    ;  /* SETCHAR uses no data */
  else if (opcode >= 128 && opcode <= 170)
  {
    word_t this_bop_address = last_bop_address;

    if (opcode == 139)  /* BOP */
    {
      this_bop_address = dvi_written - 1;
    }
    put_table (op_128_170, opcode, dtl, dvi);
    if (opcode == 139)  /* BOP */
    {
      xfer_bop_address (dtl, dvi);
      last_bop_address = this_bop_address;
    }
  }
  else if (opcode >= 171 && opcode <= 234)
    ;  /* FONTNUM uses no data */
  else if (opcode >= 235 && opcode <= 238)
    put_table (fnt, opcode, dtl, dvi);
  else if (opcode >= 239 && opcode <= 242)
  {
    n = opcode - 238;
    special (dtl, dvi, n);
  }
  else if (opcode >= 243 && opcode <= 246)
  {
    n = opcode - 242;
    fontdef (dtl, dvi, n);
  }
  else if (opcode == 247)
    preamble (dtl, dvi);
  else if (opcode == 248)
    postamble (dtl, dvi);
  else if (opcode == 249)
    post_post (dtl, dvi);
  else if (opcode >= 250 && opcode <= 255)
    ;  /* these, undefined, opcodes use no data */
  else
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(xfer_args) : opcode %d not handled.\n",
      opcode);
  }

  return 1;  /* OK */
}
/* xfer_args */


int
set_seq
#ifdef STDC
  (FILE * dtl, FILE * dvi)
#else
  (dtl, dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* Called _after_ a BSEQ_CHAR command */
/* Read bytes from dtl file, */
/* writing corresponding SETCHAR or SET1 commands to DVI file, */
/* _until_ unescaped ESEQ_CHAR is found */
/* Return 1 if OK, 0 on error */
/****  dt2dv assumes 8 bit characters,      ****/
/****  but some day one might change that.  ****/
{
  int status = 1;  /* status = 1 if OK, 0 if error */
  int more;  /* sequence of font characters continuing? */
  int escape = 0;  /* flag set if previous character was an escape */
  int ch;  /* character read from DTL file */
  more = 1;
  while (more)
  {
    /* ignore read_char status, to allow unprintable characters */
    (Void) read_char (dtl, &ch);
    /* but check for end of dtl file, or serious file reading error */
    if (ch < 0)
    {
      PRINT_PROGNAME;
      fprintf (stderr, "(set_seq) : ");
      fprintf (stderr, "end of dtl file, ");
      fprintf (stderr, "or serious dtl file reading error\n");
      dinfo();
      more = 0;
      status = 0;  /* bad news */
    }
    else  /* read dtl file, okay */
    {
      if (ch == ESC_CHAR && escape == 0)  /* escape next character */
      {
        escape = 1;
      }
      else
      {
        if (ch == ESEQ_CHAR && escape == 0)  /* end of sequence */
        {
          more = 0;
        }
        else if (ch <= 127)  /* can use SETCHAR */
        {
          put_byte (ch, dvi);
        }
        else if (ch <= 255)  /* can use SET1 */
        {
          put_byte (128, dvi);  /* SET1 opcode */
          put_unsigned (1, (U4) ch, dvi);
        }
        else
        {
          PRINT_PROGNAME;
          fprintf (stderr, "(set_seq) : ");
          fprintf (stderr,
            "ERROR : DTL character %d is not in range 0 to 255\n",
            ch);
          dexit (1);
          more = 0;
          status = 0;  /* Error, because dt2dv assumes 8 bit characters */
        }
        escape = 0;  /* current ch is not an escape character */
      }
    }
  }
  return status;
}
/* set_seq */


U4
xfer_hex
#ifdef STDC
  (int n, FILE * dtl, FILE * dvi)
#else
  (n, dtl, dvi)
  int n;
  FILE * dtl;
  FILE * dvi;
#endif
/* translate unsigned n-byte hexadecimal number from dtl to dvi file. */
/* return value of hexadecimal number */
{
  U4 unum = 0;  /* at most this space needed */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of arguments converted by sscanf */
  static Token token = "";  /* DTL token */

  if (n < 1 || n > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(xfer_hex) : INTERNAL ERROR : asked for %d bytes.  Must be 1 to 4.\n",
      n);
    dexit (1);
  }

  nread = read_token (dtl, token);

  nconv = sscanf (token, XF4, &unum);

  if (nconv < 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_hex) : DTL FILE ERROR (%s) :  %s \"%s\".\n",
      dtl_filename, "hexadecimal number expected, not", token);
    dexit (1);
  }

  put_unsigned (n, unum, dvi);

  return unum;
}
/* xfer_hex */


U4
xfer_oct
#ifdef STDC
  (int n, FILE * dtl, FILE * dvi)
#else
  (n, dtl, dvi)
  int n;
  FILE * dtl;
  FILE * dvi;
#endif
/* translate unsigned n-byte octal number from dtl to dvi file. */
/* return value of octal number */
{
  U4 unum = 0;  /* at most this space needed */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of arguments converted by sscanf */
  static Token token = "";  /* DTL token */

  if (n < 1 || n > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(xfer_oct) : INTERNAL ERROR : asked for %d bytes.  Must be 1 to 4.\n",
      n);
    dexit (1);
  }

  nread = read_token (dtl, token);

  nconv = sscanf (token, OF4, &unum);

  if (nconv < 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_oct) : DTL FILE ERROR (%s) :  %s \"%s\".\n",
      dtl_filename, "octal number expected, not", token);
    dexit (1);
  }

  put_unsigned (n, unum, dvi);

  return unum;
}
/* xfer_oct */


U4
xfer_unsigned
#ifdef STDC
  (int n, FILE * dtl, FILE * dvi)
#else
  (n, dtl, dvi)
  int n;
  FILE * dtl;
  FILE * dvi;
#endif
/* translate unsigned n-byte number from dtl to dvi file. */
/* return value of unsigned number */
{
  U4 unum = 0;  /* at most this space needed */

  unum = get_unsigned (dtl);
  put_unsigned (n, unum, dvi);

  return unum;
}
/* xfer_unsigned */


S4
xfer_signed
#ifdef STDC
  (int n, FILE * dtl, FILE * dvi)
#else
  (n, dtl, dvi)
  int n;
  FILE * dtl;
  FILE * dvi;
#endif
/* translate signed n-byte number from dtl to dvi file. */
/* return value of signed number */
{
  S4 snum = 0;

  snum = get_signed (dtl);
  put_signed (n, snum, dvi);

  return snum;
}
/* xfer_signed */


U4
get_unsigned
#ifdef STDC
  (FILE * dtl)
#else
  (dtl)
  FILE * dtl;
#endif
/* read unsigned number from dtl file. */
/* return value of unsigned number */
{
  U4 unum = 0;  /* at most this space needed */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of arguments converted by sscanf */
  static Token token = "";  /* DTL token */

  nread = read_token (dtl, token);

  nconv = sscanf (token, UF4, &unum);

  if (nconv < 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(get_unsigned) : DTL FILE ERROR (%s) :  %s \"%s\".\n",
      dtl_filename, "unsigned number expected, not", token);
    dexit (1);
  }

  return unum;
}
/* get_unsigned */


S4
get_signed
#ifdef STDC
  (FILE * dtl)
#else
  (dtl)
  FILE * dtl;
#endif
/* read signed number from dtl file. */
/* return value of signed number */
{
  S4 snum = 0;
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of sscanf arguments converted and assigned */
  static Token token = "";

  nread = read_token (dtl, token);

  nconv = sscanf (token, SF4, &snum);

  if (nconv < 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(get_signed) : DTL FILE ERROR (%s) :  %s \"%s\".\n",
      dtl_filename, "signed number expected, not", token);
    dexit (1);
  }

  return snum;
}
/* get_signed */


int
put_unsigned
#ifdef STDC
  (int n, U4 unum, FILE * dvi)
#else
  (n, unum, dvi)
  int n;
  U4 unum;
  FILE * dvi;
#endif
/* put unsigned in-byte integer to dvi file */
/* DVI format uses Big-endian storage of numbers: */
/* most significant byte is first. */
/* return number of bytes written. */
{
  Byte ubyte[4];  /* at most 4 bytes needed in DVI format */
  int i;

  if (n < 1 || n > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_unsigned) : INTERNAL ERROR : asked for %d bytes.  Must be 1 to 4.\n",
      n);
    dexit (1);
  }

  /* Big-endian storage. */
  for (i = 0; i < n; i++)
  {
    ubyte[i] = (Byte) (unum % 256);
    unum /= 256;
  }
  /* Reverse order for big-endian representation. */
  for (i = n-1;  i >= 0;  i--)
  {
    put_byte ((int) ubyte[i], dvi);
  }

  return n;
}
/* put_unsigned */


int
put_signed
#ifdef STDC
  (int n, S4 snum, FILE * dvi)
#else
  (n, snum, dvi)
  int n;
  S4 snum;
  FILE * dvi;
#endif
/* put signed in-byte integer to dvi file */
/* DVI format uses 2's complement Big-endian storage of signed numbers: */
/* most significant byte is first. */
/* return number of bytes written. */
{
  /* Will this deal properly with the sign? */

  if (n < 1 || n > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_signed) : INTERNAL ERROR : asked for %d bytes.  Must be 1 to 4.\n",
      n);
    dexit (1);
  }

  /* How do we ensure 2's complement representation? */
  /* Here's a trick that I hope is portable, given ANSI C. */
  /* See K&R (2nd edition), Appendix A6.2 "Integral Conversions". */

  /* Convert snum to U4 data type */
  put_unsigned (n, (U4) snum, dvi);

  return n;
}
/* put_signed */


int
check_bmes
#ifdef STDC
  (FILE * dtl)
#else
  (dtl)
  FILE * dtl;
#endif
/* check that a BMES_CHAR is the next non-whitespace character in dtl */
{
  int ch;  /* next non-whitespace character in dtl */

  /* `(Void)' because we ignore the number of spaces skipped */
  (Void) skip_space (dtl, &ch);

  if (ch < 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(check_bmes) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "end of dtl file, or reading error\n");
    dexit (1);
  }

  if (ch != BMES_CHAR)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(check_bmes) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "BMES_CHAR (`%c') %s, not `%c' (char %d).\n",
      BMES_CHAR, "expected before string", ch, ch);
    dexit (1);
  }

  return 1;  /* OK */
}
/* check_bmes */


int
check_emes
#ifdef STDC
  (FILE * dtl)
#else
  (dtl)
  FILE * dtl;
#endif
/* check that an EMES_CHAR is the next character in dtl */
{
  int ch;  /* dtl character */

  if (read_char (dtl, &ch) == 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(check_emes) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "end of dtl file, or reading error\n");
    dexit (1);
  }

  if (ch != EMES_CHAR)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(check_emes) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "EMES_CHAR (`%c') %s, not `%c' (char %d).\n",
      EMES_CHAR, "expected to follow string", ch, ch);
    dexit (1);
  }

  return 1;  /* OK */
}
/* check_emes */


/* Size typically used in this program for Lstring variables */
#define LSIZE 1024


Void
init_Lstring
#ifdef STDC
  (Lstring * lsp, long int n)
#else
  (lsp, n)
  Lstring * lsp;
  long int n;
#endif
{
  lsp->l = 0;
  lsp->m = n;
  lsp->s = (char *) gmalloc (n);
}
/* init_Lstring */


Void
de_init_Lstring
#ifdef STDC
  (Lstring * lsp)
#else
  (lsp)
  Lstring * lsp;
#endif
{
  lsp->l = 0;
  lsp->m = 0;
  free (lsp->s);
  lsp->s = NULL;  /* to be sure */
}
/* de_init_Lstring */


Lstring *
alloc_Lstring
#ifdef STDC
  (long int n)
#else
  (n)
  long int n;
#endif
{
  Lstring * lsp;
  lsp = (Lstring *) gmalloc (sizeof (Lstring));
  init_Lstring (lsp, n);
  return (lsp);
}
/* alloc_Lstring */


Void
free_Lstring
#ifdef STDC
  (Lstring * lstr)
#else
  (lstr)
  Lstring * lstr;
#endif
{
  free (lstr->s);
  free (lstr);
}
/* free_Lstring */


Void
ls_putb
#ifdef STDC
  (int ch, Lstring * lstr)
#else
  (ch, lstr)
  int ch;
  Lstring * lstr;
#endif
/* write byte ch into Lstring *lstr */
{
  if (lstr->l >= lstr->m)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(ls_putb) : ERROR : No more room in Lstring.\n");
    dexit (1);
  }
  else
  {
    lstr->s [(lstr->l)++] = ch;
  }
}
/* ls_putb */


long int
get_Lstring
#ifdef STDC
  (FILE * dtl, Lstring * lstr)
#else
  (dtl, lstr)
  FILE * dtl;
  Lstring * lstr;
#endif
/* get a string from dtl file, store as an Lstring in *lstr. */
/* lstr must already be allocated and initialised. */
/* return length of Lstring *lstr */
{
  U4 nch;
  int char_status = CHAR_OK;  /* OK so far */

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(get_Lstring) : entering get_Lstring.\n");
  }

  check_bmes (dtl);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(get_Lstring) : string is: \"");
  }

  for (nch=0; ; nch++)
  {
    int ch;

    char_status = read_string_char (dtl, &ch);

    if (char_status == CHAR_FAIL)
    {
      /* end of dtl file, or reading error */
      fprintf (stderr, "\n");
      PRINT_PROGNAME;
      fprintf (stderr, "(get_Lstring) : DTL FILE ERROR (%s) : ",
        dtl_filename);
      fprintf (stderr, "cannot read string[");
      fprintf (stderr, UF4, nch);
      fprintf (stderr, "] from dtl file.\n");
      dexit (1);
    }

    if (debug)
    {
      fprintf (stderr, "%c", ch);
    }

    if (char_status == CHAR_EOS)
    {
      if (ch != EMES_CHAR)
      {
        PRINT_PROGNAME;
        fprintf (stderr, "(get_Lstring) : INTERNAL ERROR : ");
        fprintf (stderr, "char_status = CHAR_FAIL,\n");
        fprintf (stderr,
          "but ch = %c (char %d) is not EMES_CHAR = %c (char %d)\n",
          ch, ch, EMES_CHAR, EMES_CHAR);
        dexit (1);
      }
      (Void) unread_char ();
      break;  /* end of string */
    }
    else if (char_status == CHAR_OK)
    {
      ls_putb (ch, lstr);
    }
    else
    {
        PRINT_PROGNAME;
        fprintf (stderr, "(get_Lstring) : INTERNAL ERROR : ");
        fprintf (stderr, "char_status = %d is unfamiliar!\n", char_status);
        dexit (1);
    }
  }
  /* end for */

  if (debug)
  {
    fprintf (stderr, "\".\n");
  }

  check_emes (dtl);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(get_Lstring) : leaving get_Lstring.\n");
  }

  return (lstr->l);
}
/* get_Lstring */


Void
put_Lstring
#ifdef STDC
  (const Lstring * lstr, FILE * dvi)
#else
  (str, dvi)
  Lstring lstr;
  FILE * dvi;
#endif
{
  size_t fwret;
  fwret = fwrite ((lstr->s), sizeof (char), (size_t) (lstr->l), dvi);

  dvi_written += fwret;

  if (fwret < lstr->l)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_Lstring) : DVI File ERROR : not all bytes written ");
    fprintf (stderr, "(%ld of %ld).\n",
      (long int) fwret, (long int) (lstr->l));
    dexit (1);
  }
}
/* put_Lstring */


U4
xfer_len_string
#ifdef STDC
  (int n, FILE * dtl, FILE * dvi)
#else
  (n, dtl, dvi)
  int n;
  FILE * dtl;
  FILE * dvi;
#endif
/* transfer (length and) quoted string from dtl to dvi file, */
/* return number of bytes written to dvi file. */
{
  U4 k, k2;
  Lstring lstr;

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_len_string) : entering xfer_len_string.\n");
  }

  init_Lstring (&lstr, LSIZE);

  /* k[n] : length of special string */

  k = get_unsigned (dtl);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_len_string) : string's nominal length k = ");
    fprintf (stderr, UF4, k);
    fprintf (stderr, " characters.\n");
  }

  k2 = get_Lstring (dtl, &lstr);

  if (k2 != k)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_len_string) : WARNING : string length (");
    fprintf (stderr, UF4, k);
    fprintf (stderr, ") in DTL file is wrong\n");
    fprintf (stderr, "Writing correct value (");
    fprintf (stderr, UF4, k2);
    fprintf (stderr, ") to DVI file\n");
  }

  put_unsigned (n, k2, dvi);

  put_Lstring (&lstr, dvi);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_len_string) : leaving xfer_len_string.\n");
  }

  de_init_Lstring (&lstr);

  return (n + k2);
}
/* xfer_len_string */


S4
xfer_bop_address
#ifdef STDC
  (FILE * dtl, FILE * dvi)
#else
  (dtl, dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* translate signed 4-byte bop address from dtl to dvi file. */
/* return value of bop address written to DVI file */
{
  S4 snum = 0;  /* at most this space needed for byte address */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of arguments converted by sscanf */
  static Token token = "";  /* DTL token */

  nread += read_token (dtl, token);

  nconv = sscanf (token, SF4, &snum);

  if (nconv != 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_bop_address) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "signed number expected, not \"%s\".\n", token);
    dexit (1);
  }

  if (snum != last_bop_address)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_bop_address) : WARNING : byte address (");
    fprintf (stderr, WF, snum);
    fprintf (stderr, ")\n");
    fprintf (stderr, "for previous bop in DTL file is wrong\n");
    fprintf (stderr, "Writing correct value (");
    fprintf (stderr, WF, last_bop_address);
    fprintf (stderr, ") to DVI file\n");
  }

  put_signed (4, last_bop_address, dvi);

  return last_bop_address;
}
/* xfer_bop_address */


S4
xfer_postamble_address
#ifdef STDC
  (FILE * dtl, FILE * dvi)
#else
  (dtl, dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* translate signed 4-byte postamble address from dtl to dvi file. */
/* return value of postamble address written to DVI file */
{
  S4 snum = 0;  /* at most this space needed for byte address */
  COUNT nread = 0;  /* number of DTL bytes read by read_token */
  int nconv = 0;  /* number of arguments converted by sscanf */
  static Token token = "";  /* DTL token */

  nread += read_token (dtl, token);

  nconv = sscanf (token, SF4, &snum);

  if (nconv != 1)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_postamble_address) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "signed number expected, not \"%s\".\n", token);
    dexit (1);
  }

  if (snum != postamble_address)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(xfer_postamble_address) : WARNING : byte address (");
    fprintf (stderr, WF, snum);
    fprintf (stderr, ")\n");
    fprintf (stderr, "for postamble in DTL file is wrong\n");
    fprintf (stderr, "Writing correct value (");
    fprintf (stderr, WF, postamble_address);
    fprintf (stderr, ") to DVI file\n");
  }

  put_signed (4, postamble_address, dvi);

  return postamble_address;
}
/* xfer_postamble_address */


int
put_table
#ifdef STDC
  (op_table table, int opcode, FILE * dtl, FILE * dvi)
#else
  (table, opcode, dtl, dvi)
  op_table table;
  int opcode;
  FILE * dtl;
  FILE * dvi;
#endif
{
  /* table:  {char * name; int first, last; op_info * list; }; */
  /* op_info:   {int code; char * name; int nargs; char * args; }; */

  op_info op;  /* entry in table */
  int i;
  int pos;  /* current position in string being scanned */
  static String args = "";

  /* Defensive programming. */
  if (opcode < table.first || opcode > table.last)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_table) : DTL FILE (OR INTERNAL) ERROR : opcode %d ", opcode);
    fprintf (stderr, "is outside table %s [ %d to %d ] !\n",
      table.name, table.first, table.last);
    dexit (1);
  }

  op = table.list [ opcode - table.first ];

  /* More defensive programming. */
  if (opcode != op.code)
  {
    PRINT_PROGNAME;
    fprintf (stderr,
      "(put_table) : INTERNAL ERROR : opcode %d for command \"%s\"",
      opcode, op.name);
    fprintf (stderr, " faulty in table \"%s\".\n", table.name);
    dexit (1);
  }

  /* process all the arguments, according to size and sign */

  strncpy (args, op.args, MAXSTRLEN);

  pos = 0;
  for (i=0; i < op.nargs; i++)
  {
    int argtype = 0;
    int nscan = 0;  /* number of bytes read by sscanf */
    int nconv = 0;  /* number of sscanf arguments converted & assigned */

    /* sscanf() does NOT advance over its input: */
    /* C strings lack internal state information, which C files have. */
    /* On Sun/OS, sscanf calls ungetc on the string it reads, */
    /* which therefore has to be writable. */

    nconv = sscanf (args + pos, "%d%n", &argtype, &nscan);

    if (nconv < 1 || nscan < 1)
    {
      PRINT_PROGNAME;
      fprintf (stderr,
        "(put_table) : INTERNAL ERROR : internal read of table %s failed!\n",
        table.name);
      dexit (1);
    }

    pos += nscan;

    if (argtype < 0)
      xfer_signed (-argtype, dtl, dvi);
    else
      xfer_unsigned (argtype, dtl, dvi);
  }
  /* end for */

  return 1;  /* OK */
}
/* put_table */


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


U4
special
#ifdef STDC
  (FILE * dtl,  FILE * dvi,  int n)
#else
  (dtl,  dvi,  n)
  FILE * dtl;
  FILE * dvi;
  int n;
#endif
/* read special (1 <= n <= 4 byte) data from dtl, and write in dvi */
/* return number of bytes written */
{
  U4  nk;

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(special) : entering special.\n");
  }

  if (n < 1 || n > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(special) : DTL FILE ERROR (%s) : special %d, ",
      dtl_filename, n);
    fprintf (stderr, "range is 1 to 4.\n");
    dexit (1);
  }

  /* k[n] : length of special string */
  /* x[k] : special string */
  /* nk = n + k */
  nk = xfer_len_string (n, dtl, dvi);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(special) : leaving special.\n");
  }

  return (nk);
}
/* special */


int
fontdef
#ifdef STDC
  (FILE * dtl,  FILE * dvi,  int suffix)
#else
  (dtl,  dvi,  suffix)
  FILE * dtl;
  FILE * dvi;
  int suffix;
#endif
/* read fontdef fnt_def1 .. fnt_def4 from dtl, and write in dvi */
/* suffix is the fontdef suffix : 1 to 4 */
/* return number of bytes written */
{
  U4  a, l, a2, l2;
  U4 k;
  Lstring lstr1, lstr2;

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : entering fontdef.\n");
  }

  if (suffix < 1 || suffix > 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : DTL FILE ERROR (%s) : ",
      dtl_filename);
    fprintf (stderr, "font def %d, but range is 1 to 4.\n", suffix);
    dexit (1);
  }

  init_Lstring (&lstr1, LSIZE);
  init_Lstring (&lstr2, LSIZE);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : about to read font number.\n");
  }

  /* k[suffix] : font number */
  if (suffix == 4)
    k = xfer_signed (suffix, dtl, dvi);
  else
    k = xfer_unsigned (suffix, dtl, dvi);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : font ");
    fprintf (stderr, UF4, k);
    fprintf (stderr, ".\n");
  }

#ifdef HEX_CHECKSUM
  /* c[4] : (hexadecimal) checksum : I (gt) would prefer this */
  xfer_hex (4, dtl, dvi);
#else /NOT HEX_CHECKSUM */
  /* c[4] : checksum (octal, for comparison with tftopl's .pl file) */
  xfer_oct (4, dtl, dvi);
#endif

  /* s[4] */
  xfer_unsigned (4, dtl, dvi);

  /* d[4] */
  xfer_unsigned (4, dtl, dvi);

  /* If DTL file's edited, a and l may be wrong. */

  /* a[1] : length of font `area' (directory) portion of pathname string */
  a = get_unsigned (dtl);

  /* l[1] : length of font portion of pathname string */
  l = get_unsigned (dtl);

  /* n[a+l] : font pathname string <= area + font */

  a2 = get_Lstring (dtl, &lstr1);

  if (a2 != a)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : WARNING : font area string's length (");
    fprintf (stderr, UF4, a);
    fprintf (stderr, ") in DTL file is wrong\n");
    fprintf (stderr, "Writing correct value (");
    fprintf (stderr, UF4, a2);
    fprintf (stderr, ") to DVI file\n");
  }

  put_unsigned (1, a2, dvi);

  l2 = get_Lstring (dtl, &lstr2);

  if (l2 != l)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : WARNING : font string's length (");
    fprintf (stderr, UF4, l);
    fprintf (stderr, ") in DTL file is wrong\n");
    fprintf (stderr, "Writing correct value (");
    fprintf (stderr, UF4, l2);
    fprintf (stderr, ") to DVI file\n");
  }

  put_unsigned (1, l2, dvi);

  put_Lstring (&lstr1, dvi);
  put_Lstring (&lstr2, dvi);

  de_init_Lstring (&lstr2);
  de_init_Lstring (&lstr1);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(fontdef) : leaving fontdef.\n");
  }

  return (suffix + 4*4 + 2*1 + a2 + l2);
}
/* fontdef */


U4
preamble
#ifdef STDC
  (FILE * dtl,  FILE * dvi)
#else
  (dtl,  dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* read preamble from dtl, and write in dvi */
/* return number of bytes written */
{
  U4  k1;

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(preamble) : entering preamble.\n");
  }

  /* i[1] */
  xfer_unsigned (1, dtl, dvi);

  /* num[4] */
  xfer_unsigned (4, dtl, dvi);

  /* den[4] */
  xfer_unsigned (4, dtl, dvi);

  /* mag[4] */
  xfer_unsigned (4, dtl, dvi);

  /* k[1] : length of comment */
  /* x[k] : comment string */
  /* k1 = 1 + k */
  k1 = xfer_len_string (1, dtl, dvi);

  if (debug)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(preamble) : leaving preamble.\n");
  }

  return (1 + 3*4 + k1);
}
/* preamble */


int
postamble
#ifdef STDC
  (FILE * dtl,  FILE * dvi)
#else
  (dtl,  dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* read postamble from dtl, and write in dvi */
/* return number of bytes written */
{
  postamble_address = dvi_written - 1;

  /* p[4] : DVI address of previous bop command */
  /*        --- unsigned? --- or signed, as I assume? */
  /* For, surely  p  should be  -1  if the DVI file has NO bop? */
  xfer_bop_address (dtl, dvi);

  /* num[4] */
  xfer_unsigned (4, dtl, dvi);

  /* den[4] */
  xfer_unsigned (4, dtl, dvi);

  /* mag[4] */
  xfer_unsigned (4, dtl, dvi);

  /* l[4] */
  xfer_unsigned (4, dtl, dvi);

  /* u[4] */
  xfer_unsigned (4, dtl, dvi);

  /* s[2] */
  xfer_unsigned (2, dtl, dvi);

  /* t[2] */
  xfer_unsigned (2, dtl, dvi);

  return (6*4 + 2*2);
}
/* postamble */


int
post_post
#ifdef STDC
  (FILE * dtl,  FILE * dvi)
#else
  (dtl,  dvi)
  FILE * dtl;
  FILE * dvi;
#endif
/* read post_post from dtl, and write in dvi */
/* return number of bytes written */
{
  /* hope I'm writing the "223" bytes in an 8-bit clean way */
  int n223 = 0;  /* number of "223" bytes in final padding */

  /* q[4] : DVI address of post command */
  /*        --- unsigned? --- or signed, as I assume? */
  /* what happens if there is NO postamble command? */
  /* shouldn't  q  be  -1  then? */

  xfer_postamble_address (dtl, dvi);

  /* i[1] : DVI identification byte = 2 */
  xfer_unsigned (1, dtl, dvi);

  for (n223 = 0;  true;  n223++)
  {
    COUNT nread = 0;  /* number of DTL bytes read by read_token */
    static Token token;

    strcpy (token, "");

    nread = read_token (dtl, token);

    /* check whether end of dtl file */
    if (nread == 0)
    {
      if (group)
      {
	/* dtl file shouldn't end before an ECOM */
        PRINT_PROGNAME;
	fprintf (stderr, "(post_post) : DTL FILE ERROR (%s) : ",
          dtl_filename);
        fprintf (stderr, "premature end of DTL file!\n");
	fprintf (stderr,
	  "%d complete iterations of \"padding byte\" loop;\n", n223);
	fprintf (stderr, "troublesome token = \"%s\"\n", token);
	dexit (1);
      }
      /* leave the "223" loop */
      break;
    }
    else if (strcmp (token, "223") == 0)
    {
      /* token is a "223" padding byte */
      /* loop again */
    }
    else
    {
      /* read a non-empty token that wasn't "223" */
      (Void) unread_char ();
      if (group)
      {
	if (strcmp (token, ECOM) == 0)
	{
	  /* end of DTL's post_post command */
	}
	else
	{
	  /* error : expected end of post_post */
          PRINT_PROGNAME;
	  fprintf (stderr, "(post_post) : DTL FILE ERROR (%s) : ",
            dtl_filename);
	  fprintf (stderr, "token \"%s\" should be ECOM (\"%s\")\n",
            token, ECOM);
	  dexit (1);
	}
      }
      /* leave the "223" loop */
      break;
    }
  }
  /* end for */

  if (n223 < 4)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(post_post) : DTL FILE ERROR (%s) : \n",
      dtl_filename);
    fprintf (stderr, "fewer than four `223' padding bytes.\n");
    fprintf (stderr, "Will write at least four `223' padding bytes.\n");
  }

  /* check whether the DVI file size is a multiple of 4 bytes */
  if ((dvi_written + n223) % 4 != 0)
  {
    PRINT_PROGNAME;
    fprintf (stderr, "(post_post) : WARNING : \n");
    fprintf (stderr, "DVI size ");
    fprintf (stderr, WF, dvi_written);
    fprintf (stderr, " (bytes) wouldn't be a multiple of 4 !\n");
    fprintf (stderr,
      "Will write (at least four) `223' padding bytes until it is.\n");
  }

  /* final padding of DVI file by "223" bytes to a multiple of 4 bytes, */
  /* with at least 4 bytes */

  for (n223 = 0;  (n223 < 4) || (dvi_written % 4 != 0);  n223++)
  {
    /* add a "223" padding byte */
    put_byte (223, dvi);
  }

  return (4 + 1 + n223);
}
/* post_post */


/* end of dt2dv.c */
