/*
 * DVI previewer for X.
 *
 * Eric Cooper, CMU, September 1985.
 *
 * Code derived from dvi-imagen.c.
 *
 * Modification history:
 * 1/1986	Modified for X.10	--Bob Scheifler, MIT LCS.
 * 7/1988	Modified for X.11	--Mark Eichin, MIT
 * 12/1988	Added 'R' option, toolkit, magnifying glass
 *					--Paul Vojta, UC Berkeley.
 * 2/1989	Added tpic support	--Jeffrey Lee, U of Toronto
 * 4/1989	Modified for System V	--Donald Richardson, Clarkson Univ.
 * 3/1990	Added VMS support	--Scott Allendorf, U of Iowa
 * 7/1990	Added reflection mode	--Michael Pak, Hebrew U of Jerusalem
 * 1/1992	Added greyscale code	--Till Brychcy, Techn. Univ. Muenchen
 *					  and Lee Hetherington, MIT
 *
 *	Compilation options:
 *	SYSV	compile for System V
 *	VMS	compile for VMS
 *	X10	compile for X10
 *	NOTOOL	compile without toolkit (X11 only)
 *	BUTTONS	compile with buttons on the side of the window (needs toolkit)
 *	MSBITFIRST	store bitmaps internally with most significant bit first
 *	BMSHORT	store bitmaps in shorts instead of bytes
 *	BMLONG	store bitmaps in longs instead of bytes
 *	ALTFONT	default for -altfont option
 *	A4	use European size paper
 *	TEXXET	support reflection dvi codes (right-to-left typesetting)
 *	GREY	use grey levels to shrink fonts
 */

#include "xdvi.h"
#include "dvi.h"
#include <sys/stat.h>

#ifdef	sun
extern	char	*sprintf();
#endif

#ifndef	X_NOT_STDC_ENV
#include <stdlib.h>
#else
char	*realloc();
#endif
#if	defined(macII) && !defined(__STDC__) /* stdlib.h doesn't define these */
char	*realloc();
#endif /* macII */

#define	PK_PRE		247
#define	PK_ID		89
#define	PK_MAGIC	(PK_PRE << 8) + PK_ID
#define	GF_PRE		247
#define	GF_ID		131
#define	GF_MAGIC	(GF_PRE << 8) + GF_ID
#define	VF_PRE		247
#define	VF_ID_BYTE	202
#define	VF_MAGIC	(VF_PRE << 8) + VF_ID_BYTE
#define	PXL_MAGIC1	0
#define	PXL_MAGIC2	1001

#define	dvi_oops(str)	longjmp(dvi_env, (int) str);

static	struct stat fstatbuf;

static	Boolean	font_not_found;

/*
 * DVI preamble and postamble information.
 */
static	char	job_id[300];
static	long	numerator, denominator, magnification;

/*
 * Offset in DVI file of last page, set in read_postamble().
 */
static	long	last_page_offset;


/*
 *	free_vf_chain frees the vf_chain structure.
 */

static	void
free_vf_chain(tnp)
	struct tn *tnp;
{
	while (tnp != NULL) {
	    register struct tn *tnp1 = tnp->next;
	    free((char *) tnp);
	    tnp = tnp1;
	}
}


/*
 *	Release all shrunken bitmaps for all fonts.
 */

void
reset_fonts()
{
	register struct font *f;
	register struct glyph *g;

	for (f = font_head; f != NULL; f = f->next)
	    if ((f->flags & FONT_LOADED) && !(f->flags & FONT_VIRTUAL))
		for (g = f->glyph; g <= f->glyph + f->maxchar; ++g) {
		    if (g->bitmap2.bits) {
			free(g->bitmap2.bits);
			g->bitmap2.bits = NULL;
		    }
#ifdef	GREY
		    if (g->pixmap2) {
			XDestroyImage(g->image2);
			g->pixmap2 = NULL;
		    }
#endif
		}
}

/*
 *	realloc_font allocates the font structure to contain (newsize + 1)
 *	characters.
 */

