static char copyr[] = "Copyright 1990 by Dr. R. Brooks Van Horn, Jr.";

/*
	This version of the Print Screen routine is for the Epson LQ 2500
	series printer and a VGA or EGA video adaptor with the higher
	resolution video modes, including super VGA.  These C-routines
	were written using Mix's Power C.
*/

#include <malloc.h>
#include <bios.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>

#define	 DITHER	   7	    /* dimension of dithering matrix */
#define	 dpi	 180	    /* printer resolution in dots per inch */
#define	 pmode	  39	    /* 24 pin mode for the dpi resolution */
#define	 esc	  27

/* functions needed in program */
void interrupt (far * int05)(void);
void interrupt PrtScrn (void);
int Print ( int, char * );
int Graphics ( char, char, char );
void PinSet ( char *, char *, char *, int, int );
char Read_Pixel (int, int);

/* static global data needed */
static int Max_Rows;
static int Max_Cols;
static int number;
static int phase;
static char Video_Mode;
static long Columns;
int  h_ratio, h_fract, v_ratio, v_fract;
char far * dds	 = 0x00000000;	  /* DOS Data Segment */
int  far * dkb	 = 0x0000041a;	  /* DOS Keyboard Area */
char far * vds	 = 0x00000500;	  /* Video Data Segment */
char far * Regen = 0xa0000000;	  /* Video Screen Address */

struct PINS {
	int	first [12];
	int	last  [12];
	char	value [12];
} head;


/*----------------------------------------------------------------------*/

main ()
{
    unsigned int pgm_size;
    char far * ptr = 0x00000467; /* use cassette data area */

    number = 0;
    *vds = 0;
    if (*ptr == 'b' && *(ptr+1) == 'v' && *(ptr+2) == 'h') {
	puts ("Epson24.Exe TSR is already resident.");
	exit (1);
    }

    int05 = getvect (0x05);
    *(ptr  ) = 'b';
    *(ptr+1) = 'v';
    *(ptr+2) = 'h';
    setvect (0x05, PrtScrn);
    pgm_size = farsetsize(0);
    puts ("Epson LQ-2500 Graphics Print Screen TSR");
    puts ( copyr );
    puts ("has now been installed.");
    keep (0, pgm_size);
}

/*----------------------------------------------------------------------*/

