/* dvi.c The dvi reader for dvi printers.
 * Copyright 1985 Massachusetts Institute of Technology.
 * Author: cjl@oz
 * Slightly changed by Kees Straatman (mcvax!guvaxin!kees) for Kyocera driver
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include "fonts.h"
#include "dev.h"
#include "util.h"
#include "dvi.h"

extern char *rindex();
extern char *mktemp();
extern long ftell();

#define DVI_STACK_SIZE      512 /* Maximum dvi stack size supported. */
#define NFONTDIR_MAX         16
#define MAXDRIFT              2
#ifndef PXLDIR
#define PXLDIR "/usr/lib/tex/pxlfonts"
#endif

/* Runtime parameters specified by user
 */
FILE *out = stdout;
FILE *in;
char *dirvec[NFONTDIR_MAX];
char *acctfile = (char *)0;
int  npages = 0;
int dirveclen;
unsigned long devopts,fontopts;

/* Runtime parameters specified by the dvi file & the user
 */
char pre_comment[257];
long numerator,denominator,half_denominator,dvinum,dviden,dvimag;
long globalmag;


/* Runtime data structures
 */
char stdoutbuf[BUFSIZ];
long h_pix,v_pix,h,v,w,x,y,z,device_dpi,fntspc;
unsigned long fntvec[512],chrvec[512][4];
int stackptr,fntveclen;
long hstack[DVI_STACK_SIZE];
long h_pixstack[DVI_STACK_SIZE];
long vstack[DVI_STACK_SIZE];
long v_pixstack[DVI_STACK_SIZE];
long wstack[DVI_STACK_SIZE];
long xstack[DVI_STACK_SIZE];
long ystack[DVI_STACK_SIZE];
long zstack[DVI_STACK_SIZE];

/* Stack manipulation: push and pop
 */

push()
{
  if (stackptr >= DVI_STACK_SIZE) croak("dvi stack overflow");
  hstack[stackptr] = h;
  h_pixstack[stackptr] = h_pix;
  vstack[stackptr] = v;
  v_pixstack[stackptr] = v_pix;
  wstack[stackptr] = w;
  xstack[stackptr] = x;
  ystack[stackptr] = y;
  zstack[stackptr] = z;
  stackptr++;
}

pop()
{
  if (stackptr <= 0) croak("dvi stack underflow");
  stackptr--;
  h = hstack[stackptr];
  h_pix = h_pixstack[stackptr];
  v = vstack[stackptr];
  v_pix = v_pixstack[stackptr];
  w = wstack[stackptr];
  x = xstack[stackptr];
  y = ystack[stackptr];
  z = zstack[stackptr];
}

position()
{
  register int dh = h_pix - (h * numerator + half_denominator) / denominator;
  register int dv = v_pix - (v * numerator + half_denominator) / denominator;

  if (dh > MAXDRIFT) h_pix -= MAXDRIFT;
  else if (dh < -MAXDRIFT) h_pix += MAXDRIFT;
  if (dv > MAXDRIFT) v_pix -= MAXDRIFT;
  else if (dv < -MAXDRIFT) v_pix += MAXDRIFT;
  dev_position(h_pix,v_pix);
}

/* Drawing: set and setrule
 */
set(ch,movep)
     unsigned long ch;
     int movep;
{
  long texwidth,devwidth;

  f_use_char(ch,&texwidth,&devwidth);
  position();
  f_setc(ch,devwidth);
  if (movep) {
    h += texwidth;
    h_pix += devwidth;
  }
}

setrule(movep)
     int movep;
{
  long a = sget4(in);
  long b = sget4(in);
  long a_pix,b_pix;

  if (a > fntspc || a < -fntspc)
    a_pix = ((v + a) * numerator + half_denominator) / denominator - v_pix;
  else if (a < 0)
    a_pix = - ((-a * numerator + half_denominator) / denominator);
  else
    a_pix = (a * numerator + half_denominator) / denominator;
  if (b > fntspc || b < -fntspc)
    b_pix = ((h + b) * numerator + half_denominator) / denominator - h_pix;
  else if (b < 0)
    b_pix = - ((-b * numerator + half_denominator) / denominator);
  else
    b_pix = (b * numerator + half_denominator) / denominator;
  if (a_pix > 0 && b_pix > 0) {
    position();
    dev_draw_box(a_pix,b_pix);
  }
  if (movep) {
    h += b;
    h_pix += b_pix;
  }
}

