/* RIPFONT.C by John Elliott, November 2003.
   This program is public domain, please copy! 
   Note: Must be compiled by a large-model compiler (I used Pacific C).
*/

#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <time.h>


typedef void (*DRAWFUNC)(int row, int col, unsigned char c);

void drawch_cga(int row, int col, unsigned char c);
void drawch_herc(int row, int col, unsigned char c);
void drawch_3270(int row, int col, unsigned char c);
void putscr(unsigned char *buf, int len);
void getscr(unsigned char *buf, int len);

typedef struct vid_data
{
	int text_originx;
	int text_originy;
	int gfx_offsetx;
	int gfx_offsety;
	int char_height;
	int char_width;
	char *filename;
	int textmode;
	int gfxmode;
	int tscrlen;
	int char_wb;
	unsigned int vid_seg;
	unsigned int vid_len;
	DRAWFUNC drawc;
} VID_DATA;

VID_DATA vd_pc3270 = { 6, 6, 8, 13, 14, 9, "3270font.psf", 2, 6, 2048, 2, 0xB800, 0x4000, drawch_3270 };
VID_DATA vd_cga    = { 0, 0, 0,  0,  8, 8, "cgafont.psf",  0, 5, 1024, 1, 0xB800, 0x4000, drawch_cga };
VID_DATA vd_mda    = { 0, 0, 0,  0, 14, 9, "mdafont.psf",  7,-7, 2048, 2, 0xB000, 0x8000, drawch_herc };

VID_DATA *gl_vd;
int gl_herc = 1;

typedef struct psf2_header
{
	long magic;
	long version;
	long hdrlen;
	long flags;
	long nchars;
	long charlen;
	long height;
	long width;
} PSF2_HEADER;

PSF2_HEADER psf2;

#define PSF2_MAGIC (0x864ab572L)


unsigned char textscr[2048];
unsigned char gfxscr[32768];
unsigned char fontbuf[8192];

typedef unsigned char far *PBYTE;
PBYTE cdata;
PBYTE graftabl;
PBYTE *int1f;

void sleep(int secs)
{
	long t, t1;

	time(&t);
	while (secs)
	{
		time(&t1);
		if (t != t1) 
		{ 
			--secs; 
			t = t1;
		}
	}
}

void setcurpos(int row, int col)
{
	union REGS rg;

	rg.x.ax = 0x200;
	rg.x.bx = 0x000;
	rg.h.dl = col;
	rg.h.dh = row;
	int86(0x10, &rg, &rg);
}

void drawch(int row, int col, unsigned char c)
{
	union REGS rg;

	rg.x.ax = 0x200;
	rg.x.bx = 0x000;
	rg.h.dl = col;
	rg.h.dh = row;
	int86(0x10, &rg, &rg);

	rg.h.ah = 9;
	rg.h.al = c;
	rg.x.bx = 15;
	rg.x.cx = 1;
	int86(0x10, &rg, &rg);
}

void drawch_cga(int row, int col, unsigned char c)
{
	*int1f = fontbuf + c * gl_vd->char_height * gl_vd->char_wb;
	drawch(row, col, 0x80);
}

void drawch_3270(int row, int col, unsigned char c)
{
	int nr, nc;
	unsigned char smask, dmask, *chr, *chrbase;
	PBYTE scr;
	union REGS rgi;

	
	rgi.x.ax = 0x0C01;
	rgi.x.dx = row * gl_vd->char_height + gl_vd->gfx_offsety;
	chrbase = fontbuf + (c * gl_vd->char_wb * gl_vd->char_height);

	for (nr = 0; nr < gl_vd->char_height; nr++)
	{
		chr  = chrbase + nr * gl_vd->char_wb;
		rgi.x.cx = col * gl_vd->char_width + gl_vd->gfx_offsetx;
		scr = MK_FP(gl_vd->vid_seg, 0);
		scr += (80 * (rgi.x.dx/2));
		if (rgi.x.dx & 1) scr += 0x2000;
		scr += (rgi.x.cx / 8);
		smask = 0x80;
		dmask = 0x80 >> (rgi.x.cx % 8);

		for (nc = 0; nc < gl_vd->char_width; nc++)
		{
			if (chr[0] & smask) scr[0] |= dmask;
			smask = smask >> 1;
			if (smask == 0)
			{
				smask = 0x80;
				chr++;
			}
			dmask = dmask >> 1;
			if (dmask == 0)
			{
				dmask = 0x80;
				scr++;
			}
			rgi.x.cx++;
		}
		rgi.x.dx++;
	}	
}