void interrupt PrtScrn (void)
{
    int row, col, index, height, cbit, error, tsum, kb_head, kb_tail;
    int col_to_do, total, pin, i, j, k, rtot, extra, tcheck, kntr;
    char pat, pat1, pat2, pat3, zero = 0x00;
    char init_prn[]  = { esc, '@' };	      /* inititialize printer */
    char graph[] = { esc, '*', pmode, 0, 0 }; /* 24 pin graphics 180 dpi */
    char set_lf_180[] = { esc, '3', 24 };     /* set line feed to 24/180" */
    char crlf[]	 = { 0x0d, 0x0a };	      /* do cr/lf cmd */
    char eop[] = { 0x0c, esc, '@' };	      /* reset & eject page */
    char ahp[] = { esc, '$', 60, 0 };	      /* move 1" from left margin */
    union REGS regs;

    /* if user pushed called us twice with no result then do it now */
    if (++number > 1)
	*vds = 0;
    if (*vds != 0) { /* if printing is active then exit */
	sound (1500,5);
	sound (1000,5);
	sound (1500,5);
	return;
    }
    number = 0;
    Video_Mode = *(dds + 0x0449);
    if ((Video_Mode < 4) || (Video_Mode == 7)) {/* if not a graphics mode */
	int05();
	*vds = 0;
	return;
    }
    /* we are now in a position to do a print screen */
    *vds = 0xff;      /* mark print screen status flag for others */
    enable();	      /* enable further interrupts */
    kb_head = *(dkb);
    kb_tail = *(dkb+1);
    Max_Rows  = (int) *(dds + 0x0484) + 1;
    Max_Cols  = ((int) *(dds + 0x044a)) + (((int) *(dds + 0x0000044b)) << 8);
    height    = (int) *(dds + 0x0485);
    Max_Rows *= height; /* convert text chars to pixels */
    Columns   = (long) Max_Cols;
    Max_Cols *= 8;
    /*
       The dimensions of the screen were obtained from the bios data area.
       The next step is to determine the size of the picture based on
       reserving a one inch margin on all sides and trying to use a 5 x 5
       dithering matrix. We use the term vertical to represent the
       direction along the 8.5" side of the printer paper and horizontal
       along the 11" side.

			 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
			 ³		   1"		   ³
			 ³	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿   ³
			 ³	³	     Top	  ³	 ³
			 ³	³			  ³	 ³
			 ³	³			  ³	 ³
	       <-------	 ³ 1"³	   Picture	  ³1" ³ 8.5"(v)
		 Paper	 ³	³			  ³	 ³
		 Feed	 ³	³Left		  ³	 ³
			 ³	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ   ³
			 ³		   1"		   ³
			 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
				      11" (h)
    */
    v_ratio = (13 * dpi) >> 1; /* setup for 6.5" along vertical side */
    /* but adjust if it is too big for dithering matrix */
    v_ratio = v_ratio > (DITHER * Max_Rows) ? (DITHER * Max_Rows) : v_ratio;
    h_ratio = (4 * v_ratio + 1) / 3; /* 4:3 ratio adj for length */
    /* get the fraction part of the ratio for vertical and horizontal */
    v_fract = v_ratio % Max_Rows;
    v_fract = (10 * v_fract + (Max_Rows>>1)) / Max_Rows; /* in [0,9] */
    h_fract = h_ratio % Max_Cols;
    h_fract = (10 * h_fract + (Max_Cols>>1)) / Max_Cols; /* in [0,9] */
    /* then get the integer part of the ratios */
    v_ratio = v_ratio / Max_Rows;
    h_ratio = h_ratio / Max_Cols;
    /* now check for overflows in rounding too high */
    if (v_fract >= 10) {
	v_ratio++;
	v_fract -= 10;
    }
    if (h_fract >= 10) {
	h_ratio++;
	h_fract -= 10;
    }
    /* define the number of bytes to send printer per line of graphics */
    tcheck = ((10 * v_ratio + v_fract) * Max_Rows) / 10;
    graph[3] = tcheck & 0x00ff;
    graph[4] = tcheck >> 8;
    /* initialize the printer */
    regs.byte.ah = 1;
    regs.word.dx = 0;
    int86 ( 23, &regs, &regs );	 /* initialize port LPT0 */
    if ( Print ( 2, init_prn ) ) /* reset the  printer */
	goto GET_OUT;
    for (k = 0; k < 5; ++k)	 /* make a 1" top margin */
	if ( Print ( 2, crlf ) )
	    goto GET_OUT;
    if ( Print ( 3, set_lf_180 ) )
	goto GET_OUT;
    /* for the 24 pin printer, get the number of column pixels/pass */
    col_to_do = 240 / (10 * h_ratio + h_fract);
    kntr = 0;
    for ( col = 0; col < Max_Cols; col += col_to_do ) {
	/* move print head 1" from the left margin */
	if ( Print ( 4, ahp ) )	 /* advance to horizontal position */
	    goto GET_OUT;
	/* check for key pressed */
	if ((*dkb != kb_head) || (*(dkb+1) != kb_tail)) {
	    *dkb = kb_head;	 /* restore head of kbd */
	    *(dkb+1) = kb_tail;	 /* restore tail of kbd */
	    Print ( 3, eop );
	    goto GET_OUT;
	}
	/* declare number of items to send to printer */
	tsum = 0;
	if ( Print ( 5, graph ) )
	    goto GET_OUT;
	rtot = 0;
	extra = 0;
	for (row = Max_Rows-1; row >= 0; --row ) {
	    total = 0;
	    pin = 0;
	    k = 0;
	    /* for this row, get the next col_to_do column values */
	    for ( cbit = 0; cbit < col_to_do ; ++cbit ) {
		/* read a pixel's color */
		pat = Read_Pixel ( row, (col+cbit) );
		/* replicate that color at h_ratio pins */
		pin += h_ratio;
		/* add fraction in to the sum */
		total += h_fract;
		if (total >= 10) {  /* adjust for fractionals */
		    total -= 10;
		    ++pin;
		}
		head.first [cbit] = k;	       /* 1st pin location */
		head.last  [cbit] = pin;       /* last pin location */
		head.value[cbit]  = pat;       /* pen color */
		k = pin;
	    }
	    /*
		We now have the pins loaded, next determine the
		three dithered bytes to use for this print head.
	    */
	    rtot = v_ratio;		 /* vert no to duplicate */
	    extra += v_fract;		 /* spill-over */
	    if (extra >= 10) {
		++rtot;
		extra -= 10;
	    }
	    /*
	       combine the col_to_do pins into 3 bytes to be printed
	    */
	    for ( k = 0; k < rtot; k++) {
		if (++tsum <= tcheck) {
		    PinSet ( &pat1, &pat2, &pat3, k, col_to_do );
		    if ( Graphics ( pat1, pat2, pat3 ) )
			goto GET_OUT;
		}
	    } /* end for k loop on PinSet */
	} /* end for-loop on rows */
	for ( ; tsum <= tcheck; ++tsum ) {
	    Graphics ( zero, zero, zero );
	}
	/* do a cr/lf on the printer at the end of line of graphics */
	if ( Print (2, crlf) )
	    goto GET_OUT;
    } /* end for-loop on cols */
    Print ( 3, eop );	  /* reset the printer and do a form feed */
GET_OUT:
    *vds = 0;
    return;
}

/*----------------------------------------------------------------------*/

