/* $Id: emspecial.c 57073 2020-12-05 05:22:50Z takuji $
 *   emspecial.c
 *   This routine handles the emTeX special commands.
 */
#include "dvips.h" /* The copyright notice in that file is included too!*/

#include <ctype.h>
#include <stdlib.h>
/*
 *   The external declarations:
 */
#include "protos.h"

#ifdef EMTEX
/* emtex specials, added by rjl */

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

static long emmax = 161;

/*
 *   We define these seek constants if they don't have their
 *   values already defined.
 */
#ifndef SEEK_SET
#define SEEK_SET (0)
#endif
#ifndef SEEK_END
#define SEEK_END (2)
#endif

struct empt {
   struct empt *next;
   shalfword point;
   integer x, y;
};

static struct empt **empoints = NULL;
boolean emused = FALSE;  /* true if em points used on this page */
integer emx, emy;

struct emunit {
   const char *unit;
   float factor;
};
struct emunit emtable[] = {
  {"pt",72.27},
  {"pc",72.27/12},
  {"in",1.0},
  {"bp",72.0},
  {"cm",2.54},
  {"mm",25.4},
  {"dd",72.27/(1238.0/1157)},
  {"cc",72.27/12/(1238.0/1157)},
  {"sp",72.27*65536},
  {0,0.0}
};

static void emgraph(char *filename, float emwidth, float emheight);

/* clear the empoints array if necessary */
void
emclear(void)
{
   int i;
   if (emused && empoints)
      for (i=0; i<emmax; i++)
         empoints[i] = 0;
   emused = FALSE;
}

/* put an empoint into the empoints array */
static struct empt *
emptput(shalfword point, integer x, integer y)
{
   struct empt *p;
   int start;

   emused = TRUE;
   start = point % emmax;
   p = empoints[start];
   while ( p ) {
      if ( p->point == point )
         break;
      p = p->next;
   }
   if (p == 0) {
      p = (struct empt *)mymalloc(sizeof(struct empt));
      p->next = empoints[start];
      empoints[start] = p;
   }
   p->point = point;
   p->x = x;
   p->y = y;
   return(p);
}

/* get an empoint from the empoints array */
static struct empt *
emptget(shalfword point)
{
   struct empt *p;
   int start;

   start = point % emmax;
   if (emused == TRUE) {
      p = empoints[start];
      while (p) {
	 if (p->point == point)
	    return p;
	 p = p->next;
      }
   }
   sprintf(errbuf,"!em: point %d not defined",point);
   specerror(errbuf);
   return(NULL); /* never returns due to error */
}


/* convert width into dpi units */
static float
emunits(float width, char *unit)
{
struct emunit *p;
	for (p=emtable; p->unit; p++) {
	   if (strcmp(p->unit,unit)==0)
		return( width * actualdpi / p->factor );
	}
	return (-1.0); /* invalid unit */
}

/* The main routine for \special{em:graph ...} called from dospecial.c */
/* the line cut parameter is not supported (and is ignored) */