/* Movement: right and down
 */

right(dx)
     long dx;
{
  h += dx;
  if (dx > fntspc || dx < -fntspc * 4)
    h_pix = (h * numerator + half_denominator) / denominator;
  else if (dx < 0)
    h_pix -= (-dx * numerator + half_denominator) / denominator;
  else
    h_pix += (dx * numerator + half_denominator) / denominator;
}

down(dy)
     long dy;
{
  v += dy;
  if (dy > fntspc * 5)
    v_pix = (v * numerator + half_denominator) / denominator;
  else if (dy < 0)
    v_pix -= (-dy * numerator + half_denominator) / denominator;
  else
    v_pix += (dy * numerator + half_denominator) / denominator;
}


/* Special: xxx
 */
xxx(k)
     long k;
{
  position();
  dev_special(k,in);
}


/* Fonts: fntdef
 */
fntdef(fontnum)
     unsigned long fontnum;
{
  unsigned long tfmchecksum = get4(in);
  long s = sget4(in);
  long d = sget4(in);
  int a = get1(in);
  int l = get1(in);
  int i;
  char area[257];
  char name[257];
  long mag;

  for (i = 0; i < a; i++) area[i] = getc(in); area[a] = 0;
  for (i = 0; i < l; i++) name[i] = getc(in); name[l] = 0;
  mag = (long) (((double) s) * ((double) globalmag) / ((double) d));
  debug("fntdef: num= %d area=\"%s\" name=\"%s\" mag=%d s=%d d=%d\n",
	fontnum,area,name,mag,s,d);
  f_define_font(fontnum,0,area,name,mag,s,tfmchecksum);
}

/* Fonts: fntdef
 */
nullfntdef(fontnum)
     unsigned long fontnum;
{
  register int a,l,i;

  (void) getc(in); (void) getc(in); (void) getc(in); (void) getc(in);
  (void) getc(in); (void) getc(in); (void) getc(in); (void) getc(in);
  (void) getc(in); (void) getc(in); (void) getc(in); (void) getc(in);
  a = getc(in);
  l = getc(in);
  for (i = 0; i < a; i++) (void) getc(in);
  for (i = 0; i < l; i++) (void) getc(in);
}

/* Record this font number on fntvec
 * We'll later pass fntvec to the fonts module
 */
int fntmark(fnt)
     unsigned long fnt;
{
  register int i;

  for (i = 0; i < fntveclen; i++) if (fntvec[i] == fnt) return(i);
  if (fntveclen >= sizeof(fntvec)) croak("too many fonts");
  chrvec[fntveclen][0] = 0;
  chrvec[fntveclen][1] = 0;
  chrvec[fntveclen][2] = 0;
  chrvec[fntveclen][3] = 0;
  fntvec[fntveclen] = fnt;
  return(fntveclen++);
}

chrmark(ch,i)
     unsigned long ch;
     int i;
{
  chrvec[i][ch/32] |= (1 << (ch % 32));
}

/* End of page processing: eop
 */
eop()
{
  dev_eop();
}

