/* DVI to BJ200 driver */

/*
 * Copyright 1994 Chris Smith
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * Note:
 * The GPL is the license under which I have extracted gs386.S
 * from Ghostscript and included it with this program.  The
 * 'Ghostscript GPL' referred to in that file is evidently
 * the ver.2 GPL, as seen in gs261/COPYING.  If Aladdin or Torek
 * has any problem with this redistribution I will happily adjust
 * things to suit, everyone else is referred to /dev/null.
 */

/* This program is based on a 1985 version of Chris Torek's imagen1
   modified by me (then) to support pk fonts and other stuff and (now)
   to drive a Bubblejet.  The original 1985 code bears no copyright notice,
   a later generation of that code, in mctex, bears the following notice.

   * Copyright (C) 1987, 1989 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.  

   Errors & omissions are mine. */

#include "types.h"
#include "dvi.h"
#include "dviclass.h"
#include "dvicodes.h"
#include "fio.h"
#include "postamble.h"
#include "pxl.h"
#include "pk.h"
#include "bj200.h"
#include "bj200b.h"

#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <getopt.h>

/* flags */

const struct option long_options[] =
{
  { "draft",		no_argument, 	 0, 'd' },
  { "landscape",	no_argument,	 0, 'l' },
  { "two-up",		no_argument,	 0, '2' },
  { "four-up",		no_argument,	 0, '4' },
  { "even",		no_argument,	 0, 'e' },
  { "odd",		no_argument,	 0, 'o' },
  { "paper-length",	required_argument, 0, 'V' },
  { "x-offset",		required_argument, 0, 'X' },
  { "y-offset",		required_argument, 0, 'Y' },
  { "magnification",	required_argument, 0, 'm' },
  { "silent",		no_argument,	 0, 's' },
  { 0 },
};

const char short_options[] = "dl24eoV:X:Y:m:s";


long *transpose ();
long *flip ();

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

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

#define ttyprint(fmt,x) do {\
    sprintf (ttybuf, fmt, x);\
    ttyout (ttybuf);} while (0)

char ttybuf[128];


/* 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. */

/* The exception that proves the rule is that hh and SPtoDEV(dvi_h) aren't
   allowed to get more than MaxDrift units apart. */

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

typedef int FONT_STATE;		/* avoid enum until cc changes */
#define UNSEEN 0		/* not loaded, no chars from the font yet */
#define BAD 1			/* missing or bad format */
#define RELOAD 2		/* flushed to make space */
#define LOADED 3		/* loaded */

struct fontinfo {
    struct pxltail *px;		/* pxl file info */
    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[256];	/* width (in DEVs) of each char */
    int	    hiFontIndex;	/* 0 or imagen family for chars 128-255 */
    FONT_STATE font_state;	/* UNSEEN if not loaded */
    char *font_name;		/* from postamble */
    i32 DVIChecksum,DVIScaledSize,DVIDesignSize;
    char    cload[256];		/* flag for ``char glyph loaded from file'' */
#ifdef notyet
    int     UseTime;		/* cache info: flush fonts on LRU basis */
#endif
};

static nextHiFont = 96;		/* hi fonts assigned 95, 94, ... */
static int  numfonts = 0;	/* ordinary fonts 0, 1, ... */

struct fontinfo FontInfo[NFONTS];/* the fonts themselves */
struct fontinfo *CurrentFont;	/* the current font (if any) */
int     CurrentFontIndex;	/* == CurrentFont - FontIndex */

char   *pkfonts;		/* getenv("PKFONTS") */

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

int	DPI;			/* xx -d => device resolution (dots/inch) */
int	DFlag;			/* -d => draft mode */
int	PFlag;			/* -p => no page reversal */
int     LFlag;			/* -l => landscape mode (eventually...) */
int     SFlag;			/* -s => silent (no page #s) */
int	EFlag;			/* -e => even pages only */
int	OFlag;			/* -o => odd pages only */
int	TwoFlag;		/* -2 => 2-up mode */
int	FourFlag;		/* -4 => 4-up mode, hah */
float	VFlag = 11.0;		/* -V => paper length, inches */