void
realloc_font(fontp, newsize)
	struct font		*fontp;
	WIDEARG(ubyte, int)	newsize;
{
	struct glyph *glyph;

	glyph = fontp->glyph = (struct glyph *) realloc((char *) fontp->glyph,
	    ((unsigned int) newsize + 1) * sizeof(struct glyph));
	if (glyph == NULL) oops("! Cannot reallocate space for glyph array.");
	if (newsize > fontp->maxchar)
	    bzero((char *) (glyph + fontp->maxchar + 1),
		(int) (newsize - fontp->maxchar) * sizeof(struct glyph));
	maxchar = fontp->maxchar = newsize;
}


/*
 *	realloc_virtual_font does the same thing for virtual fonts.
 */

void
realloc_virtual_font(fontp, newsize)
	struct font		*fontp;
	WIDEARG(ubyte, int)	newsize;
{
	struct macro *macro;

	macro = fontp->macro = (struct macro *) realloc((char *) fontp->macro,
	    ((unsigned int) newsize + 1) * sizeof(struct macro));
	if (macro == NULL) oops("! Cannot reallocate space for macro array.");
	if (newsize > fontp->maxchar)
	    bzero((char *) (macro + fontp->maxchar + 1),
		(int) (newsize - fontp->maxchar) * sizeof(struct macro));
	maxchar = fontp->maxchar = newsize;
}


/*
 *	load_font locates the raster file and reads the index of characters,
 *	plus whatever other preprocessing is done (depending on the format).
 */

void
load_font(fontp)
	struct font *fontp;
{
	float	fsize	= fontp->fsize;
	int	dpi	= fsize + 0.5;
	char	*font_found;
	int	size_found;
	int	magic;

	fontp->flags |= FONT_LOADED;
	fontp->file = font_open(fontp->fontname, &font_found,
	    (WIDEARG(float, double)) fsize, &size_found, fontp->magstepval,
	    &fontp->filename);
	if (fontp->file == NULL) {
	    Fprintf(stderr, "Can't find font %s.\n", fontp->fontname);
	    font_not_found = True;
	    return;
	}
	--n_files_left;
	if (font_found != NULL) {
	    Fprintf(stderr,
		    "Can't find font %s; using %s instead at %d dpi\n",
		    fontp->fontname, font_found, dpi);
	    free(fontp->fontname);
	    fontp->fontname = font_found;
	}
	else if (size_found > (int) (5 * 1.002 * fsize + 0.5) ||
		size_found < (int) (5 * 0.998 * fsize + 0.5))
	    Fprintf(stderr,
		"Can't find font %s at %d dpi; using %d dpi instead.\n",
		fontp->fontname, dpi, (size_found + 2) / 5);
	fontp->fsize = (float) size_found / 5;
	fontp->timestamp = ++current_timestamp;
	fontp->maxchar = maxchar = 255;
	fontp->set_char_p = set_char;
	magic = two(fontp->file);
#ifdef	USE_PK
	if (magic == PK_MAGIC) read_PK_index(fontp);
	else
#endif
#ifdef	USE_GF
	    if (magic == GF_MAGIC) read_GF_index(fontp);
	else
#endif
	    if (magic == VF_MAGIC) read_VF_index(fontp);
	else
#ifdef	USE_PXL
	    if (magic == PXL_MAGIC1 && two(fontp->file) == PXL_MAGIC2)
		read_PXL_index(fontp);
	else
#endif
	    oops("Cannot recognize format for font file %s", fontp->filename);

	if (fontp->flags & FONT_VIRTUAL) {
	    while (maxchar > 0 && fontp->macro[maxchar].pos == NULL) --maxchar;
	    if (maxchar < 255)
		realloc_virtual_font(fontp, WIDEARG(,(int)) maxchar);
	}
	else {
	    while (maxchar > 0 && fontp->glyph[maxchar].addr == 0) --maxchar;
	    if (maxchar < 255)
		realloc_font(fontp, WIDEARG(,(int)) maxchar);
	}
}


/*
 *	MAGSTEPVALUE - If the given magnification is close to a \magstep
 *	or a \magstephalf, then return twice the number of \magsteps.
 *	Otherwise return NOMAGSTP.
 */

#define	NOMAGSTP (-29999)
#define	NOBUILD	29999