noprint_page(c,p)
     long c[10];
     long p;
{
  int f;
  register int ch;

  (void) c; (void) p;
  fntveclen = 0;
  for (;;) {
    if (ferror(in)) croak("noprint_page dvi input");
    switch (ch = getc(in)) {
    case SETRULE:
    case PUTRULE:
      (void) getc(in);
      (void) getc(in);
      (void) getc(in);
      (void) getc(in);

    case RIGHT4:
    case W4:
    case DOWN4:
    case X4:
    case Y4:
    case Z4:
      (void) getc(in);

    case RIGHT3:
    case W3:
    case X3:
    case DOWN3:
    case Y3:
    case Z3:
      (void) getc(in);

    case RIGHT2:
    case W2:
    case X2:
    case DOWN2:
    case Y2:
    case Z2:
      (void) getc(in);

    case RIGHT1:
    case W1:
    case X1:
    case DOWN1:
    case Y1:
    case Z1:
      (void) getc(in);

    case NOP:
    case PUSH:
    case POP:
    case W0:
    case X0:
    case Y0:
    case Z0:
      break;

    case EOP:
      return;


    case SET1:
    case PUT1:          chrmark(get1(in),f);    break;
    case SET2:
    case PUT2:          chrmark(get2(in),f);    break;
    case SET3:
    case PUT3:          chrmark(get3(in),f);    break;
    case SET4:
    case PUT4:          chrmark(get4(in),f);    break;
    case FNT1:          f = fntmark(get1(in));  break;
    case FNT2:          f = fntmark(get2(in));  break;
    case FNT3:          f = fntmark(get3(in));  break;
    case FNT4:          f = fntmark(get4(in));  break;
    case FNTDEF1:       nullfntdef(get1(in));   break;
    case FNTDEF2:       nullfntdef(get2(in));   break;
    case FNTDEF3:       nullfntdef(get3(in));   break;
    case FNTDEF4:       nullfntdef(get4(in));   break;
    case XXX1:          swallow(get1(in),in);   break;
    case XXX2:          swallow(get2(in),in);   break;
    case XXX3:          swallow(get3(in),in);   break;
    case XXX4:          swallow(get4(in),in);   break;

    case EOF:
      croak("noprint_page EOF between BOP and EOP");
      break;

    default:
      if (ch >= SETCHAR0 && ch <= SETCHAR127)
	chrmark((unsigned long) (ch - SETCHAR0),f);
      else if (ch >= FNTNUM0 && ch <= FNTNUM63)
	f = fntmark((unsigned long) (ch - FNTNUM0));
      else croak("noprint_page dvi command %d between BOP and EOP",ch);
      break;
    }
  }
}

print_page(c,p)
     long c[10];
     long p;
{
  long ch;

  (void) p;
  stackptr = 0;
  h = v = w = x = y = z = h_pix = v_pix = 0;
  position();
  for (;;) {
    if (ferror(in)) croak("print_page dvi input");
    switch (ch = getc(in)) {
    case EOF:
      croak("print_page EOF between BOP and EOP");
      break;

    case SET1:          set(get1(in),1);        break;
    case SET2:          set(get2(in),1);        break;
    case SET3:          set(get3(in),1);        break;
    case SET4:          set(get4(in),1);        break;
    case SETRULE:       setrule(1);             break;
    case PUT1:          set(get1(in),0);        break;
    case PUT2:          set(get2(in),0);        break;
    case PUT3:          set(get3(in),0);        break;
    case PUT4:          set(get4(in),0);        break;
    case PUTRULE:       setrule(0);             break;
    case NOP:                                   break;
    case EOP:           eop();                  return;
    case PUSH:          push();                 break;
    case POP:           pop();                  break;
    case RIGHT1:        right(sget1(in));       break;
    case RIGHT2:        right(sget2(in));       break;
    case RIGHT3:        right(sget3(in));       break;
    case RIGHT4:        right(sget4(in));       break;
    case W0:            right(w);               break;
    case W1:            right(w = sget1(in));   break;
    case W2:            right(w = sget2(in));   break;
    case W3:            right(w = sget3(in));   break;
    case W4:            right(w = sget4(in));   break;
    case X0:            right(x);               break;
    case X1:            right(x = sget1(in));   break;
    case X2:            right(x = sget2(in));   break;
    case X3:            right(x = sget3(in));   break;
    case X4:            right(x = sget4(in));   break;
    case DOWN1:         down(sget1(in));        break;
    case DOWN2:         down(sget2(in));        break;
    case DOWN3:         down(sget3(in));        break;
    case DOWN4:         down(sget4(in));        break;
    case Y0:            down(y);                break;
    case Y1:            down(y = sget1(in));    break;
    case Y2:            down(y = sget2(in));    break;
    case Y3:            down(y = sget3(in));    break;
    case Y4:            down(y = sget4(in));    break;
    case Z0:            down(z);                break;
    case Z1:            down(z = sget1(in));    break;
    case Z2:            down(z = sget2(in));    break;
    case Z3:            down(z = sget3(in));    break;
    case Z4:            down(z = sget4(in));    break;
    case FNT1:          f_use_font(get1(in),&fntspc);   break;
    case FNT2:          f_use_font(get2(in),&fntspc);   break;
    case FNT3:          f_use_font(get3(in),&fntspc);   break;
    case FNT4:          f_use_font(get4(in),&fntspc);   break;
    case FNTDEF1:       nullfntdef(get1(in));   break;
    case FNTDEF2:       nullfntdef(get2(in));   break;
    case FNTDEF3:       nullfntdef(get3(in));   break;
    case FNTDEF4:       nullfntdef(get4(in));   break;
    case XXX1:          xxx(get1(in));          break;
    case XXX2:          xxx(get2(in));          break;
    case XXX3:          xxx(get3(in));          break;
    case XXX4:          xxx(get4(in));          break;

    default:
      if (ch >= SETCHAR0 && ch <= SETCHAR127)
	set((unsigned long) (ch - SETCHAR0),1);
      else if (ch >= FNTNUM0 && ch <= FNTNUM63)
	f_use_font((unsigned long) (ch - FNTNUM0),&fntspc);
      else croak("print_page dvi command %d between BOP and EOP",ch);
      break;
    }
  }
}

