/* dvi.c The dvi reader for dvi printers.
 * Copyright 1985 Massachusetts Institute of Technology.
 * Author: cjl@oz
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.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];
long start_page = -1000000;
long end_page = 1000000;
long usermag;
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();
  dev_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 = s * globalmag / d;
  debug("fntdef: num= %d area=\"%s\" name=\"%s\" mag=%d s=%d\n",
	fontnum,area,name,mag,s);
  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. */
  if (usermag) globalmag = usermag; else 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 usermag=%d\n",
	dvinum,dviden,dvimag,usermag);
  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);
  if (start_page <= c[0] && c[0] <= end_page) {
    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]);
  } else noprint_page(c,p);
}

/* 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);
  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();
}

file_term()
{
  f_term();
  close_ef();
  dev_term();
}

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); 
  }
}

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 '8':
      fontopts |= F_INIT_QMS800;
      break;

    case 'd':
      debugging++;
      break;

    case 'f':
      start_page = atoi((argv[i][2])? argv[i]+2 : argv[++i]);
      break;

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

    case 'l':
      devopts |= DEV_INIT_LANDSCAPE;
      fontopts |= F_INIT_LANDSCAPE;
      break;

    case 'm':
      usermag = atoi((argv[i][2])? argv[i]+2 : argv[++i]);
      break;

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

    case 'o':
      if (f = fopen(argv[++i],"w")) {
	if (out != stdout) (void) fclose(out);
	out = f; outfname = argv[i];
	f = NULL;
      }
      break;
      
    case 't':
       end_page = atoi((argv[i][2])? argv[i]+2 : argv[++i]);
      break;

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

    default:
      croak("bad switch: %s",argv[i]);
      break;
  } else {
    nfiles++;
    if (f = fopen(argv[i],"r")) {
      in = f; infname = argv[i];
      dvi_file();
      (void) fclose(f);
    } else croak("couldn't open %s",argv[i]);
  }
  /* If I'm a lpd filter, see if I'm for a qms800 */
  if (user && host && (out == stdout) && !nfiles && !access("qms800",0))
    fontopts |= F_INIT_QMS800;
  if (!nfiles) process_stdin();
  exit(0);
}