void
emspecial(char *p)
{
float emwidth, emheight;
shalfword empoint1, empoint2;
struct empt *empoint;
char emunit[30];
char emstr[500];
char *emp;

        hvpos();
	for (emp = p+3; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
	if (strncmp(emp, "linewidth", 9) == 0) {
	   /* code for linewidth */
	   for (emp = emp+9; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
	   sscanf(emp, "%f%2s", &emwidth, emunit);
	   emwidth = emunits(emwidth,emunit);
	   if (emwidth!=-1.0) {
	      snprintf(emstr, sizeof(emstr), "%.1f setlinewidth", emwidth);
	      cmdout(emstr);
#ifdef DEBUG
   if (dd(D_SPECIAL))
      fprintf(stderr, "em special: Linewidth set to %.1f dots\n",
		emwidth);
#endif
	   } else {
	      sprintf(errbuf,"Unknown em: special width");
	      specerror(errbuf);
	   }
	}
        else if (strncmp(emp, "moveto", 6) == 0) {
#ifdef DEBUG
   if (dd(D_SPECIAL))
#ifdef SHORTINT
      fprintf(stderr, "em special: moveto %ld,%ld\n", hh, vv);
#else
      fprintf(stderr, "em special: moveto %d,%d\n", hh, vv);
#endif
#endif
           emx = hh;
           emy = vv;
        }
        else if (strncmp(emp, "lineto", 6) == 0) {
#ifdef DEBUG
   if (dd(D_SPECIAL))
#ifdef SHORTINT
      fprintf(stderr, "em special: lineto %ld,%ld\n", hh, vv);
#else
      fprintf(stderr, "em special: lineto %d,%d\n", hh, vv);
#endif
#endif
	   cmdout("np");
	   numout(emx);
	   numout(emy);
	   cmdout("a");
	   numout(hh);
	   numout(vv);
	   cmdout("li");
	   cmdout("st");
           emx = hh;
           emy = vv;
        }
	else if (strncmp(emp, "point", 5) == 0) {
           if (empoints == NULL) {
              empoints =
              (struct empt **)mymalloc((integer)emmax * sizeof(struct empt *));
              emused = TRUE;
              emclear();
           }
	   for (emp = emp+5; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
           empoint1 = (shalfword)atoi(emp);
           empoint = emptput(empoint1,hh,vv);
#ifdef DEBUG
   if (dd(D_SPECIAL))
#ifdef SHORTINT
      fprintf(stderr, "em special: Point %d is %ld,%ld\n",
#else
      fprintf(stderr, "em special: Point %d is %d,%d\n",
#endif
		empoint->point, empoint->x, empoint->y);
#endif
	}
	else if (strncmp(emp, "line", 4) == 0) {
	   for (emp = emp+4; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
           empoint1 = (shalfword)atoi(emp);
	   for (; *emp && isdigit((unsigned char)*emp); emp++); /* skip point 1 */
	   if ( *emp && strchr("hvp",*emp)!=0 )
	      emp++;  /* skip line cut */
	   for (; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
	   if ( *emp && (*emp==',') )
	      emp++; /*  skip comma separator */
	   for (; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
           empoint2 = (shalfword)atoi(emp);
	   for (; *emp && isdigit((unsigned char)*emp); emp++); /* skip point 2 */
	   if ( *emp && strchr("hvp",*emp)!=0 )
	      emp++;  /* skip line cut */
	   for (; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
	   if ( *emp && (*emp==',') )
	      emp++; /*  skip comma separator */
	   emwidth = -1.0;
	   emunit[0]='\0';
	   sscanf(emp, "%f%2s", &emwidth, emunit);
	   emwidth = emunits(emwidth,emunit);
#ifdef DEBUG
   if (dd(D_SPECIAL))
      fprintf(stderr, "em special: Line from point %d to point %d\n",
		empoint1, empoint2);
#endif
	   cmdout("np");
	   if (emwidth!=-1.0) {
#ifdef DEBUG
   if (dd(D_SPECIAL))
   fprintf(stderr,"em special: Linewidth temporarily set to %.1f dots\n",
		emwidth);
#endif
	   	strcpy(emstr,"currentlinewidth");
	   	cmdout(emstr);
	        snprintf(emstr, sizeof(emstr), "%.1f setlinewidth", emwidth);
	        cmdout(emstr);
	   }
           empoint = emptget(empoint1);
	   numout(empoint->x);
	   numout(empoint->y);
	   cmdout("a");
           empoint = emptget(empoint2);
	   numout(empoint->x);
	   numout(empoint->y);
	   cmdout("li");
	   cmdout("st");
	   if (emwidth!=-1.0) {
	   	strcpy(emstr,"setlinewidth");
	   	cmdout(emstr);
	   }
	}
	else if (strncmp(emp, "message", 7) == 0) {
           fprintf_str(stderr, "em message: %s\n", emp+7);
	}
	else if (strncmp(emp, "graph", 5) == 0) {
	   int i;
	   for (emp = emp+5; *emp && isspace((unsigned char)*emp); emp++); /* skip blanks */
	   for (i=0; *emp && !isspace((unsigned char)*emp) && !(*emp==','); emp++) {
	      if (i > (int)sizeof(emstr) - 2) {
                fprintf(stderr, "em:graph: special too long, truncating\n");
                break;
	      }
	      emstr[i++] = *emp; /* copy filename */
	   }
	   emstr[i] = '\0';
	   /* now get optional width and height */
	   emwidth = emheight = -1.0;	/* no dimension is <= 0 */
	   for (; *emp && ( isspace((unsigned char)*emp) || (*emp==',') ); emp++)
	    ;  /* skip blanks and comma */
	   if (*emp) {
	      sscanf(emp, "%f%2s", &emwidth, emunit); /* read width */
	      emwidth = emunits(emwidth,emunit); /* convert to pixels */
	      for (; *emp && (*emp=='.'||isdigit((unsigned char)*emp)||isalpha((unsigned char)*emp)); emp++)
	       ; /* skip width dimension */
	      for (; *emp && ( isspace((unsigned char)*emp) || (*emp==',') ); emp++)
	       ;  /* skip blanks and comma */
	      if (*emp) {
	         sscanf(emp, "%f%2s", &emheight, emunit); /* read height */
	         emheight = emunits(emheight,emunit)*vactualdpi/actualdpi;
	      }
	   }
	   if (emstr[0]) {
	      emgraph(emstr,emwidth,emheight);
	   }
	   else {
              fprintf(stderr, "em:graph: no file given\n");
	   }
	}
	else {
           sprintf(errbuf,
	      "Unknown em: command (%s) in \\special will be ignored", p);
           specerror(errbuf);
	}
	return;
   }


/* em:graph routines */

/* The graphics routines currently decode 3 types of IBM-PC based graphics   */
/* files: .pcx, .bmp, .msp. The information on the formats has occasionally  */
/* been sketchy, and subject to interpretation. I have attempted to implement*/
/* these routines to correctly decode the graphics file types mentioned.     */
/* The compressed .bmp file type has not been tested fully due to a lack of  */
/* test files.                                                               */
/*                                                                           */
/* The method of reading in the headers of the binary files is ungainly, but */
/* portable. Failure to use a byte by byte read and convert method will      */
/* result in portability problems. Specifically there is no requirement that */
/* elements of a C structure be contiguous, only that they have an ascending */
/* order of addresses.                                                       */
/*                                                                           */
/* The current implementations of the graphics format to postscript          */
/* conversion are not the most efficient possible. Specifically: color       */
/* formats are converted to RGB ratios prior to using a simple thresholding  */
/* method to determine if the postscript bit is to be set. The thresholding  */
/* method is compabible with that used in emtex's drivers.                   */
/*                                                                           */
/* Please send bug reports relating to the em:graph routines to:             */
/*                maurice@bruce.cs.monash.edu.au                             */
/*                                                                           */
/* My thanks to Russell Lang for his assistance with the implementation of   */
/* these routines in a manner compatible with dvips and for the optimization */
/* of some routines.                                                         */
/*                                                                           */
/*                                                   Maurice Castro          */
/*                                                   8 Oct 92                */

/* Routines to read binary numbers from IBM PC file types */
static integer
readinteger(FILE *f)
{
   integer i;
   int r;

   i = 0;
   for (r = 0; r < 4; r++)
   {
      i = i |  ( ((integer) fgetc(f)) << (8*r) );
   }
   return(i);
}

static halfword
readhalfword(FILE *f)
{
   halfword i;
   int r;

   i = 0;
   for (r = 0; r < 2; r++)
   {
     i = i |  ( ((halfword) fgetc(f)) << (8*r) );
   }
   return(i);
}

#define readquarterword(f) ((unsigned char)fgetc(f))
#define tobyte(x) ((x/8) + (x%8 ? 1 : 0))

/* These routines will decode PCX files produced by Microsoft
 * Windows Paint.  Other programs may not produce files which
 * will be successfully decoded, most notably version 1.xx of
 * PC Paint. */

/*
 *   Type declarations.  integer must be a 32-bit signed; shalfword must
 *   be a sixteen-bit signed; halfword must be a sixteen-bit unsigned;
 *   quarterword must be an eight-bit unsigned.
 */
typedef struct
{
	quarterword man;
	quarterword ver;
	quarterword enc;
	quarterword bitperpix;
	halfword xmin;
	halfword ymin;
	halfword xmax;
	halfword ymax;
	halfword hres;
	halfword vres;
	quarterword pal[48];
	quarterword reserved;
	quarterword colorplanes;
	halfword byteperline;
	halfword paltype;
	quarterword fill[58];
} PCXHEAD;

static int
PCXreadhead(FILE *pcxf, PCXHEAD *pcxh)
{
	pcxh->man = readquarterword(pcxf);
	pcxh->ver = readquarterword(pcxf);
	pcxh->enc = readquarterword(pcxf);
	pcxh->bitperpix = readquarterword(pcxf);
	pcxh->xmin = readhalfword(pcxf);
	pcxh->ymin = readhalfword(pcxf);
	pcxh->xmax = readhalfword(pcxf);
	pcxh->ymax = readhalfword(pcxf);
	pcxh->hres = readhalfword(pcxf);
	pcxh->vres = readhalfword(pcxf);
	fread(pcxh->pal, 1, 48, pcxf);
	pcxh->reserved = readquarterword(pcxf);
	pcxh->colorplanes = readquarterword(pcxf);
	pcxh->byteperline = readhalfword(pcxf);
	pcxh->paltype = readhalfword(pcxf);
	fread(pcxh->fill, 1, 58, pcxf);

	if (feof(pcxf))
		return(0);				/* fail */
	if (pcxh->man != 0x0a)
		return(0);				/* fail */
	if (pcxh->enc != 0x1)
		return(0);				/* fail */
	return(1);					/* success */
	}

static int
PCXreadline(FILE *pcxf,
	    unsigned char *pcxbuf,
	    unsigned int byteperline)
{
	int n;
	int c;
	int i;

	n = 0;
	memset(pcxbuf,0,byteperline);
	do {
		c = fgetc(pcxf);
		if ((c & 0xc0) == 0xc0) {
			i = c & 0x3f;
			c = fgetc(pcxf) & 0xff;
			while ((i--) && (n < byteperline)) pcxbuf[n++] = c;
		}
		else {
			pcxbuf[n++] = c & 0xff;
		}
	} while (n < byteperline);
	if (c==EOF) n=0;
	return(n);
}

static void
PCXgetpalette(FILE *pcxf, PCXHEAD *pcxh,
	      unsigned char *r,
	      unsigned char *g,
	      unsigned char *b)
{
	int i;

	/* clear palette */
	for (i=0; i < 256; i++) {
		r[i] = 0;
		g[i] = 0;
		b[i] = 0;
	}

	switch (pcxh->ver) {
		case 0:
			/* version 2.5 of PC Paint Brush */
			for (i=0; i < 16; i++) {
				r[i] = pcxh->pal[i*3];
				g[i] = pcxh->pal[i*3+1];
				b[i] = pcxh->pal[i*3+2];
			}
			break;
		case 2:
			if (pcxh->colorplanes != 1) {
			    /* version 2.8 of PC Paint Brush with valid Palette */
			for (i=0; i < 16; i++) {
				r[i] = pcxh->pal[i*3];
				g[i] = pcxh->pal[i*3+1];
				b[i] = pcxh->pal[i*3+2];
			    }
			}
			else {	/* not sure if this is correct - rjl */
				/* mono palette */
				r[0] = 0x00; g[0] = 0x00; b[0] = 0x00;
				r[1] = 0xff; g[1] = 0xff; b[1] = 0xff;
			}
			break;
		case 3:
			/* version 2.8 of PC Paint Brush with no valid Palette */
			/* either mono or default */

			if (pcxh->colorplanes != 1) {
				/* Color default palette - should be checked */
				r[0] = 0x00;		g[0] = 0x00;		b[0] = 0x00;
				r[1] = 0x80;		g[1] = 0x00; 		b[1] = 0x00;
				r[2] = 0x00; 		g[2] = 0x80; 		b[2] = 0x00;
				r[3] = 0x80;		g[3] = 0x80;	 	b[3] = 0x00;
				r[4] = 0x00;		g[4] = 0x00; 		b[4] = 0x80;
				r[5] = 0x80;		g[5] = 0x00; 		b[5] = 0x80;
				r[6] = 0x00;		g[6] = 0x80;	 	b[6] = 0x80;
				r[7] = 0x80;		g[7] = 0x80; 		b[7] = 0x80;
				r[8] = 0xc0;		g[8] = 0xc0;		b[8] = 0xc0;
				r[9] = 0xff;		g[9] = 0x00; 		b[9] = 0x00;
				r[10] = 0x00; 		g[10] = 0xff; 		b[10] = 0x00;
				r[11] = 0xff; 		g[11] = 0xff; 		b[11] = 0x00;
				r[12] = 0x00; 		g[12] = 0x00; 		b[12] = 0xff;
				r[13] = 0xff; 		g[13] = 0x00; 		b[13] = 0xff;
				r[14] = 0x00; 		g[14] = 0xff; 		b[14] = 0xff;
				r[15] = 0xff; 		g[15] = 0xff; 		b[15] = 0xff;
			}
			else {
				/* mono palette */
				r[0] = 0x00;		g[0] = 0x00;		b[0] = 0x00;
				r[1] = 0xff;		g[1] = 0xff; 		b[1] = 0xff;
			}
			break;
		case 5:
		default:
			/* version 3.0 of PC Paint Brush or Better */
			fseek(pcxf, -769L, SEEK_END);
			/* if signature byte is correct then read the palette */
			/* otherwise copy the existing palette */
			if (fgetc(pcxf) == 12) {
				for (i=0; i < 256; i++) {
					r[i] = fgetc(pcxf);
					g[i] = fgetc(pcxf);
					b[i] = fgetc(pcxf);
				}
			}
			else {
				for (i=0; i < 16; i++) {
					r[i] = pcxh->pal[i*3];
					g[i] = pcxh->pal[i*3+1];
					b[i] = pcxh->pal[i*3+2];
				}
			}
			break;
	}
}

static void
PCXshowpicture(FILE *pcxf, int wide, int high, int bytes,
	       int cp, int bp, unsigned char *r,
	       unsigned char *g, unsigned char *b)
{
	int x;
	int y;
	int c;
	unsigned char *rowa[4];				/* row array */
	unsigned char *row;
	int p;
	unsigned char *pshexa;
	int xdiv, xmod, xbit;
	int width;
	int bytewidth;

	bytewidth = tobyte(wide);
	width = bytewidth * 8;

	/* output the postscript image size preamble */
	cmdout("/picstr ");
	numout((integer)tobyte(wide));
	cmdout("string def");

	numout((integer)width);
	numout((integer)high);
	numout((integer)1);
	newline();

	cmdout("[");
	numout((integer)width);
	numout((integer)0);
	numout((integer)0);
	numout((integer)-high);
	numout((integer)0);
	numout((integer)0);
	cmdout("]");

	nlcmdout("{currentfile picstr readhexstring pop} image");

	/* allocate the postscript hex output array */
	pshexa = (unsigned char *) mymalloc((integer)bytewidth);

	/* make sure that you start at the right point in the file */
	fseek(pcxf, (long) sizeof(PCXHEAD), SEEK_SET);

	/* malloc the lines */
	row = (unsigned char *) mymalloc((integer)bytes * cp);
	for (c = 0; c < cp; c++)
		rowa[c] = row + bytes*c;

	for (y = 0; y < high; y++) {
		/* clear the postscript hex array */
		memset(pshexa,0xff,bytewidth);

		/* read in all the color planes for a row of pixels */
		PCXreadline(pcxf, rowa[0], bytes*cp);

		/* process each pixel */
		for (x = 0; x < wide; x++) {
			/* build up the pixel value from color plane entries */
			p = 0;
			xdiv = x>>3;
			xmod = 7 - (x&7);
			xbit = 1 << xmod;
			switch(bp) {
				case 1:
					for (c = 0; c < cp; c++) {
						row = rowa[c];
						p |= ( (unsigned)(row[xdiv] & xbit) >> xmod ) << c;
					}
					break;
				case 4:  /* untested - most programs use 1 bit/pixel, 4 planes */
					row = rowa[0]; /* assume single plane */
					p = ( x & 1 ? row[xdiv] : row[xdiv]>>4 ) & 0x0f;
					break;
				case 8:
					row = rowa[0]; /* assume single plane */
					p = (unsigned) (row[x]);
					break;
				default:
					fprintf(stderr, "em:graph: Unable to Decode this PCX file\n");
					return;
			}
			if ((r[p] < 0xff) || (g[p] < 0xff) || (b[p] < 0xff))
				pshexa[xdiv] &= (~xbit);
			else
				pshexa[xdiv] |= xbit;
		}
		newline();
		mhexout(pshexa,(long)bytewidth);
	}
	free(pshexa);
	free(rowa[0]);
}

static void
imagehead(char *filename, int wide, int high,
	  float emwidth, float emheight)
{
	char *fullname = NULL, *name;
	if (!quiet) {
#ifdef KPATHSEA
	    fullname = (char *)kpse_find_file (filename, pictpath, 0);
#endif
	    if (!fullname)
		name = filename;
	    else
		name = fullname;
	    if (strlen(name) + prettycolumn > STDOUTSIZE) {
		fprintf(stderr,"\n");
		prettycolumn = 0;
	    }
	    fprintf_str(stderr,"<%s",name);
	    fflush(stderr);
	    prettycolumn += 2+strlen(name);
	    if (fullname) free (fullname);
	}
	hvpos();
	nlcmdout("@beginspecial @setspecial");
	if (!disablecomments) {
		cmdout("%%BeginDocument: em:graph");
		cmdout(filename);
		newline();
	}
	/* set the image size */
	if (emwidth <= 0.0)  emwidth = (float)wide;
	if (emheight <= 0.0)  emheight = (float)high;
	floatout(emwidth*72/actualdpi);
	floatout(emheight*72/vactualdpi);
	newline();
	cmdout("scale");
#ifdef DEBUG
	if (dd(D_SPECIAL)) {
	  fprintf(stderr,
	    "\nem:graph: %s width  %d pixels scaled to %.1f pixels\n",
	    filename, wide, emwidth);
	  fprintf_str(stderr,
	    "em:graph: %s height %d pixels scaled to %.1f pixels\n",
	    filename, high, emheight);
   	}
#endif
}

static void
imagetail(void)
{
	if (!disablecomments) {
	    fprintf(bitfile, "\n%%%%EndDocument\n");
	    linepos = 0;
	}
	nlcmdout("@endspecial");
	if (!quiet) {
	    fprintf(stderr,">");
	    fflush(stderr);
	}
}

static void
pcxgraph(FILE *pcxf, char *filename,
	 float emwidth, float  emheight /* dimension in pixels */ )
{
	PCXHEAD pcxh;
	unsigned char red[256];
	unsigned char green[256];
	unsigned char blue[256];
	int wide;
	int high;
	int bytes;
	int cp;
	int bpp;

	if (!PCXreadhead(pcxf, &pcxh)) {
		sprintf(errbuf,"em:graph: Unable to Read Valid PCX Header");
		specerror(errbuf);
	}
	PCXgetpalette(pcxf, &pcxh, red, green, blue);

	/* picture size calculation */
	wide = (pcxh.xmax - pcxh.xmin) + 1;
	high = (pcxh.ymax - pcxh.ymin) + 1;
	bytes = pcxh.byteperline;
	cp = pcxh.colorplanes;
	bpp = pcxh.bitperpix;

	imagehead(filename,wide,high,emwidth,emheight);
	PCXshowpicture(pcxf, wide, high, bytes, cp, bpp, red, green, blue);
	imagetail();
}

/* Microsoft Paint routines */
struct wpnt_1 {
	quarterword id[4];
	halfword width;
	halfword high;
	halfword x_asp;
	halfword y_asp;
	halfword x_asp_prn;
	halfword y_asp_prn;
	halfword width_prn;
	halfword high_prn;
	integer chk_sum;
	halfword chk_head;
};

#define WPAINT_1 1
#define WPAINT_2 2

static void
MSP_2_ps(FILE *f, int wide, int high)
{
	char *line;
	char *l;
	int i;
	int j;
	unsigned char a, b, c;
	int d;
	int width;
	halfword *linelen;

	/* an undocumented format - based on a type of run length encoding    */
	/* the format is made up of a list of line lengths, followed by a set */
	/* of lines. Each line is made up of 2 types of entries:              */
	/* 	1) A 3 term entry (0 a b): the byte b is repeated a times     */
	/*	2) A variable length entry (a xxxx....xxxx): a bytes are read */
	/*	   from the file.                                             */
	/* These entries are combined to build up a line                      */

	width = tobyte(wide)*8;

	/* output the postscript image size preamble */
	cmdout("/picstr");
	numout((integer)tobyte(wide));
	cmdout("string def");

	numout((integer)width);
	numout((integer)high);
	numout((integer)1);

	cmdout("[");
	numout((integer)width);
	numout((integer)0);
	numout((integer)0);
	numout((integer)-high);
	numout((integer)0);
	numout((integer)0);
	cmdout("]");

	nlcmdout("{currentfile picstr readhexstring pop} image");

	fseek(f, 32, SEEK_SET);

	/* read in the table of line lengths */
	linelen = (halfword *) mymalloc((integer)sizeof(halfword) * high);
	for (i = 0; i < high; i++) {
		linelen[i] = readhalfword(f);
		if (feof(f))
			return;
	}

	line = (char *) mymalloc((integer)tobyte(wide));
	for (i = 0; i < high; i++) {
		memset(line, 0xff, tobyte(wide));
		l = line;
		if (linelen[i] != 0) {
			d = linelen[i];
			while (d) {
				a = fgetc(f);
				d--;
				if (a == 0) {
					b = fgetc(f);
					c = fgetc(f);
					d -= 2;
					for (j = 0; j < b; j++)
						*l++ = c;
				}
				else {
					for (j = 0; j < a; j++)
						*l++ = fgetc(f);
					d -= j;
				}
			}
		}
		newline();
		mhexout((unsigned char *) line,(long)tobyte(wide));
	}
	free(linelen);
	free(line);
}

static void
MSP_1_ps(FILE *f, int wide, int high)
{
	char *line;
	int i;
	int width;

	width = tobyte(wide)*8;
	/* an partly documented format - see The PC Sourcebook                */
	/* the format is made up of a simple bitmap.                          */

	/* output the postscript image size preamble */
	cmdout("/picstr");
	numout((integer)tobyte(wide));
	cmdout("string def");

	numout((integer)width);
	numout((integer)high);
	numout((integer)1);

	cmdout("[");
	numout((integer)width);
	numout((integer)0);
	numout((integer)0);
	numout((integer)-high);
	numout((integer)0);
	numout((integer)0);
	cmdout("]");

	nlcmdout("{currentfile picstr readhexstring pop} image");

	fseek(f, 32, SEEK_SET);

	line = (char *) mymalloc((integer)tobyte(wide));
	for (i = 0; i < high; i++) {
		fread(line, 1, tobyte(wide), f);
		newline();
		mhexout((unsigned char *) line,(long)tobyte(wide));
	}
	free(line);
}


static void
mspgraph(FILE *f, char *filename, float emwidth, float emheight)
{
	struct wpnt_1 head;
	int paint_type = 0;

        /* read the header of the file and figure out what it is */
	fread(head.id, 1, 4, f);
	head.width = readhalfword(f);
	head.high = readhalfword(f);
	head.x_asp = readhalfword(f);
	head.y_asp = readhalfword(f);
	head.x_asp_prn = readhalfword(f);
	head.y_asp_prn = readhalfword(f);
	head.width_prn = readhalfword(f);
	head.high_prn = readhalfword(f);
	head.chk_sum = readinteger(f);
	head.chk_head = readhalfword(f);

	if (feof(f)) {
		fprintf(stderr, "em:graph: Unable to Read Valid MSP Header\n");
		return;
	}

        /* check the id bytes */
	if (!memcmp(head.id, "DanM", 4))
        	paint_type = WPAINT_1;
	if (!memcmp(head.id, "LinS", 4))
		paint_type = WPAINT_2;


	imagehead(filename,head.width,head.high,emwidth,emheight);
	switch (paint_type) {
		case WPAINT_1:
                	MSP_1_ps(f, head.width, head.high);
			break;
                case WPAINT_2:
                	MSP_2_ps(f, head.width, head.high);
			break;
		default:
			sprintf(errbuf, "em:graph: Unknown MSP File Type");
			specerror(errbuf);
	}
	imagetail();
}

/* ------------------------------------------------------------------------ */
/* .BMP file structures */
struct rgbquad {
	char blue;
	char green;
	char red;
	char res;
};

struct bitmapinfoheader {
	integer size;
	integer width;
	integer height;
	halfword planes;			/* must be set to 1 */
	halfword bitcount;			/* 1, 4, 8 or 24 */
	integer compression;
	integer sizeimage;
	integer xpelspermeter;
	integer ypelspermeter;
	integer clrused;
	integer clrimportant;
};

#ifdef WIN32
#undef RGB
#endif

/* constants for the compression field */
#define RGB 0L
#define RLE8 1L
#define RLE4 2L

struct bitmapfileheader {
	char type[2];
	integer size;
	halfword reserved1;
	halfword reserved2;
	integer offbits;
};

static void
rgbread(FILE *f, int w, int b, char *s)
{
	int i;

	/* set the line to white */
	memset(s, 0xff, ((w*b)/8)+1);

	/* read in all the full bytes */
	for (i = 0; i < (w * b) / 8; i++)
		*s++ = fgetc(f);

	/* read in a partly filled byte */
	if ((w * b) % 8) {
		i++;
		*s++ = fgetc(f);
	}

	/* check that we are on a 32 bit boundary; otherwise align */
	while (i % 4 != 0) {
		fgetc(f);
		i++;
	}
}

unsigned rle_dx = 0;	/* delta command horizontal offset */
unsigned rle_dy = 0;	/* delta command vertical offset */

/* checked against output from Borland Resource Workshop */
static void
rle4read(FILE *f, int w, int b, char *s)
{
	int i;
	int limit;
	int ch;
	unsigned cnt;
	int hi;

	limit = (w*b)/8;
	i = 0;
	hi = TRUE;
	/* set the line to white */
	memset(s, 0xff, limit+1);

	if (rle_dy) {
	    rle_dy--;
	    return;
	}

	if (rle_dx) {
	    for (; rle_dx>1; rle_dx-=2) {
		s++;
		i++;
	    }
	    if (rle_dx)
	    	hi = FALSE;
	}

	while (i<=limit) {
	    cnt = fgetc(f);
	    ch  = fgetc(f);
	    if (cnt == 0) { /* special operation */
		switch(ch) {
		    case 0:	/* EOL */
			return;	/* this is our way out */
		    case 1:	/* End of Bitmap */
		    	return;
		    case 2:	/* Delta */  /* untested */
			rle_dx = fgetc(f) + i*2 + (hi ? 1 : 0);
			rle_dy = fgetc(f);
			return;
		    default:	/* next cnt bytes are absolute */
		    	/* must be aligned on word boundary */
			if (!hi)
				fprintf(stderr,"em:graph: RLE4 absolute is not byte aligned\n");
			for (cnt = ch; cnt>0 && i<=limit; cnt-=2) {
		    	    i++;
			    *s++ = fgetc(f);
			}
			if (ch % 4)	/* word align file */
			    fgetc(f);
  		}
	    }
	    else {   /* cnt is repeat count */
		if (!hi) {   /* we are about to place the low 4 bits */
		    ch = ((ch>>4)&0x0f) | ((ch<<4)&0xf0); /* swap order */
		    i++;
		    *s = (*s & 0xf0) | (ch & 0x0f);
                    s++;
		    hi = TRUE;
		    cnt--;
		}
		/* we are about to place the high 4 bits */
		for (; cnt>1 && i<=limit; cnt-=2) { /* place the whole bytes */
		    i++;
		    *s++ = ch;
	        }
		if (cnt) { /* place the partial byte */
		    *s = (*s & 0x0f) | (ch & 0xf0);
		    hi = FALSE;
		}
	    }
  	}
}

/* untested */
static void
rle8read(FILE *f, int w, int b, char *s)
{
	int i;
	int limit;
	int ch;
	unsigned cnt;

	limit = (w*b)/8;
	i = 0;
	/* set the line to white */
	memset(s, 0xff, limit+1);

	if (rle_dy) {
	    rle_dy--;
	    return;
	}

	if (rle_dx) {
	    for (; rle_dx > 0; rle_dx--) {
		s++;
		i++;
	    }
	}

	while (i<=limit) {
	    cnt = fgetc(f);
	    ch  = fgetc(f);
	    if (cnt == 0) { /* special operation */
		switch(ch) {
		    case 0:	/* EOL */
			return;	/* this is our way out */
		    case 1:	/* End of Bitmap */
			return;
		    case 2:	/* Delta */  /* untested */
			rle_dx = fgetc(f) + i;
			rle_dy = fgetc(f);
			return;
		    default:	/* next cnt bytes are absolute */
			for (cnt = ch; cnt>0 && i<=limit; cnt--) {
		    	    i++;
			    *s++ = fgetc(f);
		        }
			if (ch % 2)	/* word align file */
			    fgetc(f);
		}
	    }
	    else { /* cnt is repeat count */
		for (; cnt>0 && i<=limit; cnt--) {
		    i++;
		    *s++ = ch;
		}
	    }
	}
}

static void
bmpgraph(FILE *f, char *filename, float emwidth, float emheight)
{
	struct bitmapfileheader bmfh;
	struct bitmapinfoheader bmih;

	#define ISBLACKSIZ 256
	unsigned char isblack[ISBLACKSIZ];
	unsigned char rr;
	unsigned char gg;
	unsigned char bb;
	unsigned char c = 0;

	char *line;
	char *pshexa;

	int clrtablesize;
	int i;
	int j;

	unsigned char omask;
	int oroll;

	unsigned char emask = 0;
	integer ewidth = 0;
	int isOS2;

        /* read the header of the file */
	fread(bmfh.type, 1, 2, f);
	bmfh.size = readinteger(f);
	bmfh.reserved1 = readhalfword(f);
	bmfh.reserved2 = readhalfword(f);
	bmfh.offbits = readinteger(f);
	if (feof(f)) {
		sprintf(errbuf, "em:graph: Unable to Read Valid BMP Header\n");
		specerror(errbuf);
		return;
	}

	bmih.size = readinteger(f);
	if (bmih.size == 12) { /* OS2 bitmap */
		isOS2 = TRUE;
		bmih.width = readhalfword(f);
		bmih.height = readhalfword(f);
		bmih.planes = readhalfword(f);
		bmih.bitcount = readhalfword(f);
		/* the following don't exist in OS2 format so fill with 0's */
		bmih.compression = RGB;
		bmih.sizeimage = 0;
		bmih.xpelspermeter = 0;
		bmih.ypelspermeter = 0;
		bmih.clrused = 0;
		bmih.clrimportant = 0;
	}
	else { /* Windows bitmap */
		isOS2 = FALSE;
		bmih.width = readinteger(f);
		bmih.height = readinteger(f);
		bmih.planes = readhalfword(f);
		bmih.bitcount = readhalfword(f);
		bmih.compression = readinteger(f);
		bmih.sizeimage = readinteger(f);
		bmih.xpelspermeter = readinteger(f);
		bmih.ypelspermeter = readinteger(f);
		bmih.clrused = readinteger(f);
		bmih.clrimportant = readinteger(f);
	}

	if (feof(f)) {
		sprintf(errbuf, "em:graph: Unable to Read Valid BMP Info");
		specerror(errbuf);
		return;
	}

	if (memcmp(bmfh.type, "BM", 2)) {
		sprintf(errbuf, "em:graph: Unknown BMP File Type");
		specerror(errbuf);
		return;
	}

	if ((bmih.compression == RLE4) && (bmih.bitcount != 4)) {
		sprintf(errbuf, "em:graph: Can't do BMP RLE4 with %d bits per pixel",
			bmih.bitcount);
		specerror(errbuf);
		return;
	}

	if ((bmih.compression == RLE8) && (bmih.bitcount != 8)) {
		sprintf(errbuf, "em:graph: Can't do BMP RLE8 with %d bits per pixel\n",
			bmih.bitcount);
		specerror(errbuf);
		return;
	}

	imagehead(filename,(int)bmih.width,(int)bmih.height,emwidth,emheight);

	/* determine the size of the color table to read */
	clrtablesize = 0;
	if (bmih.clrused == 0) {
		switch (bmih.bitcount) {
			case 1:
				clrtablesize = 2;
				break;
			case 4:
				clrtablesize = 16;
				break;
			case 8:
				clrtablesize = 256;
				break;
			case 24:
				break;
		}
	}
        else
		clrtablesize = bmih.clrused;

	if (clrtablesize > ISBLACKSIZ) {
		/* This is wrong, since we won't read the whole file below.
		   But we can't give correct output without more work,
		   and it's unlikely these specials are still in use.  */
		sprintf(errbuf,
		   "em color table size (%d) larger than %d; output incorrect",
		        clrtablesize, ISBLACKSIZ);
   		specerror(errbuf);
		clrtablesize = ISBLACKSIZ;
	}
	
	/* read in the color table */
	for (i = 0; i < clrtablesize; i++) {
		bb = fgetc(f);
		gg = fgetc(f);
		rr = fgetc(f);
		isblack[i] = (rr < 0xff) || (gg < 0xff) || (bb < 0xff);
		if (!isOS2)
			(void) fgetc(f);
	}

	line = (char *) mymalloc((integer)((bmih.width * bmih.bitcount) / 8) + 1);
	pshexa = (char *) mymalloc((integer)tobyte(bmih.width));

	/* output the postscript image size preamble */
	cmdout("/picstr");
	numout((integer)tobyte(bmih.width));
	cmdout("string def");

	numout((integer)bmih.width);
	numout((integer)bmih.height);
	numout((integer)1);

	cmdout("[");
	numout((integer)bmih.width);
	numout((integer)0);
	numout((integer)0);
	numout((integer)bmih.height);
	numout((integer)0);
	numout((integer)bmih.height);
	cmdout("]");

	nlcmdout("{currentfile picstr readhexstring pop} image");

	if (bmih.bitcount == 1) {
		if (bmih.width%8)
			emask = (1<<(8-(bmih.width%8)))-1;	/* mask for edge of bitmap */
		else
			emask = 0;
		ewidth = tobyte(bmih.width);
	}

	/* read in all the lines of the file */
	for (i = 0; i < bmih.height; i++) {
		memset(pshexa,0xff,tobyte(bmih.width));
		switch (bmih.compression) {
		    case RGB:
			rgbread(f, (int) bmih.width, (int) bmih.bitcount, line);
			break;
		    case RLE4:
			rle4read(f, (int) bmih.width, (int) bmih.bitcount, line);
			break;
		    case RLE8:
			rle8read(f, (int) bmih.width, (int) bmih.bitcount, line);
			break;
		    default:
			sprintf(errbuf,"em:graph: Unknown BMP compression\n");
			specerror(errbuf);
			return;
		}

		omask = 0x80;
		oroll = 7;

		if (bmih.bitcount == 1) {
		    if (isblack[0])
			for (j = 0; j < ewidth; j++)
				pshexa[j] = line[j];
		    else
			for (j = 0; j < ewidth; j++)
				pshexa[j] = ~line[j];
		    pshexa[ewidth-1] |= emask;
		}
		else {
		    for (j = 0; j < bmih.width; j++) {
			switch (bmih.bitcount) {
				case 4:
					c = line[j>>1];
					if (!(j&1))
						c >>= 4;
					c = isblack[ c & 0x0f ];
					break;
				case 8:
					c = isblack[ (int)(line[j]) ];
					break;
				case 24:
					rr = line[j*3];
					gg = line[j*3+1];
					bb = line[j*3+2];
					c = (rr < 0xff) || (gg < 0xff) || (bb < 0xff);
					break;
			}
			if (c)
			    pshexa[j/8] &= ~omask;
			else
			    pshexa[j/8] |= omask;
			oroll--;
			omask >>= 1;
			if (oroll < 0) {
			    omask = 0x80;
			    oroll = 7;
			}
		    }
		}
		newline();
		mhexout((unsigned char *) pshexa,(long)tobyte(bmih.width));
	}
	imagetail();
	free(pshexa);
	free(line);
}
/* ------------------------------------------------------------------------ */

#define PCX 0
#define MSP 1
#define BMP 2
const char *extarr[]=
{ ".pcx", ".msp", ".bmp", NULL };

static void
emgraph(char *filename, float emwidth, float emheight)
{
	char fname[500];
	int filetype;
	FILE *f;
	char *env;
	char id[4];
	int i;

	strcpy(fname, filename);

	/* find the file */
	f = search(figpath, fname, READBIN);
	if (f == (FILE *)NULL) {
   	    if ( (env = getenv("DVIDRVGRAPH")) != NULL )
#ifdef KPATHSEA
		f = search((kpse_file_format_type)env,filename,READBIN);
#else
		f = search(env,filename,READBIN);
#endif
	}
	/* if still haven't found it try adding extensions */
	if (f == (FILE *)NULL) {
	    i = 0;
	    while (extarr[i] != NULL) {
		strcpy(fname, filename);
		strcat(fname, extarr[i]);
		f = search(figpath, fname, READBIN);
		if (f == (FILE *)NULL) {
	    	    if ( (env = getenv("DVIDRVGRAPH")) != NULL )
#ifdef KPATHSEA
			f = search((kpse_file_format_type)env,filename,READBIN);
#else
			f = search(env,filename,READBIN);
#endif
		}
		if (f != (FILE *)NULL)
		    break;
		i++;
	    }
	}

	filetype = -1;
	if (f != (FILE *)NULL) {
	    for (i=0; i<4; i++) {
		id[i] = readquarterword(f);
	    }
	    if ( (id[0] == 0x0a) && (id[2] == 0x01) )
		filetype = PCX;
	    if (!memcmp(id, "DanM", 4))
		filetype = MSP;
	    if (!memcmp(id, "LinS", 4))
		filetype = MSP;
	    if (!memcmp(id, "BM", 2))
		filetype = BMP;
	    fseek(f, 0L, SEEK_SET);
	}

	switch (filetype) {
		case PCX:
			pcxgraph(f, fname, emwidth, emheight);
			break;
		case MSP:
			mspgraph(f, fname, emwidth, emheight);
			break;
		case BMP:
			bmpgraph(f, fname, emwidth, emheight);
			break;
		default:
			snprintf(fname, sizeof(fname),
                                 "em:graph: %s: File not found", filename);
			error(fname);
	}
	if (f != (FILE *)NULL)
	    close_file(f);
}

#else
void
emspecial(char *p)
{
	sprintf(errbuf,"emTeX specials not compiled in this version");
	specerror(errbuf);
}
#endif /* EMTEX */