/* Preamble processing.
 */
computescale()
{
  double dd;

  /* Compute scale factors for this file. */
  globalmag = dvimag;

  /* This is real hokey, but will probably work for TEX82. */
  if (dvinum != 25400000 || dviden != 473628672) {
    fprintf(stderr,"DVI file numerator and denominator weren't exactly ");
    fprintf(stderr,"what was expected.\nScaling may be wrong.\n");
  }
  numerator = ((long) (((double) dvinum)
		       / ((double) 254000)
		       / ((double) 10)));
  denominator = ((long) (((double) dviden)
			 / ((double) globalmag)
			 * ((double) 1000)
			 / ((double) device_dpi)
			 / ((double) 10)));
  half_denominator = denominator / 2;
  debug("Computescale: globalmag=%d numerator=%d denominator=%d\n",
	globalmag,numerator,denominator);

}


preamble()
{
  int i = getc(in);
  int k;

  dvinum = get4(in);
  dviden = get4(in);
  dvimag = get4(in);
  k = get1(in);
  if (i != 2) croak("id_byte=%d, probably not a dvi file",i);
  for (i = 0; i < k; i++) pre_comment[i] = getc(in);
  pre_comment[k] = '\0';
  computescale();
  debug("Preamble: dvinum=%d dviden=%d dvimag=%d\n",
	dvinum,dviden,dvimag);
  debug("          comment: \"%s\"\n",pre_comment);
}

/* Beginning of page processing
 */
bop()
{
  int i;
  long c[10];
  long p,fpos;

  for (i = 0; i < 10; i++) c[i] = sget4(in);
  p = sget4(in);
  debug("first pass of page %d\n",c[0]);
  fpos = ftell(in);           /* Remember where we are */
  noprint_page(c,p);          /* First pass noprint to get fntvec */
  /* Tell font module about the fonts */
  f_newpage(fntvec,chrvec,fntveclen);
  debug("second pass of page %d\n",c[0]);
  fseek(in,fpos,0);           /* Back up */
  print_page(c,p);            /* And finally do the page */
  debug("done with page %d\n",c[0]);
  npages++;
}

file_term()
{
  f_term();
  close_ef();
  dev_term();
}

/* Postamble processing.
 */

find_postamble()
{
  int ch;
  long i = -4;
  long q;

  do (void) fseek(in,i--,2); while ((ch = getc(in)) == TRAILER);
  if (ch != 2) croak("postamble id_byte=%d, probably not a dvi file",ch);
  (void) fseek(in,i-4,2);
  if ((ch = getc(in)) != POSTPOST) croak("no POSTPOST where expected");
  q = sget4(in);
  (void) fseek(in,q,0);
  if ((ch = getc(in)) != POST) croak("no POST where expected");
}