char Read_Pixel ( int row, int col )
{
    long offset = (long)(col>>3) + (long) row * Columns;
    int	 plane;
    char color = 0x00, mask, temp;
    union REGS regs;

    if ((row > Max_Rows) || (col > Max_Cols))
	return 0;
    if (Video_Mode < 8) { /* for non-ega/vga modes do */
	/* a ROM BIOS call to get color */
	regs.word.ax = 0x0d00;
	regs.word.bx = 0;
	regs.word.cx = col;
	regs.word.dx = row;
	int86 ( 16, &regs, &regs );
	color = regs.byte.al;
    }
    else {
	/* use ega/vga registers to get the color */
	mask = 0x80 >> (col % 8);     /* the bit mask for addressed byte */
	outp (0x3ce,4);		      /* select the Map Mask register */
	for (plane = 3; plane >= 0; --plane) {
	    outp (0x3cf,plane);	      /* address the bit plane */
	    temp = *(Regen + offset) & mask; /* mask off the color bit */
	    temp = temp ? 1 : 0 ;     /* set bit if color match */
	    color = (color << 1) | temp; /* save plane results */
	}
	outp (0x3c4,2);	  /* restore adaptor to Write Mode 0 */
	outp (0x3c5,15);  /* with all planes enabled */
    }
    return color;
}

/*----------------------------------------------------------------------*/

void PinSet ( pat1, pat2, pat3, sect, maxc )
char *pat1, *pat2, *pat3;  /* the three bytes to be sent to the printer */
int  sect;		   /* which relative print head column we are on */
int  maxc;		   /* the number of entries in the head structure */
{
    char pat;
    char pats[DITHER][DITHER]; /* the dithering matrix */
    char R, G, B, I;
    int	 bit, k, ndx, b1, b2, wd, i, j, top, kmod;
    union COVER {    /* equivalence is needed for the 24 pins */
	long	lword;
	char	bytes[4];
    } cover;


    top = v_ratio;
    if (v_fract) ++top;
    top = top < DITHER ? top : DITHER;
    *pat1 = *pat2 = *pat3 = 0x00;
    for (ndx = 0; ndx < maxc; ++ndx) {
	/* retrieve the color for this pin section */
	pat = head.value[ndx];
	b1  = head.first[ndx];
	b2  = head.last [ndx];
	if (!pat)  /* skip remainder if this is a zero byte */
	   continue;
	/* get planes that are on in the pattern */
	switch (top) {
	    case 1:
	    case 2:
		R = pat;
		kmod = 1;
		break;
	    case 3:
		R = (pat & 0x03); /* B or R */
		G = (pat & 0x0c); /* G or I */
		kmod = 2;
		break;
	    case 4:
		B = pat & 0x09;	 /* B or I */
		R = pat & 0x0a;	 /* R or I */
		G = pat & 0x0c;	 /* G or I */
		I = pat & 0x08;	 /* I	   */
		kmod = 3;
		break;
	    default:
		B = pat & 0x01;
		R = pat & 0x02;
		G = pat & 0x04;
		I = pat & 0x08;
		kmod = 4;
	}
	for (i = 0, k = 0; i < top; ++i) {
	    for (j = 0; j < top; ++j, ++k) {
		switch ( k % kmod ) {
		    case 0:
			pats[i][j] = R;
			break;
		    case 1:
			pats[i][j] = G;
			break;
		    case 2:
			pats[i][j] = B;
			break;
		    case 3:
			pats[i][j] = I;
		} /* end switch block */
	    } /* end for j loop */
	}  /* end for i loop */

	/* now create bytes for printing with these patterns */
	cover.lword = 0L;
	for (bit = b1; bit < b2; ++bit) {
	    k = bit - b1;  /* col index to pats array */
	    if (pats[k][sect]) {  /* if we are to dither this */
		j = bit;
		wd = j / 8;	/* word index */
		i  = 7 - ( j % 8 ); /* bit index */
		cover.bytes[wd] |= (0x01 << i); /* and the bit mask */
	    }
	}
	*pat1 |= cover.bytes[0];
	*pat2 |= cover.bytes[1];
	*pat3 |= cover.bytes[2];
    }
    return;
}

/*--------------------------------------------------------------*/

int Graphics ( char byte1, char byte2, char byte3 )
{
    char array[3];

    array[0] = byte1;
    array[1] = byte2;
    array[2] = byte3;
    return ( Print (3, array) );
}

/*--------------------------------------------------------------*/

int Print ( int items, char * strng )
{
    int i;
    int result;
    union REGS regs;

    for (i= 0; i < items; ++i) {
	regs.byte.ah = 0;	    /* function number */
	regs.byte.al = strng[i];    /* character to print */
	regs.word.dx = 0;	    /* printer Lpt0 */
	int86 ( 23, &regs, &regs );
	result = (int) regs.byte.ah;	  /* printer return code */
	if (result & 0x0029) {
	    /* printer error */
	    sound ( 750, 27 );
	    return ( result );
	}
    }
    return 0;
}