void drawch_herc(int row, int col, unsigned char c)
{
	int nr, nc;
	unsigned char smask, dmask, *chr, *chrbase;
	PBYTE scr;
	union REGS rgi;

	
	rgi.x.ax = 0x0C01;
	rgi.x.dx = row * gl_vd->char_height + gl_vd->gfx_offsety;
	chrbase = fontbuf + (c * gl_vd->char_wb * gl_vd->char_height);

	for (nr = 0; nr < gl_vd->char_height; nr++)
	{
		chr  = chrbase + nr * gl_vd->char_wb;
		rgi.x.cx = col * gl_vd->char_width + gl_vd->gfx_offsetx;
		scr = MK_FP(gl_vd->vid_seg, 0);
		scr += (90 * (rgi.x.dx/4)) + (0x2000 * (rgi.x.dx % 4));
		scr += (rgi.x.cx / 8);
		smask = 0x80;
		dmask = 0x80 >> (rgi.x.cx % 8);

		for (nc = 0; nc < gl_vd->char_width; nc++)
		{
			if (chr[0] & smask) scr[0] |= dmask;
			smask = smask >> 1;
			if (smask == 0)
			{
				smask = 0x80;
				chr++;
			}
			dmask = dmask >> 1;
			if (dmask == 0)
			{
				dmask = 0x80;
				scr++;
			}
			rgi.x.cx++;
		}
		rgi.x.dx++;
	}	
}



void drawstr(int r, int c, char *s)
{
	while (*s)
	{
		drawch(r,c,*s);
		++s;
		++c;
		if (c == 40) 
		{
			c = 0;
			++r;
		}
	}
}

void mkdisp(int gfx)
{
	int r,c,n;
	unsigned char ch = 0;

	if (gfx)
	{
		memset(gfxscr, 0, gl_vd->vid_len);
		putscr(gfxscr, gl_vd->vid_len);
	}

	for (r=c=n=0; n < 256; n++)
	{
		if (gfx) (*gl_vd->drawc)(r,c,ch);
		else	 drawch(gl_vd->text_originy + r, gl_vd->text_originx +c, ch);
		++c;
		if (c == 40) { c=0; ++r; }
		++ch;
	}
	
}
/* Using CGA mode 6, get the current ROS+GRAFTABL font combination.
 * Not very suitable for 9x14 fonts, but the best we can do. */

void getfont()
{
	int n;
	unsigned char *pch;
	unsigned char *scr = MK_FP(gl_vd->vid_seg, 0);

	for (n = 0; n < 256; n++)
	{
		pch = fontbuf + n * gl_vd->char_wb * gl_vd->char_height;
		drawch(0, 0, n);
		pch[0*gl_vd->char_wb] = scr[0x0000];
		pch[1*gl_vd->char_wb] = scr[0x2000];
		pch[2*gl_vd->char_wb] = scr[0x0050];		
		pch[3*gl_vd->char_wb] = scr[0x2050];
		pch[4*gl_vd->char_wb] = scr[0x00A0];		
		pch[5*gl_vd->char_wb] = scr[0x20A0];
		pch[6*gl_vd->char_wb] = scr[0x00F0];		
		pch[7*gl_vd->char_wb] = scr[0x20F0];
	}
}


