/*
 * Copyright (c) 1987 University of Maryland Department of Computer Science.
 * All rights reserved.  Permission to copy for any purpose is hereby granted
 * so long as this copyright notice remains intact.
 */

#ifndef lint
static char rcsid[] = "$Header$";
#endif

/*
 * DVI to Interpress driver
 *
 * Reads DVI version 2 files and converts to Xerox Interpress format.
 */

#include "types.h"
#include "dvi.h"
#include "dviclass.h"
#include "dvicodes.h"
#include "fio.h"
#include "pxl.h"
#include "search.h"
#include "interpress.h"
#include <stdio.h>

char  *ProgName;
extern int   errno;
extern char *optarg;
extern int   optind;

/* Globals */
char	serrbuf[BUFSIZ];	/* buffer for stderr */

/* DVI style arithmetic:  when moving horizontally by a DVI distance >=
   ``space'', we are to recompute horizontal position from DVI units;
   otherwise, we are to use device resolution units to keep track of
   horizontal position.  A similar scheme must be used for vertical
   positioning. */
struct fontinfo {
    struct pxltail *px;		/* pxl file info */
    int     ipfont;		/* Interpress font index */
    i32     pspace;		/* boundary between ``small'' & ``large''
				   spaces (for positive horizontal motion) */
    i32     nspace;		/* -4 * pspace, for negative motion */
    i32     vspace;		/* 5 * pspace, for vertical motion */
    int     cwidth[128];	/* width (in DEVs) of each char */
    char    cload[128];		/* flag for ``char loaded into Imagen'' */
};

int     MaxDrift;		/* the maximum allowable difference between
				   hh and SPtoDEV(dvi_h), and vv and.... */

struct search *FontFinder;	/* search table for DVI index => fontinfo */
struct fontinfo *CurrentFont;	/* the current font (if any) */
int	NextIPFont;		/* during font definition, the next ip font
				   index */
int	FontErrors;		/* true => error(s) during font definition */

char   *TeXfonts;		/* getenv("TEXFONTS") */

int     ExpectBOP;		/* true => BOP ok */
int     ExpectEOP;		/* true => EOP ok */

int	DPI;			/* -d => device resolution (dots/inch) */
/* int  LFlag;			/* -l => landscape mode (eventually...) */
int     SFlag;			/* -s => silent (no page #s) */
int     XFlag;			/* -x => debug (undocumented) */

int     hh;			/* current horizontal position, in DEVs */
int     vv;			/* current vertical position, in DEVs */

/* Similar to dvi_stack, but includes ``hh'' and ``vv'', which are usually
   but not always the same as SPtoDEV(h) and SPtoDEV(v): */
struct localstack {
    int     stack_hh;
    int     stack_vv;
    struct dvi_stack    stack_dvi;
};

struct localstack  *dvi_stack;	/* base of stack */
struct localstack  *dvi_stackp;	/* current place in stack */

int	HHMargin;		/* horizontal margin (in DEVs) */
int	VVMargin;		/* vertical margin (in DEVs) */

int	Numerator;		/* numerator from DVI file */
int	Denominator;		/* denominator from DVI file */
int	DVIMag;			/* magnification from DVI file */

double	UserMag;		/* user specified magnification */
double	GlobalMag;		/* overall magnification (UserMag*DVIMag) */
double	conv;			/* conversion factor for magnified DVI units */

double	OneHalf = 0.5;		/* .5, so compiler can generate constant only
				   once */
double	Zero = 0.0;		/* likewise */
double	_d_;			/* Used to store intermediate results.  The
				   compiler should do this for us, but it's
				   too stupid. */

int	IpHH;			/* Interpress horizontal position */
int	IpVV;			/* Interpress vertical position */
int	IpFont;			/* Interpress current-font number */

char *getenv (), *malloc ();

/* Absolute value */
#define ABS(n) ((n) >= 0 ? (n) : -(n))

/* Round a floating point number to integer */
#define ROUND(f) ((int) (_d_ = (f), \
			 _d_ < Zero ? _d_ - OneHalf : _d_ + OneHalf)

/* Convert to floating point */
#define FLOAT(i) ((double) (i))

/* Convert a value in sp's to dev's, and vice versa */
#define SPtoDEV(sp)  (ROUND ((sp)  * conv))
#define DEVtoSP(dev) (ROUND ((dev) / conv))

/* Put a two-byte (word) value */
#define putword(w) (putchar ((w) >> 8), putchar (w))

/* Correct devpos (the virtual device position) to be within MaxDrift pixels
   of dvipos (the virtual DVI position). */