float	XOffset;
float	YOffset;		/* offsets for margins, inches */

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

int dev_w, dev_y;		/* w0 and y0 values, in DEVs */
				/* good values for SP (unit space) and 
				 * IL (interline distance) */

int n_pages;			/* page count of dvi file */

/* 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	XXMargin;		/* right half margin (in DEVs) */
int	YYMargin;		/* bottom half margin (in DEVs) */

int mid_side;			/* true if page is not last -- more pages
				   will be printed on the same side of paper */
int odd_last;			/* true if we should eject a leftover
				   odd side in the feeder at eof */

long	PrevPagePointer;	/* The previous page pointer from the DVI
				   file.  This allows us to read the file
				   backwards, which obviates the need for
				   page reversal (which isn't supported on
				   the 8/300). */
long cur_page;			/* beginning of current page */

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	fix_per_dvi;		/* conversion factor for 2**-20 point */

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	FontErrors;		/* true => error(s) occurred during font
				   definitions from DVI postamble */

extern char *getenv ();
extern void *malloc ();

static int position_page (int count[10]);
static long *draft_screen (long *pxlp, int pxlw, int pxlh);

/* 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 to the Imagen */
#define putword(w) (putchar ((w) >> 8), putchar (w))

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

/* Given a DVI index number, find the font table entry for it.  If 'define'
   is nonzero, make an entry for it. */
struct fontinfo *
FindFont (dviindex, define)
register i32 dviindex;
struct fontinfo *define;
{
    struct finder {
	i32     dvinum;		/* the DVI number */
	struct fontinfo *f;	/* the font info defined for it */
    };
    register struct finder *f;
    static struct finder    finder[NFONTS];

 /* If there are fonts defined, look around for the given font */
    if (numfonts) {
	register int    h,
			l,
			m;
	h = numfonts - 1;
	l = 0;
	while (l <= h) {
	    f = &finder[m = (l + h) >> 1];
	    if (f -> dvinum > dviindex)
		h = m - 1;
	    else if (f -> dvinum < dviindex)
		l = m + 1;
	    else {
		if (define)
		    error (1, 0, "font %d already defined", dviindex);
		return f -> f;
	    }
	}
	f = &finder[l];
    }
    else
	f = finder;

 /* f is now where the font should have been found, if anywhere */
    if (define) {
	register struct finder *f1 = &finder[numfonts++];
	register struct finder *f2;

	if (numfonts > nextHiFont)
	    error (1, 0, "too many fonts used (%d)", numfonts);
	while (f1 > f) {
	    *f1 = *(f2 = f1 - 1);
	    f1 = f2;
	}
	f -> dvinum = dviindex;
	f -> f = define;
	return 0;
    }
    error (1, 0, "font %d not in finder table!", dviindex);
 /* NOTREACHED */
}

/* 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 = 256;
    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 */

    if (!ExpectBOP)
	error (1, 0, "unexpected BOP");

    dvi_stackp = dvi_stack;

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

    /* return here if we decide to skip a page */
  again:

    /* read page numbers and prev page pointer */

    for (i = count; i < &count[sizeof count / sizeof *count]; i++)
      fGetLong (stdin, *i);
    cur_page = PrevPagePointer;
    fGetLong (stdin, PrevPagePointer);
    
    /* Figure out where to put this page on the paper */

    switch (position_page (count))
      {
	/* Normal position, top left */
      case 0:
	hh = HHMargin;
	vv = VVMargin;
	break;

	/* Top right */
      case 1:
	hh = XXMargin;
	vv = VVMargin;
	break;

	/* Bottom left */
      case 2:
	hh = HHMargin;
	vv = YYMargin;
	break;

	/* Bottom right */
      case 3:
	hh = XXMargin;
	vv = YYMargin;
	break;

	/* Skip it */
      case -1:
	if (PFlag)		/* going forward */
	  {
	    if (skip_page () == DVI_BOP) goto again;
	    return 0;
	  }
	else
	  {
	    if (PrevPagePointer == -1)
	      return 0;
	    fseek (stdin, PrevPagePointer, 0);
	    if (getchar () == DVI_BOP) goto again;
	    return 0;
	  }

      default:
	assert (0 && "bad case val");
      }

    if (!SFlag) ttyprint ("[%d]", count[0]);

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

    return 1;
}

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

  ExpectEOP = 0;
  ExpectBOP++;

  /* if the page just printed is not the last to go on this side, do not
     physically print yet. */

  if (! mid_side) {
    lp_endpage ();
    lp_beginpage (); }

  if (! PFlag)
    if (PrevPagePointer != -1)
      (void) fseek (stdin, PrevPagePointer, 0);
}