static unsigned char herc_gfx_init[] = 
{
	0x38, 0x2d, 0x30, 0x08, 0x5a, 0x00, 0x57, 0x57, 0x02,
	0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

static unsigned char herc_txt_init[] = 
{
	0x61, 0x50, 0x52, 0x0f, 0x19, 0x06, 0x19, 0x19, 0x02,
	0x0d, 0x0b, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*
static void herc_cls()
{
  unsigned char * screen = MK_FP(0xB000, 0);

  memset(&screen[0], 0, 0x1eef);
  memset(&screen[0x2000], 0, 0x1eef);
  memset(&screen[0x4000], 0, 0x1eef);
  memset(&screen[0x6000], 0, 0x1eef);
}*/


static void herc_gfxmode()
{
  int i;
  union REGS regs;

  regs.x.ax = 7;
  int86(0x10, &regs, &regs);
  outp(0x3b8,0);
  outp(0x3bf,3);
  for (i=0; i<18; i++) 
  {
    outp(0x3b4,i);
    outp(0x3b5,herc_gfx_init[i]);
  }
  outp(0x3b8,6);
//  herc_cls();
  outp(0x3b8,14);
}


static void herc_txtmode()
{
  int i;
  union REGS regs;

  regs.x.ax = 7;
  int86(0x10, &regs, &regs);
  outp(0x3bf,0);
  outp(0x3b8,0x28);
  for (i=0; i<18; i++) {
    outp(0x3b4,i);
    outp(0x3b5,herc_txt_init[i]);
  }
}


void setmode(int m)
{
	union REGS rg;

	if (m == -7 && gl_herc)	/* Herc graphics mode */
	{
		herc_gfxmode();
		return;
	}
	if (m == 7 && gl_herc)
	{
		herc_txtmode();
		return;
	}
	if (m < 0) m = -m;	
	rg.x.ax = m & 0xFF;
	int86(0x10, &rg, &rg);
}

unsigned char getmode()
{
	union REGS rg;

	rg.x.ax = 0x0F00;
	int86(0x10, &rg, &rg);
	return rg.h.al;
}


void putscr(unsigned char *buf, int len)
{
	void *scr = MK_FP(gl_vd->vid_seg, 0);
	memcpy(scr, buf, len);
}


void getscr(unsigned char *buf, int len)
{
	void *scr = MK_FP(gl_vd->vid_seg, 0);
	memcpy(buf, scr, len);
}

void dotest(int slow)
{
	setmode(gl_vd->textmode);
	mkdisp(0);
	getscr(textscr, gl_vd->tscrlen);
	setmode(gl_vd->gfxmode);
	mkdisp(1);
	getscr(gfxscr, gl_vd->vid_len);
	while (1)
	{
		setmode(gl_vd->textmode);
		putscr(textscr, gl_vd->tscrlen);
		if (slow) sleep(1);
		if (kbhit()) break;
		setmode(gl_vd->gfxmode);
		putscr(gfxscr, gl_vd->vid_len);
		if (kbhit()) break;
		if (slow) sleep(1);
	}
	getch();	/* Swallow the kbhit() */
	setmode(gl_vd->textmode);
}


void draw_editor()
{
	int n;

	setmode(gl_vd->textmode);
	drawstr(0,0, "Character editor");
	drawch(2,  2,0xC9);
	drawch(2, 3+gl_vd->char_width,0xBB);
	drawch(3+gl_vd->char_height, 2,0xC8);
	drawch(3+gl_vd->char_height,3+gl_vd->char_width,0xBC);
	for (n = 0; n < gl_vd->char_height; n++)
	{
		if (n < gl_vd->char_width)
		{
			drawch(2,  3+n,0xCD);
			drawch(gl_vd->char_height+3, 3+n,0xCD);
		}
		drawch(3+n,2,  0xC7);
		drawch(3+n,gl_vd->char_width+3, 0xC7);
	}
	drawstr(gl_vd->char_height+4,0,"Cursors and SPACE to edit");
	drawstr(gl_vd->char_height+5,0, "S/T to test with ROM font, ESC to exit");
}

void draw_edchar(int nc)
{
	int r,c;
	unsigned char mask;
	PBYTE pdata;

	drawch(8, 20, nc);
	
	for (r = 0; r < gl_vd->char_height; r++)
	{
		mask = 0x80;
		pdata = cdata + r*gl_vd->char_wb;
		for (c = 0; c < gl_vd->char_width; c++)
		{
			if ((*pdata) & mask) drawch(3+r,3+c,10);
			else		     drawch(3+r,3+c,7);
			mask = mask >> 1;
			if (mask == 0)
			{
				mask = 0x80;
				++pdata;
			}
		}
	}
}



void edit_char(unsigned nc)
{
	static int cx=0,cy=0, mask = 0x80;
	int n;
	PBYTE pdata;

	cdata = fontbuf + gl_vd->char_height * gl_vd->char_wb * nc;
	draw_editor();
	draw_edchar(nc);
	while (1)
	{
		setcurpos(3+cy,3+cx);
		pdata = cdata + cy * gl_vd->char_wb + (cx/8);
		switch(getch())
		{
			case '\r': case '\n': 
			case 27:		return;
			case ' ' : pdata[0] ^= mask;
				   if (pdata[0] & mask)  drawch(3+cy,3+cx,10);
				   else		         drawch(3+cy,3+cx,7);
				   break;
			case 'T': case 't': dotest(0); draw_editor(); draw_edchar(nc); break;
			case 'S': case 's': dotest(1); draw_editor(); draw_edchar(nc); break;
			case 'R': case 'r': for (n = 0; n < gl_vd->char_height * gl_vd->char_wb; n++) cdata[n] = cdata[n] >> 1; 
						draw_edchar(nc);
						break;
			case 'L': case 'l': for (n = 0; n < gl_vd->char_height * gl_vd->char_wb; n++) cdata[n] = cdata[n] << 1; 
						draw_edchar(nc);
						break;
			case 'U': case 'u': for (n = 0; n < (gl_vd->char_height-1) * gl_vd->char_wb; n++) 
					    {
						cdata[n] = cdata[n+gl_vd->char_wb];
					    } 
					    draw_edchar(nc);
					    break;
			case 'D': case 'd': for (n = (gl_vd->char_height-1) * gl_vd->char_wb; n >= gl_vd->char_wb; n--) 
					    {
						cdata[n] = cdata[n-gl_vd->char_wb];
					    } 
					    draw_edchar(nc);
					    break;
			case 0: switch(getch())
			{
				case 0x4B: if (cx) 
					   {
						--cx; 
						mask = mask << 1; 
						if (mask >= 0x100) mask = 0x01;
					   } 
					   break;
				case 0x4D: if (cx < (gl_vd->char_width-1)) 
					   {
						++cx; 
						mask = mask >> 1; 
						if (mask == 0) mask = 0x80;
					   } 
					   break;
				case 0x48: if (cy)   --cy; break; 
				case 0x50: if (cy<gl_vd->char_height-1) ++cy; break; 
			}
		}
	}
}

void draw_chooser()
{
	setmode(gl_vd->textmode);
	drawstr(0, 0, "Choose character to edit");
		/*	1...5...10...15...20...25...30...35...40 */
	drawstr(gl_vd->char_height+2, 0, "Cursor keys to select, ENTER to edit");
	drawstr(gl_vd->char_height+3, 0, "W to write font dump to disc");
	drawstr(gl_vd->char_height+4, 0, "R to read previous font dump");
	drawstr(gl_vd->char_height+5, 0, "S/T to test with ROM font, ESC to exit");
}

void rdfont()
{
	char fail[80];
	PSF2_HEADER psfh;
	FILE *fp = fopen(gl_vd->filename, "rb");
	int okay = 0;

	// This "do ... break ... break ... while (0)" construct is a fun 
	// way of jumping to the end of the function if there's an error,
	// without doing anything so crass as a goto.
	do
	{
		sprintf(fail, "Could not open file %s", gl_vd->filename);
		if (!fp) break;

		sprintf(fail, "Read error on file %s", gl_vd->filename);
		if (fread(&psfh, 1, sizeof(psfh), fp) < (int)sizeof(psfh)) break;

		if (psfh.magic != PSF2_MAGIC)
		{
			sprintf(fail, "%s is not PSF2 ", gl_vd->filename);
			break;
		}
		if (psfh.version != 0)
		{
			sprintf(fail, "%s is version %ld", gl_vd->filename,
				psfh.version);
			break;
		}
		if (psfh.hdrlen != sizeof(PSF2_HEADER))
		{
			sprintf(fail, "%s header not 32", gl_vd->filename);
			break;
		}
 		if (psfh.height != gl_vd->char_height ||
		    psfh.width != gl_vd->char_width)
		{
			sprintf(fail, "%s is %ldx%ld not %dx%d",
			gl_vd->filename, psfh.width, psfh.height,
			gl_vd->char_width, gl_vd->char_height);
			break;
		}
		if ( psfh.charlen != gl_vd->char_height * gl_vd->char_wb)
		{
			sprintf(fail, "%s is %ld not %d", gl_vd->filename,
			   psfh.charlen, gl_vd->char_height * gl_vd->char_wb);
			break;
		}
		if (psfh.nchars > 256) psfh.nchars = 256;
		if (fread(fontbuf, psfh.charlen, psfh.nchars, fp) < psfh.nchars) 
		{
			strcpy(fail, "Unexpected EOF");
			break;
		}
		fclose(fp);
		sprintf(fail, "%s has been read.", gl_vd->filename);
		okay = 1;
	} while (0);

	drawstr(gl_vd->char_height+6, 0, fail);
	drawstr(gl_vd->char_height+6, 32, " [SPACE]");
	getch();
	drawstr(gl_vd->char_height+6, 0, "                                        ");
}



void wrfont()
{
	int okay = 1;
	char fail[80];

	FILE *fp = fopen(gl_vd->filename, "rb");
	if (fp)
	{
		char c;

		fclose(fp);
		sprintf(fail, "%s exists - delete (Y/N)?", gl_vd->filename);
		drawstr(gl_vd->char_height+6, 0, fail);
		do
		{
			c = getch();
			if (c == 'N' || c == 'n') 
			{
				drawstr(gl_vd->char_height+6, 0, "                                        ");
				return;
			}
		} while (c != 'y' && c != 'Y');
		drawstr(gl_vd->char_height+6, 0, "                                        ");
		remove(gl_vd->filename);
	}
	fp = fopen(gl_vd->filename, "wb");
	if (fp)
	{
/* Emit a PSF header */
		psf2.magic   = PSF2_MAGIC;
		psf2.version = 0;
		psf2.hdrlen  = sizeof(PSF2_HEADER);
		psf2.flags   = 0;
		psf2.nchars  = 256;
		psf2.charlen = gl_vd->char_height * gl_vd->char_wb;
		psf2.height  = gl_vd->char_height;
		psf2.width   = gl_vd->char_width;
		okay = (fwrite(&psf2,   1, sizeof(psf2),    fp) == sizeof(psf2));
		if (okay) okay = (fwrite(fontbuf, psf2.charlen, 256, fp) == 256);
		fclose(fp);
	}
	else okay = 0;
	if (okay) sprintf(fail, "%s was saved.", gl_vd->filename);
	else      sprintf(fail, "%s was not saved.", gl_vd->filename);
	drawstr(gl_vd->char_height+6, 0, fail);
	drawstr(gl_vd->char_height+6, 32, " [SPACE]");
	getch();
	drawstr(gl_vd->char_height+6, 0,           "                                        ");
}


int choose_char()
{
	static unsigned int nchar = 0;
	char buf[8];

	draw_chooser();
	while (1)
	{
		sprintf(buf, "%03d: %c", nchar, nchar);
        	drawstr(5, 5, buf);
		switch(getch())
		{
			case 'T': case 't': dotest(0); draw_chooser(); break;
			case 'S': case 's': dotest(1); draw_chooser(); break;
			case 'R': case 'r': rdfont(); break;
			case 'W': case 'w': wrfont(); break;
			case 27: return -1;
			case '\n': case '\r':
				return nchar;
			case 0: switch(getch())
				{
					case 0x4D: if (nchar < 255) ++nchar; break;
					case 0x4B: if (nchar > 0) --nchar; break;
				}
		}
	}
	return -1;
}

void probe_video()
{
	union REGS rg;

	if (getmode() == 7)	
	{
		gl_vd = &vd_mda;
		return;
	}
	rg.x.ax = 0x3000;
	rg.x.cx = 0;
	rg.x.dx = 0;
	int86(0x10, &rg, &rg);
	if (rg.x.cx || rg.x.dx)
	{
		gl_vd = &vd_pc3270;
		return;
	}	
	gl_vd = &vd_cga;
	return;
}


int main(int argc, char **argv)
{
	int n, nc;
	unsigned char mode = getmode();

	for (n = 1; n < argc; n++)
	{
		if (!strcmp(argv[n], "+h")) gl_herc = 1;
		if (!strcmp(argv[n], "-h")) gl_herc = 0;
		if (!strcmp(argv[n], "+H")) gl_herc = 1;
		if (!strcmp(argv[n], "-H")) gl_herc = 0;
	}
	probe_video();

	int1f = MK_FP(0, 0x7C);
	setmode(6);	/* Deliberately not gl_vd->gfxmode */
	graftabl = *int1f;
	getfont();
	setmode(gl_vd->textmode);

	while ((nc = choose_char()) != -1)
	{
		edit_char(nc);
	}

	setmode(6);
	*int1f = graftabl;
	setmode(mode);

	return 0;
}