#define	FIXDRIFT(devpos, dvipos) \
	if (ABS ((devpos) - (dvipos)) <= MaxDrift); \
	else \
	    if ((devpos) < (dvipos)) \
		(devpos) = (dvipos) - MaxDrift; \
	    else \
		(devpos) = (dvipos) + MaxDrift

/* Compute the DEV widths of the characters in the given font */
ComputeCWidths (fi)
struct fontinfo *fi;
{
    register int    i;
    register struct chinfo *ch;
    register int   *cw;

    ch = fi -> px -> px_info;
    cw = fi -> cwidth;
    i = 128;
    while (--i >= 0) {
	*cw++ = SPtoDEV (ch -> ch_TFMwidth);
	ch++;
    }
}

SelectFont (n)
int n;
{
    register struct fontinfo *f;

    CurrentFont = f = FindFont ((i32) n, (struct fontinfo *) 0);
    CurrentFontIndex = f - FontInfo;
}

/* Start a page (process a DVI_BOP) */
/* NOTE: I'm depending on getting a BOP before any characters or rules on a
   page! */
BeginPage () {
    register int   *i;
    static int  count[10];	/* the 10 counters */
    static int  beenhere;

    if (!ExpectBOP)
	error (1, 0, "unexpected BOP");
    if (beenhere) {
	if (!SFlag)
	    putc (' ', stderr);
    }
    else
	beenhere++;

    dvi_stackp = dvi_stack;

    ExpectBOP = 0;
    ExpectEOP++;		/* set the new "expect" state */

    for (i = count; i < &count[sizeof count / sizeof *count]; i++)
	fGetLong (stdin, *i);
    fGetLong (stdin, i);	/* previous page pointer */

    if (!SFlag) {
	fprintf (stderr, "[%d", count[0]);
	(void) fflush (stderr);
    }

    putchar (imP_Page);		/* XXX */
    IpHH = 0;
    IpVV = 0;

    hh = HHMargin;
    vv = VVMargin;
    dvi_h = DEVtoSP (hh);
    dvi_v = DEVtoSP (vv);
    dvi_w = 0;
    dvi_x = 0;
    dvi_y = 0;
    dvi_z = 0;
}

/* End a page (process a DVI_EOP) */
EndPage () {
    if (!ExpectEOP)
	error (1, 0, "unexpected EOP");

    if (!SFlag) {
	putc (']', stderr);
	(void) fflush (stderr);
    }

    ExpectEOP = 0;
    ExpectBOP++;

    putchar (imP_EndPage);	/* XXX */
}

/* Begin XXX */
/* Store the relevant information from the DVI postamble, and set up
   various internal things. */
PosotAmbleHeader (p)
register struct PostAmbleInfo *p; {
    register int n;

    PrevPagePointer = p -> pai_PrevPagePointer;
    Numerator = p -> pai_Numerator;
    Denominator = p -> pai_Denominator;
    DVIMag = p -> pai_DVIMag;

 /* Here we sneakily correct for the actual device resolution, so that we can
    pretend that it's 200 dots per inch. */
    UserMag *= FLOAT (DPI) / 200.0;

    GlobalMag = DMagFactor (DVIMag) * UserMag;

 /* The conversion factor is figured as follows:  there are exactly n/d DVI
    units per decimicron, and 254000 decimicrons per inch, and 200 pixels per
    inch.  Then we have to adjust this by the stated magnification. */
    conv = (Numerator / 254000.0) * (200.0 / Denominator) * GlobalMag;

    n = p -> pai_DVIStackSize * sizeof *dvi_stack;
    dvi_stack = (struct localstack *) malloc ((unsigned) n);
    if ((dvi_stackp = dvi_stack) == 0)
	error (1, errno, "can't allocate %d DVI stack bytes", n);
    IntGlobalMag = ROUND (GlobalMag * 1000.0);
}