static	int
magstepvalue(mag)
	float	*mag;
{
	int	m	= 0;
	double	fmag	= *mag;
	double	xmag	= pixels_per_inch;
	float	margin	= fmag * 0.002;

	if (fmag < pixels_per_inch)
	    for (;;) {
		if (xmag - fmag < margin && -(xmag - fmag) < margin) {
		    *mag = xmag;
		    return m;
		}
		if (xmag < fmag) break;
		xmag *= 0.9128709292;
		--m;
	    }
	else
	    for (;;) {
		if (xmag - fmag < margin && -(xmag - fmag) < margin) {
		    *mag = xmag;
		    return m;
		}
		if (xmag > fmag) break;
		xmag *= 1.095445115;
		++m;
	    }
	return NOMAGSTP;
}


/*
 *	reuse_font recursively sets the flags for font structures being reused.
 */

static	void
reuse_font(fontp)
	struct font *fontp;
{
	struct tn *tnp;

	if (fontp->flags & FONT_IN_USE) return;

	fontp->flags |= FONT_IN_USE;
	if (list_fonts)
	    Printf("(reusing) %s at %d dpi\n", fontp->fontname,
		(int) (fontp->fsize + 0.5));
	if (fontp->flags & FONT_VIRTUAL)
	    for (tnp = fontp->vf_chain; tnp != NULL; tnp = tnp->next)
		reuse_font(tnp->fontp);
}


/*
 *      define_font reads the rest of the fntdef command and then reads in
 *      the specified pixel file, adding it to the global linked-list holding
 *      all of the fonts used in the job.
 */
void
define_font(file, cmnd, vfparent, tn_headpp)
	FILE		*file;
	WIDEARG(ubyte, unsigned int) cmnd;
	struct font	*vfparent;	/* vf parent of this font, or NULL */
	struct tn	**tn_headpp;	/* addr of head of list of TeXnumbers */
{
	register struct tn *tnp;
	struct font *fontp;
	float	fsize;
	double	scale_dimconv;
	int scale;
	int design;
	int magstepval;
	int len;
	char *fontname;
	int size;

	tnp = (struct tn *) xmalloc((unsigned) sizeof(struct tn),
	    "TeXnumber structure");
	tnp->next = *tn_headpp;
	*tn_headpp = tnp;
	tnp->TeXnumber = num(file, (int) cmnd - FNTDEF1 + 1);
	(void) four(file);	/* checksum */
	scale = four(file);
	design = four(file);
	len = one(file) + one(file);
	fontname = xmalloc((unsigned) len + 1, "font name");
	Fread(fontname, sizeof(char), len, file);
	fontname[len] = '\0';
	if(debug & DBG_PK)
	    Printf("Define font \"%s\" scale=%d design=%d\n",
		fontname, scale, design);
	if (vfparent == NULL) {
	    fsize = 0.001 * scale / design * magnification * pixels_per_inch;
	    scale_dimconv = dimconv;
	}
	else {
	    /*
	     *	The scaled size is given in units of vfparent->scale * 2 ** -20
	     *	SPELL units, so we convert it into SPELL units by multiplying by
	     *		vfparent->dimconv.
	     *	The design size is given in units of 2 ** -20 pt, so we convert
	     *	into SPELL units by multiplying by
	     *		(pixels_per_inch * 2**16) / (72.27 * 2**20).
	     */
	    fsize = (72.27 * (1<<4)) * vfparent->dimconv * scale / design;
	    scale_dimconv = vfparent->dimconv;
	}
	magstepval = magstepvalue(&fsize);
	size = 5 * fsize + 0.5;
	/*
	 * reuse font if possible
	 */
	for (fontp = font_head;; fontp = fontp->next) {
	    if (fontp == NULL) {		/* if font doesn't exist yet */
		if (list_fonts)
		    Printf("%s at %d dpi\n", fontname, (int) (fsize + 0.5));
		fontp = (struct font *) xmalloc((unsigned) sizeof(struct font),
		    "font structure");
		fontp->dimconv = scale * scale_dimconv / (1<<20);
		fontp->fontname = fontname;
		fontp->fsize = fsize;
		fontp->magstepval = magstepval;
		fontp->flags = FONT_IN_USE;
		if (vfparent == NULL) load_font(fontp);
		fontp->next = font_head;
		font_head = fontp;
		break;
	    }
	    if (strcmp(fontname, fontp->fontname) == 0
		    && size == (int) (5 * fontp->fsize + 0.5)) {
			/* if font already in use */
		reuse_font(fontp);
		free(fontname);
		break;
	    }
	}
	tnp->fontp = fontp;
}