/* Read forward to the next BOP or POST */

skip_page () 
{
  for (;;) {
    int c = getchar ();
    int p;

    if (DVI_IsChar (c)) continue;
    if (DVI_IsFont (c)) continue;

    if (c == DVI_BOP || c == DVI_POST || c == EOF) return c;

    if (p = DVI_OpLen (c)) {
      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 = getchar ();
	break;
      case 6:
	fGetWord (stdin, p);
	p = UnSign16 (p);
	break;
      case 7:
	fGet3Byte (stdin, p);
	p = UnSign24 (p);
	break; }
	    
      switch (DVI_DT (c)) {
      case DT_XXX:
	(void) fseek (stdin, (long) p, 1);
	break;
      case DT_FNTDEF:
	SkipFontDef (p);
	break; }
      continue; }

    switch (c) {
    case DVI_SETRULE:
    case DVI_PUTRULE:
      fseek (stdin, 8, 1); }}
}




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

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

    GlobalMag = UserMag * DVIMag/1000.0;

    /* The conversion facture is figured as follows: there are exactly
     * n/d decimicrons per DVI unit, and 254000 decimicrons per inch, and
     * DPI pixels per inch.  Then we have to adjust this by the stated
     * magnification. */ 

    conv = (Numerator / 254000.0) * ((double) DPI / Denominator) * GlobalMag;

    /* Also need to convert DVI units to PXL design-size units, 2^-20 point */

    fix_per_dvi = (Numerator / 25400000.) * (7578058752. / Denominator);

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

/* Handle one of the font definitions from the DVI postamble. */
PostAmbleFontDef (p)
register struct PostAmbleFont *p; 
{
    register struct fontinfo *f = CurrentFont;

    (void) FindFont (p -> paf_DVIFontIndex, f);/* define it */

    f->font_state = UNSEEN;
    f->hiFontIndex = 0;
    f->font_name = malloc (1+strlen(p->paf_name));
    strcpy (f->font_name, p->paf_name);
    f->DVIScaledSize = p->paf_DVIScaledSize;
    f->DVIDesignSize = p->paf_DVIDesignSize;
    f->DVIChecksum = p->paf_DVIChecksum;

    CurrentFont++;
    CurrentFontIndex++;
}

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

    CurrentFont = FontInfo;
    ScanPostAmble (stdin, PostAmbleHeader, PostAmbleFontDef);
}

/* 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 = getchar ();
    while (--n >= 0)
	(void) getchar ();
}

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

    setbuf (stderr, serrbuf);

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

    while ((c = getopt_long_only (argc, argv,
				  short_options, long_options, &t)) != EOF) {
	switch (c) {
#if 0
	    case 'd':		/* max drift value */
		MaxDrift = atoi (optarg);
		break;
#else
	      case 'd':		/* draft mode */
		DFlag = 1;
		break;