/* Handle one of the font definitions from the DVI postamble. */
PostAmbleFontDef (p)
register struct PostAmbleFont *p; {
    register struct fontinfo *f;
    register char *s;
    int def = S_CREATE | S_EXCL;

    f = (struct fontinfo *) SSearch (FontFinder, p -> paf_DVIFontIndex, &def);
    if (f == 0)
	if (def & S_COLL)
	    error (1, 0, "font %d already defined", p -> paf_DVIFontIndex);
	else
	    error (1, 0, "can't stash font %d (out of memory?)",
		    p -> paf_DVIFontIndex);

    f -> ipfont = NextIPFont++;
    s = GenPXLFileName (p -> paf_name, p -> paf_DVIMag,
	    p -> paf_DVIDesignSize, IntGlobalMag, TeXfonts);
    if ((f -> px = ReadPXLFile (s, 1)) == 0) {
	error (0, errno, "can't find font \"%s\"", s);
	FontErrors++;
	return;
    }
    if (p -> paf_DVIChecksum != f -> px -> px_checksum)
	    error (0, 0, "\
WARNING: width tables and raster tables have different\n\
\tchecksums for font \"%s\"\n\
\tPlease notify your TeX maintainer\n\
\t(TFM checksum = 0%o, PXL checksum = 0%o)",
		    s, dvi_checksum, f -> px -> px_checksum);

    ScaleTFMWidths (f -> px, dvi_mag);
    ComputeCWidths (f);
    f -> pspace = p -> paf_DVIMag / 6;	/* a three-unit ``thin space'' */
    f -> nspace = -4 * f -> pspace;
    f -> vspace = 5 * f -> pspace;
}

/* Read the postamble. */
ReadPostAmble () {
    static char s[2] = { 's', 0 };

    if ((FontFinder = SCreate (sizeof (struct fontinfo))) == 0)
	error (1, 0, "can't create FontFinder (out of memory?)");
    ScanPostAmble (stdin, PostAmbleHeader, PostAmbleFontDef);
    if (FontErrors)
	error (1, 0, "missing font%s prevent%s output (sorry)",
		FontErrors > 1 ? s : &s[1], FontErrors == 1 ? s : &s[1]);
}
/* End XXX */

/* Read the preamble and do a few sanity checks */
ReadPreAmble () {
    register int   n;

    rewind (stdin);
    if (GetByte (stdin) != Sign8 (DVI_PRE))
	error (1, 0, "missing PRE");
    if (GetByte (stdin) != Sign8 (DVI_VERSION))
	error (1, 0, "mismatched version numbers");
    if (GetLong (stdin) != Numerator)
	error (1, 0, "mismatched numerator");
    if (GetLong (stdin) != Denominator)
	error (1, 0, "mismatched denominator");
    if (GetLong (stdin) != DVIMag)
	error (1, 0, "mismatched \\magfactor");
    n = UnSign8 (GetByte (stdin));
    while (--n >= 0)
	(void) GetByte (stdin);
}

main (argc, argv)
int argc;
register char **argv;
{
    register int    c;
    char   *inname;

    setbuf (stderr, serrbuf);

    ProgName = *argv;
    UserMag = 1.0;
    MaxDrift = DefaultMaxDrift;
    DPI = DefaultDPI;
    inname = "stdin";

    while ((c = getopt (argc, argv, "d:m:r:s")) != EOF) {
	switch (c) {
	    case 'd':		/* max drift value */
		MaxDrift = atoi (optarg);
		break;
/*	    case 'l':		/* landscape mode */
/*		LFlag++; */
/*		break; */
	    case 'm':		/* magnification */
		UserMag = DMagFactor (atoi (optarg));
		break;
	    case 'r':		/* resolution */
		DPI = atoi (optarg);
		break;
	    case 's':		/* silent */
		SFlag++;
		break;
	    case 'x':		/* enable debugging */
		XFlag++;
		break;
	    case '?':
		fprintf (stderr, "\
Usage: %s [-d drift] [-m mag] [-s] [-r resolution] [file]\n",
			ProgName);
		(void) fflush (stderr);
		exit (1);
	}
    }
    if (optind < argc)
	if (freopen (inname = argv[optind], "r", stdin) == NULL)
	    error (1, errno, "can't open %s", inname);

 /* ReadPostAmble does an fseek which, if performed on a tty, tends to make
    the shell log one out, thus the following kludge: */
    if (isatty (fileno (stdin)))
	error (1, 0, "input from ttys is expressly forbidden!");

    TeXfonts = getenv ("TEXFONTS");
    if (TeXfonts == 0)
	TeXfonts = "";

    ReadPostAmble ();

 /* Margins -- needs work! */
    HHMargin = DefaultLeftMargin;/* XXX */
    VVMargin = DefaultTopMargin;/* XXX */

    ReadPreAmble ();
    ExpectBOP++;
    (void) fseek (stdin, PrevPagePointer, 0);

 /* All set! */
    printf ("Interpress/Xerox/2.1 ");
    PutInstructionsBody (inname);
    PutPreamble ();
    ReadDVIFile ();
 /* EOF is implicit (?) */

    exit (0);
}