postamble()
{
  int ch;
  long p = sget4(in);
  unsigned long l,u,maxstackdepth,npages;

  dvinum = get4(in);
  dviden = get4(in);
  dvimag = get4(in);
  computescale();
  l = get4(in);
  u = get4(in);
  maxstackdepth = get2(in);
  npages = get2(in);
  debug("Postamble: dvinum=%d dviden=%d dvimag=%d maxstackdepth=%d\n",
	dvinum,dviden,dvimag,maxstackdepth);
  debug("           npages=%d\n",npages);
  if (maxstackdepth > DVI_STACK_SIZE)
    croak("dvi file has too much stack depth");
  f_init(out,pgmnam,dirvec,dirveclen,numerator
	 ,denominator,globalmag,fontopts);
  signal(SIGINT, file_term);
  for (;;) {
    if (ferror(in)) croak("postamble input");
    switch (ch = getc(in)) {
    case EOF:
      croak("EOF found before POSTPOST");
      break;

    case FNTDEF1:       fntdef(get1(in));       break;
    case FNTDEF2:       fntdef(get2(in));       break;
    case FNTDEF3:       fntdef(get3(in));       break;
    case FNTDEF4:       fntdef(get4(in));       break;
    case NOP:                                   break;
    case POSTPOST:                              return;

    default:
      croak("dvi command %d in postamble",ch);
      break;
    }
  }
}

/* Process a dvi file
 */
file_init()
{
  int ch;

  if ((ch = getc(in)) != PRE)
    croak("first char %d not PRE, probably not a dvi file",ch);
  find_postamble();
  dev_init(out,devopts,&device_dpi);
  open_ef();
  postamble();
  (void) fseek(in,1L,0);
  preamble();
}

dvi_file()
{
  int ch;

  if (!dirveclen) dirvec[dirveclen++] = PXLDIR;
  file_init();
  for (;;) {
    if (ferror(in)) croak("dvi_file input");
    switch (ch = getc(in)) {
    case EOF:
      croak("EOF found between pages");
      break;

    case BOP:           bop();                  break;
    case POST:          file_term();            return;
    case FNTDEF1:       fntdef(get1(in));       break;
    case FNTDEF2:       fntdef(get2(in));       break;
    case FNTDEF3:       fntdef(get3(in));       break;
    case FNTDEF4:       fntdef(get4(in));       break;
    case NOP:                                   break;

    default:
      croak("dvi command %d between pages",ch);
      break;
    }
  }
}

process_stdin()
{
  int ch;
  FILE *tf;
  char tfn[257];
  struct stat st;

  if (fstat(fileno(stdin),&st) < 0) croak("can't stat stdin");
  if ((st.st_mode & S_IFMT) == S_IFREG) {
    in = stdin; infname = "stdin";
    dvi_file();
    in = NULL; infname = NULL;
  } else {
    /* Copy the dvi data to a temp file, since we have to do disk seeks. */
    (void) sprintf(tfn,"/usr/tmp/%s.XXXXXX",pgmnam);
    if (!(tf = fopen(mktemp(tfn),"w"))) croak("couldn't open %s",tfn);
    unlink(tfn);
    while (!ferror(stdin) && (ch = getchar()) != EOF) putc(ch,tf);
    if (ferror(stdin)) croak("error on stdin");
    (void) fseek(tf,0L,0);
    infname = tfn; in = tf;
    dvi_file();
    in = NULL; infname = NULL;
    (void) fclose(tf);
  }
}

account()
{
  FILE *acctf;

  if (user && acctfile && access(acctfile, 02) >= 0 &&
      ((acctf = fopen(acctfile, "a")) != NULL)) {
    fprintf(acctf, "%-5d\t%s:%s\n", npages, host, user);
    fclose(acctf);
  }
}

main(argc,argv)
     int argc;
     char *argv[];
{
  FILE *f;
  int nfiles = 0;
  int i;

  setbuf(stdout,stdoutbuf);
  pgmnam = argv[0];
  fontopts = 0;
  devopts = 0;


  for (i = 1; i < argc; i++)
    if (argv[i][0] == '-') switch(argv[i][1]) {

    case 'd':
      debugging++;
      break;

    case 'h':
      host = argv[++i];
      break;

    case 'n':
      user = argv[++i];
      break;

    case 'x':
    case 'y':
      break;

    default:
      croak("bad switch: %s",argv[i]);
      break;
  } else
    acctfile  = argv[i];

  process_stdin();
  account();
  exit(0);
}