/*
 *      process_preamble reads the information in the preamble and stores
 *      it into global variables for later use.
 */
static	void
process_preamble()
{
	ubyte   k;

	if (one(dvi_file) != PRE)
		dvi_oops("DVI file doesn't start with preamble");
	if (one(dvi_file) != 2)
		dvi_oops("Wrong version of DVI output for this program");
	numerator     = four(dvi_file);
	denominator   = four(dvi_file);
	magnification = four(dvi_file);
	dimconv = (((double) numerator * magnification)
		/ ((double) denominator * 1000.));
	dimconv = dimconv * (((long) pixels_per_inch)<<16) / 254000;
	specialConv = pixels_per_inch * magnification / 1000000.0;
	k = one(dvi_file);
	Fread(job_id, sizeof(char), (int) k, dvi_file);
	job_id[k] = '\0';
}

/*
 *      find_postamble locates the beginning of the postamble
 *	and leaves the file ready to start reading at that location.
 */
#define	TMPSIZ	516	/* 4 trailer bytes + 512 junk bytes allowed */
static	void
find_postamble()
{
	long	pos;
	ubyte	temp[TMPSIZ];
	ubyte	*p;
	ubyte	*p1;
	ubyte	byte;

	Fseek(dvi_file, (long) 0, 2);
	pos = ftell(dvi_file) - TMPSIZ;
	if (pos < 0) pos = 0;
	Fseek(dvi_file, pos, 0);
	p = temp + fread((char *) temp, sizeof(char), TMPSIZ, dvi_file);
	for (;;) {
	    p1 = p;
	    while (p1 > temp && *(--p1) != TRAILER) ;
	    p = p1;
	    while (p > temp && *(--p) == TRAILER) ;
	    if (p <= p1 - 4) break;	/* found 4 TRAILER bytes */
	    if (p <= temp) dvi_oops("DVI file corrupted");
	}
	pos += p - temp;
	byte = *p;
	while (byte == TRAILER) {
	    Fseek(dvi_file, --pos, 0);
	    byte = one(dvi_file);
	}
	if (byte != 2)
	    dvi_oops("Wrong version of DVI output for this program");
	Fseek(dvi_file, pos - 4, 0);
	Fseek(dvi_file, sfour(dvi_file), 0);
}


/*
 *      read_postamble reads the information in the postamble,
 *	storing it into global variables.
 *      It also takes care of reading in all of the pixel files for the fonts
 *      used in the job.
 */
static	void
read_postamble()
{
	ubyte   cmnd;
	struct font	*fontp;
	struct font	**fontpp;

	if (one(dvi_file) != POST)
	    dvi_oops("Postamble doesn't begin with POST");
	last_page_offset = four(dvi_file);
	if (numerator != four(dvi_file)
		|| denominator != four(dvi_file)
		|| magnification != four(dvi_file))
	    dvi_oops("Postamble doesn't match preamble");
		/* read largest box height and width */
	unshrunk_page_h = (spell_conv(sfour(dvi_file)) >> 16) + offset_y;
	if (unshrunk_page_h < unshrunk_paper_h)
	    unshrunk_page_h = unshrunk_paper_h;
	unshrunk_page_w = (spell_conv(sfour(dvi_file)) >> 16) + offset_x;
	if (unshrunk_page_w < unshrunk_paper_w)
	    unshrunk_page_w = unshrunk_paper_w;
	(void) two(dvi_file);	/* max stack size */
	total_pages = two(dvi_file);
	font_not_found = False;
	while ((cmnd = one(dvi_file)) >= FNTDEF1 && cmnd <= FNTDEF4)
	    define_font(dvi_file, cmnd, (struct font *) NULL, &tn_head);
	if (cmnd != POSTPOST)
	    dvi_oops("Non-fntdef command found in postamble");
	if (font_not_found)
	    dvi_oops("Not all pixel files were found");
	/*
	 * free up fonts no longer in use
	 */
	fontpp = &font_head;
	while ((fontp = *fontpp) != NULL)
	    if (fontp->flags & FONT_IN_USE)
		fontpp = &fontp->next;
	    else {
		if (debug & DBG_PK)
		    Printf("Discarding font \"%s\" at %d dpi\n",
			fontp->fontname, (int) (fontp->fsize + 0.5));
		*fontpp = fontp->next;		/* remove from list */
		free(fontp->fontname);
		if (fontp->flags & FONT_LOADED) {
		    if (fontp->file != NULL) {
			Fclose(fontp->file);
			++n_files_left;
		    }
		    free(fontp->filename);
		    if (fontp->flags & FONT_VIRTUAL) {
			register struct macro *m;

			for (m = fontp->macro;
				m <= fontp->macro + fontp->maxchar; ++m)
			    if (m->free_me) free((char *) m->pos);
			free((char *) fontp->macro);
			free_vf_chain(fontp->vf_chain);
		    }
		    else {
			register struct glyph *g;

			for (g = fontp->glyph;
				g <= fontp->glyph + fontp->maxchar; ++g) {
			    if (g->bitmap.bits != NULL) free(g->bitmap.bits);
			    if (g->bitmap2.bits != NULL) free(g->bitmap2.bits);
#ifdef	GREY
			    if (g->pixmap2 != NULL) XDestroyImage(g->image2);
#endif
			}
			free((char *) fontp->glyph);
		    }
		    free((char *) fontp);
		}
	    }
}