#endif
	    case 'l':		/* landscape mode */
		LFlag++;
		break;
	    case 'm':		/* magnification */
		UserMag *= atof (optarg);
		break;
	    case 'e':		/* even pages only */
		EFlag++;
		OFlag = 0;
		PFlag = 1;
		break;
	    case 'o':		/* odd pages only */
		OFlag++;
		EFlag = 0;
		PFlag = 1;
		break;
	    case '2':		/* 2 up */
		TwoFlag = 1;
		FourFlag = 0;
		LFlag = 1;
		UserMag *= 0.7607257743; /* magstep -1.5 */
		break;
	    case '4':		/* 4 up */
		FourFlag = 1;
		TwoFlag = 0;
		LFlag = 0;
		UserMag *= 0.5282817877; /* magstep -3.5 */
		break;
#if 0
	    case 'r':		/* resolution */
		DPI = atoi (optarg);
		break;
	    case 'p':		/* no page reversal */
		pflag ^= 1;
		break;
#endif
	    case 's':		/* silent */
		SFlag++;
		break;
	    case 'x':		/* x offset, in 1/10 inch increments */
		XOffset = atof (optarg);
		break;
	    case 'y':		/* y offset */
		YOffset = atof (optarg);
		break;
	    case 'v':		/* paper length */
		VFlag = atof (optarg);
		break;
	    case '?':
		usage ();
	}
    }

    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!");

    pkfonts = getenv ("pkfonts");
    if (pkfonts == 0)
	pkfonts = "";

    ReadPostAmble ();

 /* margins -- needs work! */
    if (TwoFlag) {
      HHMargin = Default2UpLeftMargin + ROUND (XOffset * DPI);
      XXMargin = Default2UpCenterMargin + ROUND (XOffset * DPI);
      VVMargin = Default2UpTopMargin + ROUND (YOffset * DPI);
    } else if (FourFlag) {
      HHMargin = Default4UpLeftMargin + ROUND (XOffset * DPI);
      XXMargin = Default4UpHCenterMargin + ROUND (XOffset * DPI);
      VVMargin = Default4UpTopMargin + ROUND (YOffset * DPI);
      YYMargin = Default4UpVCenterMargin + ROUND (YOffset * DPI);
    } else {
      HHMargin = DefaultLeftMargin + ROUND (XOffset * DPI);
      VVMargin = DefaultTopMargin + ROUND (YOffset * DPI);
    }

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

 /* All set! */

    lp_init (LFlag, DPI, VFlag);
    lp_beginpage ();

    ReadDVIFile ();

    /* if file ended in the middle of a 2-up or 4-up page, print it */
    if (mid_side)
      lp_endpage ();

    /* if printing even sides, may need to eject a leftover odd side */
    if (odd_last)
      putchar (' '), putchar ('\f');

    if (!SFlag) {
	(void) fprintf (stderr, "\n");
	(void) fflush (stderr);
    }

    lp_eof ();

    exit (0);
}

usage ()
{
  fprintf (stderr, "Usage: dvibj [-sdeo24lVXYm] <in >out\n\
Flags (with default values)\n\
    -silent	don't print page numbers & noise on stderr\n\
    -draft	draft mode, use less ink\n\
    -even	even pages only\n\
    -odd	odd pages only\n\
    -two-up	two pages side by side, landscape\n\
    -four-up	four pages per side, really tiny\n\
    -landscape	one-up landscape\n\
    -paper-length 11.0	length of paper in the printer (inches)\n\
    -x-offset 0.0	shift output left or right (inches)\n\
    -y-offset 0.0	shift output up or down (inches)\n\
    -magnification 1.0	increase or decrease size\n");
  exit (1);
}


/* 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 = getchar() + getchar();
    while (--i >= 0)
	(void) getchar();
}

DoSpecial (len)
    int len;			/* length of the \special string */

{
  while (--len >= 0)
    getchar ();
  return;
}