/* Skip a font definition (since we are using those from the postamble) */
/* ARGSUSED */
SkipFontDef (font)
int font;
{
    register int i;

    (void) GetLong (stdin);
    (void) GetLong (stdin);
    (void) GetLong (stdin);
    i = UnSign8 (GetByte (stdin)) + UnSign8 (GetByte (stdin));
    while (--i >= 0)
	(void) GetByte (stdin);
}

/* Perform a \special - right now ignore all of them */
DoSpecial (len)
int len;			/* length of the \special string */
{
    error (0, 0, "warning: ignoring \\special");
    (void) fseek (stdin, (long) len, 1);
}

/* Draw a rule at the current (hh,vv) position.  There are two 4 byte
   parameters.  The first is the height of the rule, and the second is the
   width.  (hh,vv) is the lower left corner of the rule. */
SetRule (advance)
int advance;
{
    i32     rwidth,		/* rule width from DVI file */
	    rheight;		/* rule height from DVI file */
    register int    h,
		    w;

    fGetLong (stdin, rheight);
    fGetLong (stdin, rwidth);

 /* Rule sizes must be computed in this manner: */
    h = conv * rheight;
    if (FLOAT (h) < (conv * rheight))
	h++;
    w = conv * rwidth;
    if (FLOAT (w) < (conv * rwidth))
	w++;

 /* put the rule out */
    if (IpHH != hh || IpVV != vv)
	SetPosition (hh, vv);
    putchar (imP_Rule);		/* XXX */
    putword (w);		/* XXX */
    putword (h);		/* XXX */
    putword (-h + 1);		/* XXX */
    if (advance) {
	hh += w;
	dvi_h += rwidth;
	if (ABS (hh - (w = SPtoDEV (dvi_h))) > MaxDrift)
	    hh = w + (hh < w ? -MaxDrift : MaxDrift);
    }
}

/* FIXME (kerns, etc, etc) */
/* resume XXX */
/* This rather large routine reads the DVI file and calls on other routines
   to do anything moderately difficult (except put characters:  there is
   a bunch of ugly code with ``goto''s which makes things much faster) */