static	void
prepare_pages()
{
	int i;

	page_offset = (long *) xmalloc((unsigned) total_pages * sizeof(long),
	    "page directory");
	i = total_pages;
	page_offset[--i] = last_page_offset;
	Fseek(dvi_file, last_page_offset, 0);
	/*
	 * Follow back pointers through pages in the DVI file,
	 * storing the offsets in the page_offset table.
	 */
	while (i > 0) {
	    Fseek(dvi_file, (long) (1+4+(9*4)), 1);
	    Fseek(dvi_file, page_offset[--i] = four(dvi_file), 0);
	}
}

void
init_page()
{
	page_w = ROUNDUP(unshrunk_page_w, mane.shrinkfactor) + 2;
	page_h = ROUNDUP(unshrunk_page_h, mane.shrinkfactor) + 2;
}

/*
 *	init_dvi_file is the main subroutine for reading the startup information
 *	from the dvi file.
 */

static	void
init_dvi_file()
{
	(void) fstat(fileno(dvi_file), &fstatbuf);
	dvi_time = fstatbuf.st_mtime;
	process_preamble();
	find_postamble();
	read_postamble();
	prepare_pages();
	init_page();
	if (current_page >= total_pages) current_page = total_pages - 1;
	hush_spec_now = hush_spec;
}

/**
 **	open_dvi_file opens the dvi file and calls init_dvi_file() to
 **	initialize it.
 **/

void
open_dvi_file()
{
	char *errmsg;

	if ((dvi_file = fopen(dvi_name, OPEN_MODE)) == NULL) {
	    int n = strlen(dvi_name);
	    char *file = dvi_name;

	    if (strcmp(dvi_name + n - sizeof(".dvi") + 1, ".dvi") == 0) {
		perror(dvi_name);
		exit(1);
	    }
	    dvi_name = xmalloc((unsigned) n + sizeof(".dvi"), "dvi file name");
	    Sprintf(dvi_name, "%s.dvi", file);
	    if ((dvi_file = fopen(dvi_name, OPEN_MODE)) == NULL) {
		perror(dvi_name);
		exit(1);
	    }
	}

	if (errmsg = (char *) setjmp(dvi_env)) oops(errmsg);
	init_dvi_file();
}

/**
 **	Check for changes in dvi file.
 **/

Boolean
check_dvi_file()
{
	struct font *fontp;

	if (dvi_file == NULL || fstat(fileno(dvi_file), &fstatbuf) != 0
	    || fstatbuf.st_mtime != dvi_time) {
		if (dvi_file) Fclose(dvi_file);
		free((char *) page_offset);
		dvi_file = fopen(dvi_name, OPEN_MODE);
		if (dvi_file == NULL)
		    dvi_oops("Cannot reopen dvi file.");
		if (list_fonts) Putchar('\n');
		free_vf_chain(tn_head);
		tn_head = NULL;
		for (fontp = font_head; fontp != NULL; fontp = fontp->next)
		    fontp->flags &= ~FONT_IN_USE;
		init_dvi_file();
		redraw_page();
		return False;
	}
	return True;
}
