static char copyr[] = "Copyright 1990 by Dr. R. Brooks Van Horn, Jr.";
/*
	This version of the Print Screen routine is for the HP Laser
	series printers and a VGA or EGA video adaptor with the higher
	resolution video modes, including super VGA.
*/

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

#define	 dither	  6
#define	 esc	 27
#define	 LENMAX	 770

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

/* data needed */
static int  dpi = 300;
static long Columns;
static int  Max_Rows;
static int  Max_Cols;
static char Video_Mode;
int  far * dkb	 = 0x0000041a;	  /* DOS Keyboard Area */
char far * dds	 = 0x00000000;	  /* DOS Data Segment */
char far * vds	 = 0x00000500;	  /* Video Data Segment */
char far * Regen = 0xa0000000;	/* Video Screen Address */
char buffer[LENMAX], pline[256];
int  h_ratio, h_fract, v_ratio, v_fract;
static int number;

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

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 ("Laser.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 ("Laser Graphics Print Screen TSR");
    puts ( copyr );
    puts ("has now been installed.");
    keep (0, pgm_size);
}

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

void interrupt PrtScrn (void)
{
    int kb_head = *dkb, kb_tail= *(dkb+1);
    int row, col, cbit, error;
    int total, pin, i, j, k, rtot, extra, tcheck, begin, lenth;
    char pat, pat1, pat2, pat3, zero = 0x00;
    char crlf[]	 = { 0x0d, 0x0a };  /* do cr/lf cmd */
    char init_prn[] = {
	esc, 'E',			       /* Reset	   */
	esc, '&', 'l', '0', 'O',	       /* Portrait */
	esc, '*', 't', '3', '0', '0', 'R',     /* dpi resolution */
	esc, '&', 'a', '6', '5', '0', 'H',     /* move cursor from left */
	esc, '&', 'a', '2', '0', '0', 'V',     /* and from the top */
	esc, '*', 'r', '1', 'A'		       /* strt grphs @ cursor pos */
    };
    /* Transmit xxx bytes of data following command */
    char graph[] = { esc, '*',	'b',  'x',  'x',  'x',	'W', 0, 0 };
    char width[] = "		   ";
    char eop[] = { esc,	 '*',  'r',  'B',	/* end graphics */
		    12,				/* form feed */
		   esc,	 'E'			/* and reset printer */
		 };
    union REGS regs;



    if (++number > 2)
	*vds = 0;

    if (*vds != 0) { /* if printing is active then exit */
	sound ( 950, 18 );
	return;
    }

    Video_Mode = *(dds + 0x0449);
    if (Video_Mode <= 3) {/* if we are not in a graphics mode */
	int05();
	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 */
    Max_Rows  = (int) *(dds + 0x0484) + 1;
    Max_Cols  = ((int) *(dds + 0x044a)) + (((int) *(dds + 0x0000044b)) << 8);
    Max_Rows *= (int) *(dds + 0x0485); /* 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); /* 6.5" * dots per inch */
    /* 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;
    }
    /* now get the actual bytes to be sent to the printer per pass */
    tcheck = ((((10 * v_ratio + v_fract) * Max_Cols) / 10) + 7) >> 3;

    /* initialize the printer */
    regs.byte.ah = 1;
    regs.word.dx = 0;
    int86 ( 0x17, &regs, &regs );  /* initialize port LPT0 */

    if (Print ( 33, init_prn )) { /* reset the	printer */
	*vds = 0;
	return;
    }

    /*
       We now process the video screen from the left to the right
       on the columns.	The given pixel values will be dithered to
       provide a shading like effect for the black and white laser
       printer.
    */

    total = 0;
    for ( col = 0; col < Max_Cols; ++col) {
	if ((col % 100) == 0)
	    sound ( 1500, 1 );
	/* check to see if a key has been pressed */
	if ((*dkb != kb_head) || (*(dkb+1) != kb_tail)) {
	    *dkb = kb_head;
	    *(dkb+1) = kb_tail;
	    Print ( 7, eop );
	    goto GET_OUT;
	}
	/* read a row of pixels for this column */
	for (i = 0, row = Max_Rows-1; row >= 0; --row, ++i )
	    buffer[i] = Read_Pixel ( row, col );

	/* clear out the printer line for packing */
	memset ( pline, 0, 256 );

	/*
	   now build the 'tcheck' bytes to send to the printer
	   remembering that we will be multiline processing too.
	*/

	pin = h_ratio;
	/* add fraction in to the sum */
	total += h_fract;
	if (total >= 10) {  /* adjust for fractionals */
	   total -= 10;
	   ++pin;
	}

	/* This column will be processed 'pin' times in dither length */
	for (k = 0; k < pin; ++k) {
	    extra = 0;
	    rtot = 0;
	    begin = 0;
	    lenth = 1;
	    for (i = 0; i < Max_Rows; ++i) {
		rtot += v_ratio;
		extra += v_fract;
		if (extra >= 10) {
		    extra -= 10;
		    ++rtot;
		}
		if (buffer[i])
		    lenth = rtot;
		PinSet ( i, k, begin, rtot );
		begin = rtot;
	    }
	    /* declare number of items to send to printer */
	    lenth = (lenth + 7) >> 3;
	    itoa ( lenth, width, 10);
	    if (lenth >= LENMAX) {
		puts (width);
		lenth = LENMAX;
		sound (1100,36);
	    }
	    strcpy ( &graph[3], width );   /* copy # bytes to txmt */
	    strcat (graph, "W");
	    for (j = 0; graph[j]; ++j) ;   /* get length of string */
	    if ( Print ( j, graph ) )
		goto GET_OUT;
	    if ( Print ( lenth, pline ) )
		goto GET_OUT;
	} /* end for-loop on duplicate rows */
    } /* end for-loop on cols */
    Print ( 7, eop );
GET_OUT:
    sound ( 1500, 9 );
    *vds = 0;
    number = 0;
    return;
}

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

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

    if (row >= Max_Rows || col >= Max_Cols)
	return 0;

    if (Video_Mode < 8) {  /* if CGA let BIOS do it */
	regs.word.ax = 0x0d00;
	regs.word.bx = 0;
	regs.word.dx = row;
	regs.word.cx = col;
	int86 ( 16, &regs, &regs );
	color = regs.byte.al;
    }
    else {
	mask = 0x80 >> (col % 8);	/* 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;  /* get bit value	*/
	    temp = temp ? 1 : 0 ;	      /* set bit if color match */
	    color = (color << 1) | temp;      /* save plane results */
	}
	outp (0x3c4,2);			/* restore to Write Mode 0 */
	outp (0x3c5,15);		/* with all planes enabled */
    }
    return color;
}

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