/* 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 (w > 0 && h > 0)
      lp_drawrule (hh, vv, w, h, DFlag);

    if (advance) {
	hh += w;
	dvi_h += rwidth;
	w = SPtoDEV (dvi_h);
	FIXDRIFT (hh, w);
    }
}

/* 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;

 /* Only way out is via "return" statement */
    for (;;) {
    /* Get the DVI byte, and if it's a character, put it */
	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;
	    if (!cf->cload[c])
		LoadGlyph (c, cf);

	    ch = &cf -> px -> px_info[c];
	    if (ch->ch_raster != 0)
	      lp_drawchar (hh, vv, ch);

	    if (advance) {
	        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 (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 = 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;
		    dev_w = p * conv; /* w in DEVs, chopped */
		    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 baseline-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;
		    dev_y = SPtoDEV (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
	    }
	    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:
		if (!BeginPage ()) return;
		break;
	    case DVI_EOP:
		EndPage ();
		if (!PFlag && PrevPagePointer == -1)
		    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:
		if (PFlag)
		    return;
		error (1, 0, "unexpected POST");
	    case DVI_POSTPOST:
		error (1, 0, "unexpected POSTPOST");
	    default:
		error (1, 0, "undefined DVI opcode (%d)", c);
	}
    }
}

#define RoundUp(n,r) (((n) + ((r) - 1)) & ~((r) - 1))

LoadGlyph (c, cf)
int c;				/* char */
register struct fontinfo *cf;	/* font */

/*
 *  Load the character c of font cf. 
 *  If necessary, first load the font file cf->px.
 *  On return, cf->px will always be set up, possibly to a null font
 */

{
    register struct chinfo *ch;	/* chinfo for c */
    register unsigned long  *p;
    int t;

    if (cf->font_state != LOADED) {
	if (cf->font_state == UNSEEN) load_font (cf);
	if (cf->font_state != LOADED) return 0; }

    cf -> cload[c] = 1;		/* mark it loaded */
    ch = &cf->px->px_info[c];
    p = ch->ch_raster;
    if (p == 0) return 1;	/* all white */

    /* get bits in pxl form */

    if (cf->px->px_fmt == PKFMT) {
	p = pktopxl (p, &cf->cwidth[c]); }

    if (! LFlag) {
      /* For portrait we will paint columns, so transpose the bit map. */
      p = transpose (p, ch->ch_height, ch->ch_width);
    } else {
      /* For landscape we will paint rows, which we have, but we need
	 them from bottom to top (the rows will be dot columns in the
	 output). */
      p = flip (p, ch->ch_height, ch->ch_width);
      /* Twiddle dimensions and reference point */
      t = ch->ch_width; ch->ch_width = ch->ch_height; ch->ch_height = t;
      t = ch->ch_yoffset; ch->ch_yoffset = ch->ch_xoffset;
      ch->ch_xoffset = ch->ch_width-1 - t;
    }

    /* For draft mode, turn off every other bit in a checkerboard pattern */
    if (DFlag)
      p = draft_screen (p, ch->ch_height, ch->ch_width);

    ch->ch_raster = p;

    return 1;
}

/* Flip char top to bottom by swapping rows. */

long *flip (long *pxlp, int pxlh, int pxlw)
{
  int x, y1, y2, t;
  long *p1, *p2;
  int pxlw_words = (pxlw + 31) >> 5;

  for (y1 = 0, y2 = pxlh - 1; y1 < y2; y1++, y2--) {
    p1 = pxlp + y1 * pxlw_words;
    p2 = pxlp + y2 * pxlw_words;
    for (x = 0; x < pxlw_words; x++)
      t = p1[x], p1[x] = p2[x], p2[x] = t;
  }

  return pxlp;
}

#define BYTES_LITTLE_ENDIAN 1	/* REMOVEME */

#if BYTES_LITTLE_ENDIAN
#define littlefix(p) ((long *) ((long) p ^ 3))
#endif

#if BYTES_BIG_ENDIAN
#define littlefix(p) (p)
#endif

#ifndef littlefix
#error define BYTES_BIG_ENDIAN or BYTES_LITTLE_ENDIAN
#endif

