/* dv2dt - convert DVI file to human-readable "DTL" format.
   - (ANSI C) version 0.6.0 - 17:54 GMT +11  Wed 8 March 1995
   - author:  Geoffrey Tobin    ecsgrt@luxor.latrobe.edu.au
   - patch:  Michal Tomczak-Jaegermann   ntomczak@vm.ucs.ualberta.ca
   - 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef KPATHSEA
#include <kpathsea/c-fopen.h>
#endif

#include "dtl.h"

#define PRINT_BCOM   if (group) fprintf (dtl, "%s", BCOM)
#define PRINT_ECOM   if (group) fprintf (dtl, "%s", ECOM)

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

typedef  struct op_info_st  op_info;

/*
  table's:
     name,
     first opcode,
     last opcode,
     pointer to opcode info.
*/
struct op_table_st {char * name; int first; int last; op_info * list; };

typedef  struct op_table_st  op_table;

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

op_info  op_info_128_170 [] =
{
  {128, "s1", 1, "1"},
  {129, "s2", 1, "2"},
  {130, "s3", 1, "3"},
  {131, "s4", 1, "-4"},
  {132, "sr", 2, "-4 -4"},
  {133, "p1", 1, "1"},
  {134, "p2", 1, "2"},
  {135, "p3", 1, "3"},
  {136, "p4", 1, "-4"},
  {137, "pr", 2, "-4 -4"},
  {138, "nop", 0, ""},
  {139, "bop", 11, "-4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4"},
  {140, "eop", 0, ""},
  {141, "[", 0, ""},
  {142, "]", 0, ""},
  {143, "r1", 1, "-1"},
  {144, "r2", 1, "-2"},
  {145, "r3", 1, "-3"},
  {146, "r4", 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, "d1", 1, "-1"},
  {158, "d2", 1, "-2"},
  {159, "d3", 1, "-3"},
  {160, "d4", 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 font with 1 to 4 bytes (opcodes 235 to 238) inclusive. */

op_info  fnt_n [] =
{
  {235, "f1", 1, "1"},
  {236, "f2", 1, "2"},
  {237, "f3", 1, "3"},
  {238, "f4", 1, "-4"}
};  /* op_info  fnt_n [] */

op_table  fnt  =  {"f", 235, 238, fnt_n};


/* function prototypes */

int open_dvi ARGS((char * dvi_file, FILE ** dvi));
int open_dtl ARGS((char * dtl_file, FILE ** dtl));
int dv2dt ARGS((FILE * dvi, FILE * dtl));

COUNT wunsigned ARGS((int n,  FILE * dvi,  FILE * dtl));
COUNT wsigned   ARGS((int n,  FILE * dvi,  FILE * dtl));
S4 rsigned   ARGS((int n,  FILE * dvi));
U4 runsigned ARGS((int n,  FILE * dvi));

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

COUNT setseq ARGS((int opcode, FILE * dvi, FILE * dtl));
Void setpchar ARGS((int charcode, FILE * dtl));
Void xferstring ARGS((int k, FILE * dvi, FILE * dtl));

COUNT special ARGS((FILE * dvi,  FILE * dtl,  int n));
COUNT fontdef ARGS((FILE * dvi,  FILE * dtl,  int n));
COUNT preamble  ARGS((FILE * dvi,  FILE * dtl));
COUNT postamble ARGS((FILE * dvi,  FILE * dtl));
COUNT postpost  ARGS((FILE * dvi,  FILE * dtl));


String program;  /* name of dv2dt program */

int
main
#ifdef STDC
  (int argc,  char * argv[])
#else
  (argc, argv)
  int argc;
  char * argv[];
#endif
{
  FILE * dvi = stdin;
  FILE * dtl = stdout;

  /* Watch out:  C's standard library's string functions are dicey */
  strncpy (program, argv[0], MAXSTRLEN);

  if (argc > 1)
    open_dvi (argv[1], &dvi);
  else if (!isatty(fileno(dvi)))
    SET_BINARY(fileno(dvi));

  if (argc > 2)
    open_dtl (argv[2], &dtl);

  dv2dt (dvi, dtl);

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

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. */
{
  if (pdvi == NULL)
  {
    fprintf (stderr, "%s:  address of dvi variable is NULL.\n", program);
    exit (1);
  }

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

  if (*pdvi == NULL)
  {
    fprintf (stderr, "%s:  Cannot open \"%s\" for binary reading.\n",
      program, dvi_file);
    exit (1);
  }

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

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. */
{
  if (pdtl == NULL)
  {
    fprintf (stderr, "%s:  address of dtl variable is NULL.\n", program);
    exit (1);
  }

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

  if (*pdtl == NULL)
  {
    fprintf (stderr, "%s:  Cannot open \"%s\" for text writing.\n",
      program, dtl_file);
    exit (1);
  }

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

int
dv2dt
#ifdef STDC
  (FILE * dvi, FILE * dtl)
#else
  (dvi, dtl)
  FILE * dvi;
  FILE * dtl;
#endif
{
  int opcode;
  COUNT count;  /* intended to count bytes to DVI file; as yet unused. */

  PRINT_BCOM;
  fprintf (dtl, "variety ");
/*  fprintf (dtl, BMES); */
  fprintf (dtl, VARIETY);
/*  fprintf (dtl, EMES); */
  PRINT_ECOM;
  fprintf (dtl, "\n");

  /* start counting DVI bytes */
  count = 0;
  while ((opcode = fgetc (dvi)) != EOF)
  {
    PRINT_BCOM;  /* start of command and parameters */
    if (opcode < 0 || opcode > 255)
    {
      count += 1;
      fprintf (stderr, "%s:  Non-byte from \"fgetc()\"!\n", program);
      exit (1);
    }
    else if (opcode <= 127)
    {
      /* setchar commands */
      /* count += 1; */
      /* fprintf (dtl, "%s%d", SETCHAR, opcode); */
      count +=
      setseq (opcode, dvi, dtl);
    }
    else if (opcode >= 128 && opcode <= 170)
    {
      count +=
      wtable (op_128_170, opcode, dvi, dtl);
    }
    else if (opcode >= 171 && opcode <= 234)
    {
      count += 1;
      fprintf (dtl, "%s%d", FONTNUM, opcode - 171);
    }
    else if (opcode >= 235 && opcode <= 238)
    {
      count +=
      wtable (fnt, opcode, dvi, dtl);
    }
    else if (opcode >= 239 && opcode <= 242)
    {
      count +=
      special (dvi, dtl, opcode - 238);
    }
    else if (opcode >= 243 && opcode <= 246)
    {
      count +=
      fontdef (dvi, dtl, opcode - 242);
    }
    else if (opcode == 247)
    {
      count +=
      preamble (dvi, dtl);
    }
    else if (opcode == 248)
    {
      count +=
      postamble (dvi, dtl);
    }
    else if (opcode == 249)
    {
      count +=
      postpost (dvi, dtl);
    }
    else if (opcode >= 250 && opcode <= 255)
    {
      count += 1;
      fprintf (dtl, "opcode%d", opcode);
    }
    else
    {
      count += 1;
      fprintf (stderr, "%s:  unknown byte.\n", program);
      exit (1);
    }
    PRINT_ECOM;  /* end of command and parameters */
    fprintf (dtl, "\n");
    if (fflush (dtl) == EOF)
    {
      fprintf (stderr, "%s:  fflush on dtl file gave write error!\n", program);
      exit (1);
    }
  } /* end while */

  return 1;  /* OK */
}
/* dv2dt */


COUNT
wunsigned
#ifdef STDC
  (int n, FILE * dvi, FILE * dtl)
#else
  (n, dvi, dtl)
  int n;
  FILE * dvi;
  FILE * dtl;
#endif
{
  U4 unum;

  fprintf (dtl, " ");
  unum = runsigned (n, dvi);
  fprintf (dtl, UF4, unum);
  return n;
}
/* end wunsigned */

COUNT
wsigned
#ifdef STDC
  (int n, FILE * dvi, FILE * dtl)
#else
  (n, dvi, dtl)
  int n;
  FILE * dvi;
  FILE * dtl;
#endif
{
  S4 snum;

  fprintf (dtl, " ");
  snum = rsigned (n, dvi);
  fprintf (dtl, SF4, snum);
  return n;
}
/* end wsigned */

U4
runsigned
#ifdef STDC
  (int n,  FILE * dvi)
#else
  (n, dvi)
  int n;
  FILE * dvi;
#endif
/* read 1 <= n <= 4 bytes for an unsigned integer from dvi file */
/* DVI format uses Big-endian storage of numbers. */
{
  U4 integer;
  int ibyte = 0;
  int i;

  if (n < 1 || n > 4)
  {
    fprintf (stderr,
      "%s:  runsigned() asked for %d bytes.  Must be 1 to 4.\n", program, n);
    exit (1);
  }

  /* Following calculation works iff storage is big-endian. */
  integer = 0;
  for (i = 0; i < n; i++)
  {
    integer *= 256;
    ibyte = fgetc (dvi);
    integer += ibyte;
  }

  return integer;
}
/* end runsigned */

S4
rsigned
#ifdef STDC
  (int n,  FILE * dvi)
#else
  (n, dvi)
  int n;
  FILE * dvi;
#endif
/* read 1 <= n <= 4 bytes for a signed integer from dvi file */
/* DVI format uses Big-endian storage of numbers. */
{
  S4 integer;
  int ibyte = 0;
  int i;

  if (n < 1 || n > 4)
  {
    fprintf (stderr,
      "%s:  rsigned() asked for %d bytes.  Must be 1 to 4.\n", program, n);
    exit (1);
  }

  /* Following calculation works iff storage is big-endian. */
  integer = 0;
  for (i = 0; i < n; i++)
  {
    integer *= 256;
    ibyte = fgetc (dvi);
    /* Big-endian implies sign byte is first byte. */
    if (i == 0 && ibyte >= 128)
    {
      ibyte -= 256;
    }
    integer += ibyte;
  }

  return integer;
}
/* end rsigned */

COUNT
wtable
#ifdef STDC
  (op_table table, int opcode, FILE * dvi, FILE * dtl)
#else
  (table, opcode, dvi, dtl)
  op_table table;
  int opcode;
  FILE * dvi;
  FILE * dtl;
#endif
/* write command with given opcode in given table */
/* return number of DVI bytes in this command */
{
  op_info op;  /* pointer into table of operations and arguments */
  COUNT bcount = 0;  /* number of bytes in arguments of this opcode */
  String args;  /* arguments string */
  int i;  /* count of arguments read from args */
  int pos;  /* position in args */

  /* Defensive programming. */
  if (opcode < table.first || opcode > table.last)
  {
    fprintf (stderr,
      "%s: opcode %d is outside table %s [ %d to %d ] !\n",
      program, opcode, table.name, table.first, table.last);
    exit (1);
  }

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

  /* Further defensive programming. */
  if (op.code != opcode)
  {
    fprintf (stderr, "%s: internal table %s wrong!\n", program, table.name);
    exit (1);
  }

  bcount = 1;
  fprintf (dtl, "%s", op.name);

  /* NB:  sscanf does an ungetc, */
  /*      so args must be writable. */

  strncpy (args, op.args, MAXSTRLEN);

  pos = 0;
  for (i = 0; i < op.nargs; i++)
  {
    int argtype;  /* sign and number of bytes in current argument */
    int nconv;  /* number of successful conversions from args */
    int nread;  /* number of bytes read from args */

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

    /* internal consistency checks */
    if (nconv != 1 || nread <= 0)
    {
      fprintf (stderr,
        "%s: internal read of table %s failed!\n", program, table.name);
      exit (1);
    }

    pos += nread;

    bcount += ( argtype < 0 ?
               wsigned  (-argtype, dvi, dtl) :
               wunsigned (argtype, dvi, dtl)  ) ;
  } /* end for */

  return bcount;

}
/* wtable */

COUNT
setseq
#ifdef STDC
  (int opcode, FILE * dvi, FILE * dtl)
#else
  (opcode, dvi, dtl)
  int opcode;
  FILE * dvi;
  FILE * dtl;
#endif
/* write a sequence of setchar commands */
/* return count of DVI bytes interpreted into DTL */
{
  int charcode = opcode;  /* fortuitous */
  int ccount = 0;

  if (!isprint (charcode))
  {
    ccount = 1;
    fprintf (dtl, "%s%02X", SETCHAR, opcode);
  }
  else
  {
    /* start of sequence of font characters */
    fprintf (dtl, BSEQ);

    /* first character */
    ccount = 1;
    setpchar (charcode, dtl);

    /* subsequent characters */
    while ((opcode = fgetc (dvi)) != EOF)
    {
      if (opcode < 0 || opcode > 127)
      {
        break;  /* not a setchar command, so sequence has ended */
      }
      charcode = opcode;  /* fortuitous */
      if (!isprint (charcode))  /* not printable ascii */
      {
        break;  /* end of font character sequence, as for other commands */
      }
      else  /* printable ASCII */
      {
        ccount += 1;
        setpchar (charcode, dtl);
      }
    }  /* end for loop */

    /* prepare to reread opcode of next DVI command */
    if (ungetc (opcode, dvi) == EOF)
    {
      fprintf (stderr, "setseq:  cannot push back a byte\n");
      exit (1);
    }

    /* end of sequence of font characters */
    fprintf (dtl, ESEQ);
  }
  return ccount;
}
/* setseq */

Void
setpchar
#ifdef STDC
  (int charcode, FILE * dtl)
#else
  (charcode, dtl)
  int charcode;
  FILE * dtl;
#endif
/* set printable character */
{
  switch (charcode)
  {
    case ESC_CHAR:
      fprintf (dtl, "%c", ESC_CHAR);
      fprintf (dtl, "%c", ESC_CHAR);
      break;
    case QUOTE_CHAR:
      fprintf (dtl, "%c", ESC_CHAR);
      fprintf (dtl, "%c", QUOTE_CHAR);
      break;
    case BSEQ_CHAR:
      fprintf (dtl, "%c", ESC_CHAR);
      fprintf (dtl, "%c", BSEQ_CHAR);
      break;
    case ESEQ_CHAR:
      fprintf (dtl, "%c", ESC_CHAR);
      fprintf (dtl, "%c", ESEQ_CHAR);
      break;
    default:
      fprintf (dtl, "%c", charcode);
      break;
  }
}
/* setpchar */

Void
xferstring
#ifdef STDC
  (int k, FILE * dvi, FILE * dtl)
#else
  (k, dvi, dtl)
  int k;
  FILE * dvi;
  FILE * dtl;
#endif
/* copy string of k characters from dvi file to dtl file */
{
  int i;
  int ch;

  fprintf (dtl, " ");
  fprintf (dtl, "'");
  for (i=0; i < k; i++)
  {
    ch = fgetc (dvi);
    if (ch == ESC_CHAR || ch == EMES_CHAR)
    {
      fprintf (dtl, "%c", ESC_CHAR);
    }
    fprintf (dtl, "%c", ch);
  }
  fprintf (dtl, "'");
}
/* xferstring */

COUNT
special
#ifdef STDC
  (FILE * dvi,  FILE * dtl,  int n)
#else
  (dvi, dtl, n)
  FILE * dvi;
  FILE * dtl;
  int n;
#endif
/* read special 1 .. 4 from dvi and write in dtl */
/* return number of DVI bytes interpreted into DTL */
{
  U4  k;

  if (n < 1 || n > 4)
  {
    fprintf (stderr, "%s:  special %d, range is 1 to 4.\n", program, n);
    exit (1);
  }

  fprintf (dtl, "%s%d", SPECIAL, n);

  /* k[n] = length of special string */
  fprintf (dtl, " ");
  k = runsigned (n, dvi);
  fprintf (dtl, UF4, k);

  /* x[k] = special string */
  xferstring (k, dvi, dtl);

  return (1 + n + k);
}
/* end special */

COUNT
fontdef
#ifdef STDC
  (FILE * dvi,  FILE * dtl,  int n)
#else
  (dvi,  dtl,  n)
  FILE * dvi;
  FILE * dtl;
  int n;
#endif
/* read fontdef 1 .. 4 from dvi and write in dtl */
/* return number of DVI bytes interpreted into DTL */
{
  U4 ku, c, s, d, a, l;
  S4 ks;

  if (n < 1 || n > 4)
  {
    fprintf (stderr, "%s:  font def %d, range is 1 to 4.\n", program, n);
    exit (1);
  }

  fprintf (dtl, "%s%d", FONTDEF, n);

  /* k[n] = font number */
  fprintf (dtl, " ");
  if (n == 4)
  {
    ks = rsigned (n, dvi);
    fprintf (dtl, SF4, ks);
  }
  else
  {
    ku = runsigned (n, dvi);
    fprintf (dtl, UF4, ku);
  }

  /* c[4] = checksum */
  fprintf (dtl, " ");
  c = runsigned (4, dvi);
#ifdef HEX_CHECKSUM
  fprintf (dtl, XF4, c);
#else /* NOT HEX_CHECKSUM */
  /* write in octal, to allow quick comparison with tftopl's output */
  fprintf (dtl, OF4, c);
#endif

  /* s[4] = scale factor */
  fprintf (dtl, " ");
  s = runsigned (4, dvi);
  fprintf (dtl, UF4, s);

  /* d[4] = design size */
  fprintf (dtl, " ");
  d = runsigned (4, dvi);
  fprintf (dtl, UF4, d);

  /* a[1] = length of area (directory) name */
  a = runsigned (1, dvi);
  fprintf (dtl, " ");
  fprintf (dtl, UF4, a);

  /* l[1] = length of font name */
  l = runsigned (1, dvi);
  fprintf (dtl, " ");
  fprintf (dtl, UF4, l);

  /* n[a+l] = font pathname string => area (directory) + font */
  xferstring (a, dvi, dtl);
  xferstring (l, dvi, dtl);

  return (1 + n + 4 + 4 + 4 + 1 + 1 + a + l);
}
/* end fontdef */

COUNT
preamble
#ifdef STDC
  (FILE * dvi,  FILE * dtl)
#else
  (dvi,  dtl)
  FILE * dvi;
  FILE * dtl;
#endif
/* read preamble from dvi and write in dtl */
/* return number of DVI bytes interpreted into DTL */
{
  U4 id, num, den, mag, k;

  fprintf (dtl, "pre");

  /* i[1] = DVI format identification */
  fprintf (dtl, " ");
  id = runsigned (1, dvi);
  fprintf (dtl, UF4, id);

  /* num[4] = numerator of DVI unit */
  fprintf (dtl, " ");
  num = runsigned (4, dvi);
  fprintf (dtl, UF4, num);

  /* den[4] = denominator of DVI unit */
  fprintf (dtl, " ");
  den = runsigned (4, dvi);
  fprintf (dtl, UF4, den);

  /* mag[4] = 1000 x magnification */
  fprintf (dtl, " ");
  mag = runsigned (4, dvi);
  fprintf (dtl, UF4, mag);

  /* k[1] = length of comment */
  fprintf (dtl, " ");
  k = runsigned (1, dvi);
  fprintf (dtl, UF4, k);

  /* x[k] = comment string */
  xferstring (k, dvi, dtl);

  return (1 + 1 + 4 + 4 + 4 + 1 + k);
}
/* end preamble */

COUNT
postamble
#ifdef STDC
  (FILE * dvi,  FILE * dtl)
#else
  (dvi,  dtl)
  FILE * dvi;
  FILE * dtl;
#endif
/* read postamble from dvi and write in dtl */
/* return number of bytes */
{
  U4 p, num, den, mag, l, u, s, t;

  fprintf (dtl, "post");

  /* p[4] = pointer to final bop */
  fprintf (dtl, " ");
  p = runsigned (4, dvi);
  fprintf (dtl, UF4, p);

  /* num[4] = numerator of DVI unit */
  fprintf (dtl, " ");
  num = runsigned (4, dvi);
  fprintf (dtl, UF4, num);

  /* den[4] = denominator of DVI unit */
  fprintf (dtl, " ");
  den = runsigned (4, dvi);
  fprintf (dtl, UF4, den);

  /* mag[4] = 1000 x magnification */
  fprintf (dtl, " ");
  mag = runsigned (4, dvi);
  fprintf (dtl, UF4, mag);

  /* l[4] = height + depth of tallest page */
  fprintf (dtl, " ");
  l = runsigned (4, dvi);
  fprintf (dtl, UF4, l);

  /* u[4] = width of widest page */
  fprintf (dtl, " ");
  u = runsigned (4, dvi);
  fprintf (dtl, UF4, u);

  /* s[2] = maximum stack depth */
  fprintf (dtl, " ");
  s = runsigned (2, dvi);
  fprintf (dtl, UF4, s);

  /* t[2] = total number of pages (bop commands) */
  fprintf (dtl, " ");
  t = runsigned (2, dvi);
  fprintf (dtl, UF4, t);

/*  return (29);  */
  return (1 + 4 + 4 + 4 + 4 + 4 + 4 + 2 + 2);
}
/* end postamble */

COUNT
postpost
#ifdef STDC
  (FILE * dvi,  FILE * dtl)
#else
  (dvi,  dtl)
  FILE * dvi;
  FILE * dtl;
#endif
/* read post_post from dvi and write in dtl */
/* return number of bytes */
{
  U4 q, id;
  int b223;  /* hope this is 8-bit clean */
  int n223;  /* number of "223" bytes in final padding */

  fprintf (dtl, "post_post");

  /* q[4] = pointer to post command */
  fprintf (dtl, " ");
  q = runsigned (4, dvi);
  fprintf (dtl, UF4, q);

  /* i[1] = DVI identification byte */
  fprintf (dtl, " ");
  id = runsigned (1, dvi);
  fprintf (dtl, UF4, id);

  /* final padding by "223" bytes */
  /* hope this way of obtaining b223 is 8-bit clean */
  for (n223 = 0; (b223 = fgetc (dvi)) == 223; n223++)
  {
    fprintf (dtl, " ");
    fprintf (dtl, "%d", 223);
  }
  if (n223 < 4)
  {
    fprintf (stderr,
      "%s:  bad post_post:  fewer than four \"223\" bytes.\n", program);
    exit (1);
  }
  if (b223 != EOF)
  {
    fprintf (stderr,
      "%s:  bad post_post:  doesn't end with a \"223\".\n", program);
    exit (1);
  }

  return (1 + 4 + 1 + n223);
}
/* end postpost */

/* end of "dv2dt.c" */