void PinSet ( ndx, sect, b1, b2 )
int  ndx;	 /* index to the color pattern to be smoothed */
int  sect;	 /* the row duplication index */
int  b1;	 /* the beginning bit to set */
int  b2;	 /* the last bit to set */
{
    char pats[dither][dither];
    int	 bit, j, k, wd, i, kmod;
    char I, R, G, B, pat = buffer[ndx];

    if (pat == 0)   /* if nothing to set then return */
	return;

    k = v_ratio >= dither ? dither : (v_ratio + 1);
    switch ( k ) {
	case 1:
	case 2:
	case 3:
	    I = R = G = B = pat;
	    kmod = 1;
	    break;
       case 4:
       case 5:
	    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;
    }
    bit = 0;
    for (i = 0; i < k; ++i) {
	for (j = 0; j < k; ++j, ++bit) {
	    switch ( bit % 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;
	    }
	}
    }
    /* now create bytes for printing with these patterns */
    for (bit = b1; bit < b2; ++bit) {
	k = bit - b1;		    /* row index to pats array */
	if (pats[k][sect]) {	    /* if dithering has something here */
	    wd = bit / 8;	    /* word index */
	    i  = 7 - ( bit % 8 );   /* bit index */
	    pline[wd] |= (0x01 << i); /* and the bit mask */
	}
    }

    return;
}

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

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 ( 0x17, &regs, &regs );
	result = (int) regs.byte.ah;	/* printer return code */
	if (result & 0x0029) {
	    /* printer error */
	    return ( result );
	}
    }
    return 0;
}