ReadDVIFile () {
    register int    c,
		    p;
    int    advance;

    IpFamily = -1;

 /* Only way out is via "return" statement */
    for (;;) {
    /* Get the DVI byte, and if it's a character, put it */
/*	c = UnSign8 (getchar ());  */
	c = getchar ();		/* getchar() returns unsigned values */

	if (DVI_IsChar (c)) {	/* I know, ugly, but ... no function call
				   overhead this way */
	    register struct chinfo *ch;
	    register struct fontinfo *cf;

set:
	    advance = 1;
put:
	    cf = CurrentFont;
	    ch = &cf -> px -> px_info[c];
	    if (ch -> ch_width == 0)
		goto ignore;	/* not a real character, just advance */
	/* BEGIN INLINE EXPANSION OF IpSetPosition (the things we do in
	   the name of efficency! ;-) ) */
	    if (ImHH != hh) {
		if (ImHH == hh - 1)
		    putchar (imP_Forw);
		else if (ImHH == hh + 1)
		    putchar (imP_Backw);
		else {
		    putchar (imP_SetHAbs);
		    putword (hh);
		}
		ImHH = hh;
	    }
	    if (ImVV != vv) {
		putchar (imP_SetVAbs);
		putword (vv);
		ImVV = vv;
	    }
	/* END INLINE EXPANSION OF ImSetPosition */
	    if (ImFamily != CurrentFontIndex) {
		putchar (imP_SetFamily);
		putchar (CurrentFontIndex);
		ImFamily = CurrentFontIndex;
	    }
	    putchar (c);
	    ImHH += cf -> cwidth[c];
ignore:
	    if (advance) {
#ifdef DEBUG
		if (XFlag) {
		    fprintf (stderr, "setchar%d h:=%d+%d=%d, hh:=%d\n",
			c, dvi_h - DEVtoSP (HHMargin), ch->ch_TFMwidth,
			dvi_h - DEVtoSP (HHMargin) + ch->ch_TFMwidth,
			hh + CurrentFont->cwidth[c] - HHMargin);
		    fflush (stderr);
		}
#endif DEBUG
		hh += cf -> cwidth[c];
		dvi_h += ch -> ch_TFMwidth;
		p = SPtoDEV (dvi_h);
		FIXDRIFT (hh, p);
	    }
	    continue;
	}
    /* Wasn't a character, maybe a font? */
	if (DVI_IsFont (c)) {
	    SelectFont ((i32) (c - DVI_FNTNUM0));
	    continue;
	}
    /* Wasn't a font, see if it's a generic one */
	if (p = DVI_OpLen (c)) {
	/* It's generic, get its parameter */
	    switch (p) {
		case 1:
		    p = Sign8 (getchar ());
		    break;
		case 2:
		    fGetWord (stdin, p);
		    p = Sign16 (p);
		    break;
		case 3:
		    fGet3Byte (stdin, p);
		    p = Sign24 (p);
		    break;
		case 4:
		    fGetLong (stdin, p);
		    break;
		case 5:
		    p = UnSign8 (getchar ());
		    break;
		case 6:
		    fGetWord (stdin, p);
		    p = UnSign16 (p);
		    break;
		case 7:
		    fGet3Byte (stdin, p);
		    p = UnSign24 (p);
		    break;
	    }
	/* Now that we have the parameter, perform the command */
	    switch (DVI_DT (c)) {
		case DT_SET:
		    c = p;
		    goto set;
		case DT_PUT:
		    c = p;
		    advance = 0;
		    goto put;
		case DT_RIGHT:
move_right:
		    dvi_h += p;
		/* DVItype tells us that we must round motions in this way:
		   ``When the horizontal motion is small, like a kern, hh
		   changes by rounding the kern; but when the motion is
		   large, hh changes by rounding the true position so that
		   accumulated rounding errors disappear.'' */
		    if (p >= CurrentFont -> pspace ||
			    p <= CurrentFont -> nspace)
			hh = SPtoDEV (dvi_h);
		    else {
			hh += SPtoDEV (p);
			p = SPtoDEV (dvi_h);
			FIXDRIFT (hh, p);
		    }
		    break;
		case DT_W:
		    dvi_w = p;
		    goto move_right;
		case DT_X:
		    dvi_x = p;
		    goto move_right;
		case DT_DOWN:
move_down:
		    dvi_v += p;
		/* ``Vertical motion is done similarly, but with the
		   threshold between ``small'' and ``large'' increased by a
		   factor of 5.  The idea is to make fractions like $1\over2$
		   round consistently, but to absorb accumulated rounding
		   errors in the baeline-skip moves.'' */
		    if (ABS (p) >= CurrentFont -> vspace)
			vv = SPtoDEV (dvi_v);
		    else {
			vv += SPtoDEV (p);
			p = SPtoDEV (dvi_v);
			FIXDRIFT (vv, p);
		    }
		    break;
		case DT_Y:
		    dvi_y = p;
		    goto move_down;
		case DT_Z:
		    dvi_z = p;
		    goto move_down;
		case DT_FNT:
		    SelectFont (p);
		    break;
		case DT_XXX:
		    DoSpecial (p);
		    break;
		case DT_FNTDEF:
		    SkipFontDef (p);
		    break;
#ifdef PARANOID
		default:
		    error (1, 0, "bad DVI_DT(%d): (%d)", c, DVI_DT (c));
#endif PARANOID
	    }
	    continue;
	}
    /* Wasn't a char, wasn't a generic command, just pick it out from the
       whole mess */
	switch (c) {
	    case DVI_SETRULE:
		SetRule (1);
		break;
	    case DVI_PUTRULE:
		SetRule (0);
		break;
	    case DVI_NOP:
		break;
	    case DVI_BOP:
		BeginPage ();
		break;
	    case DVI_EOP:
		EndPage ();
		if (PrevPagePointer == -1) {
		    if (!SFlag) {
			fprintf (stderr, "\n");
			(void) fflush (stderr);
		    }
		    return;
		}
		break;
	    case DVI_PUSH:
		dvi_stackp -> stack_hh = hh;
		dvi_stackp -> stack_vv = vv;
		dvi_stackp -> stack_dvi = dvi_current;
		dvi_stackp++;
		break;
	    case DVI_POP:
		dvi_stackp--;
		hh = dvi_stackp -> stack_hh;
		vv = dvi_stackp -> stack_vv;
		dvi_current = dvi_stackp -> stack_dvi;
		break;
	    case DVI_W0:
		p = dvi_w;
		goto move_right;
	    case DVI_X0:
		p = dvi_x;
		goto move_right;
	    case DVI_Y0:
		p = dvi_y;
		goto move_down;
	    case DVI_Z0:
		p = dvi_z;
		goto move_down;
	    case DVI_PRE:
		error (1, 0, "unexpected PRE");
	    case DVI_POST:	/* shouldn't get this, reading backwards */
		error (1, 0, "unexpected POST");
	    case DVI_POSTPOST:
		error (1, 0, "unexpected POSTPOST");
	    default:
		error (1, 0, "undefined DVI opcode (%d)", c);
	}
    }
}