long *transpose (long *pxlp, int pxlh, int pxlw)
{
  int pxlw_words = (pxlw + 31) >> 5;
  int pxlh_words = (pxlh + 31) >> 5;
  int pxlw_bytes = pxlw_words << 2;
  int pxlh_bytes = pxlh_words << 2;
  int newsize = pxlh_bytes * ((pxlw + 7) & -8);
  int nx = (pxlw + 7) >> 3;
  int ny = (pxlh + 7) >> 3;
  int dy = 8 * pxlh_bytes;
  int din = 8 * pxlw_bytes - nx;
  int dout = 1 - dy * nx;
  long *retval = malloc (newsize);
  char *in, *out;
  int x, y;

  bzero (retval, newsize);

  in = (char *) pxlp;
  out = (char *) retval;

  for (y = ny; y != 0; y--)
    {
      for (x = nx; x != 0; x--)
	{
	  memflip8x8 (littlefix (in), pxlw_bytes, littlefix (out), pxlh_bytes);
	  in++;
	  out += dy;
	}
      in += din;
      out += dout;
    }

  return retval;
}

/* Pass the bits in *p through a checkerboard screen.
   This can make 1-pixel lines at 45 degree angles vanish completely,
   and cm fonts are fond of 1-pixel lines, so additionally 
   do not clear any bit that has white on the N, S, E, and W. */

static long *draft_screen (long *pxlp, int pxlw, int pxlh)
{
  int x, y;
  int nx = (pxlw + 31) >> 5;
  int ny = pxlh;
  unsigned long *p = pxlp;
  unsigned long screen = 0x55555555;
  unsigned long t, left, right, up, down;

  for (y = 0; y < ny; y++) {
    screen = ~screen;
    for (x = 0; x < nx; x++) {
      t = *p;
      left = t >> 1 | (x == 0 ? 0 : p[-1] << 31);
      right = t << 1 | (x == nx-1 ? 0 : p[1] >> 31);
      up = y == 0 ? 0 : p[-nx];
      down = y == ny-1 ? 0 : p[nx];
      *p++ = t & (screen | ~(left | right | up | down));
    }
  }

  return pxlp;
}

load_font (cf)
    register struct fontinfo *cf;
{
    register char *s;
    register struct pxltail *px;
    double scale = GlobalMag * cf->DVIScaledSize / cf->DVIDesignSize;
    int DVIMag;
    char *comma, *warnstr;

    px = read_font (cf->font_name, scale, DPI, pkfonts);

    cf->px = px;
    if (px->px_fmt == BADFMT) {
	sprintf (ttybuf, "[%s %s]", px->px_filename, errstr); ttyout (ttybuf);
	cf->font_state = BAD; }
    else {
	cf->font_state = LOADED;

	if (px->px_fmt == ROMFMT &&
	    cf->DVIDesignSize != cf->DVIScaledSize) {
		long c = -1;
		while (++c < 128) px->px_info[c].ch_raster = 0;
		px->px_fmt = TFMFMT; }

	if (px->px_fmt == TFMFMT) {
	    ttyprint ("[font %s not found: characters will be left blank]",
		      fontname (cf->font_name, scale)); }
	    
	DVIMag = 0.5 + 5 * DPI * reconcile_scale (scale);
	if (px->px_fmt == TFMFMT)
	    px->px_magnification = DVIMag;

	sprintf (ttybuf, "[%s", px->px_filename);
	comma = warnstr = ": warning:";

	if (cf->DVIChecksum && px->px_checksum &&
	    cf->DVIChecksum != px->px_checksum) {
		strcat (ttybuf, comma); ttyout (ttybuf);
		sprintf (ttybuf, "checksum is %#o not tex's %#o", 
			 px->px_checksum, cf->DVIChecksum);
		comma = ","; }

	if (fabs (cf->DVIDesignSize - px->px_designsize / fix_per_dvi) > 0.5) {
		strcat (ttybuf, comma); ttyout (ttybuf);
		sprintf (ttybuf, "design size is %.2fpt not tex's %.2fpt",
			 px->px_designsize / 1048576.,
			 cf->DVIDesignSize * fix_per_dvi / 1048576.);
		comma = ","; }

	strcat (ttybuf, "]");
	if (!SFlag || comma != warnstr) ttyout (ttybuf); }
    
    /* Char widths calculated here from TFM widths will be overwritten */
    /* (with chardx info), if available, as chars are downloaded */

    ScaleTFMWidths (cf->px, cf->DVIScaledSize);
    ComputeCWidths (cf);

    cf->pspace = cf->DVIScaledSize / 6;	/* a three-unit "thin space" */
    cf->nspace = -4 * cf->pspace;
    cf->vspace = 5 * cf->pspace;
}

ttyout (s) 
    char *s;
{
    static long ttypos = 0;
    long l = strlen (s);

    if (ttypos != 0) {
	if (ttypos + l > 79) {
	    fputc ('\n', stderr);
	    ttypos = 0; }
	else {
	    fputc (' ', stderr);
	    ttypos++; }}

    fputs (s, stderr);
    fflush (stderr);
    ttypos += l;
}

/* Figure out where this page goes on the paper, and whether
   it is the last.   Returns quadrant number (0..3) for the top left
   corner of the image on the paper, or -1 if the page should not
   be printed at all (because it is destined for the back.)

   Sets global MID_SIDE nonzero if there are more images to follow on
   the same physical page. 

   Sets global ODD_LAST nonzero if printing even sides and EOF after
   this page would leave an unprinted odd side in the feeder.

   count[] provides the page's tex page numbers. */

static int position_page (int count[10])
{
  static int pageno;
  static int init;
  static int mid_first, mid_last;

  if (! init) {
    init = 1;
    /* If printing in reverse order, count pages down from the end,
       and print after placing the first side on the page */
    if (! PFlag) {
      pageno = n_pages + 1;
      mid_first = 0;
      mid_last= 1; }
    else {
      mid_first = 1;
      mid_last = 0; }
  }

  /* Clear mid_side in case we return early */
  mid_side = 0;

  /* For now just wimp out and pay no attention to the page counters at all. */

  if (PFlag)
    pageno++;
  else
    pageno--;

  /* (to get clever, increment pageno more than once to get to the top
     of a side, or the top of a front side, when the counters undergo
     a discontinuity.  Or increment it to match the parity of the 
     rightmost nonzero counter to get 2-sided sides on the proper sides.
     There are many screwy things to handle.) */

  if (TwoFlag)
    switch (pageno & 3)
      {
      case 1:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = mid_first; return 0;
      case 2:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = mid_last; return 1;
      case 3:
	odd_last = 0;
	if (OFlag) return -1; mid_side = mid_first; return 0;
      case 0:
	odd_last = 0;
	if (OFlag) return -1; mid_side = mid_last; return 1;
      }

  if (FourFlag)
    switch (pageno & 7)
      {
      case 1:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = mid_first; return 0;
      case 2:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = 1; return 2;
      case 3:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = 1; return 1;
      case 4:
	odd_last = EFlag;
	if (EFlag) return -1; mid_side = mid_last; return 3;
      case 5:
	odd_last = 0;
	if (OFlag) return -1; mid_side = mid_first; return 0;
      case 6:
	odd_last = 0;
	if (OFlag) return -1; mid_side = 1; return 2;
      case 7:
	odd_last = 0;
	if (OFlag) return -1; mid_side = 1; return 1;
      case 0:
	odd_last = 0;
	if (OFlag) return -1; mid_side = mid_last; return 3;
      }

  switch (pageno & 1)
    {
    case 1:
      odd_last = EFlag;
      if (EFlag) return -1; mid_side = 0; return 0;
    case 0:
      odd_last = 0;
      if (OFlag) return -1; mid_side = 0; return 0;
    }
}
