/*
 *		knjtopk.exe    KANJI FONT file <-> PK Format
 *						supported dviout.exe dviprt.exe v2.07-
 *				v1.0	June 1990, written by SHIMA
 *				v1.1    Dec. 1990, The last kaji 龠 (JIS 737E) was ignored
 *				v1.2    Dec. 1990, Support PK format -> KANJI FONT file
 *				v1.3    Dec. 1990, o & . + option
 *              v1.4    Feb. 1991, MSG 8424 -> 7424
 *				v1.5	Aug. 1991, enlarge *_BUF_SIZE
 *				v1.6    May  1993, Huge format with JXL4 style
 *													(up to 400 x 400 dot)
 *				v1.7	July 1993, A bug is fixed for a Huge font
 *				v1.8	Aug  1993, A bug is fixed in the small format
 */

#include	"stdio.h"
#include	"stdlib.h"
#include	"ctype.h"
#include	"dos.h"

#define	DEBUG			0

#define	KNJ_PK_ID		(247*256 + 'K')
#define	KNJH_PK_ID		(247*256 + 'H')

#define	REPT			14
#define	DYN_F			12

#define	DYN_2			((12 - DYN_F)*16 + 15 + DYN_F + 1)	/* 28 */
#define	DYN_L			((13 - DYN_F)*16 + DYN_F - 15)	/* 13 */

#define	FONT_BUF_SIZE	20001
#define	WORK_BUF_SIZE	41000

#define	MAX_CODE		0x7424
#define	MAX_CHAR	((((unsigned int)MAX_CODE>>8)-0x21)*94 + (MAX_CODE & 0xff) - 0x20)
/* -0x21 -> -0x20  in v1.1 */

unsigned char f_buf[FONT_BUF_SIZE];
unsigned char *w_buf;
unsigned char *w2_buf;
unsigned char *f_dir;
unsigned char f_head[32];
unsigned char t_head[32];
unsigned char row[240];
int power[8];
int gpower[9];
unsigned int pktype = 3;

unsigned int max_char = MAX_CHAR;

static unsigned int bit[8] =
{128, 64, 32, 16, 8, 4, 2, 1};

void set_num(unsigned char **, long);
int rpt_cnt(int, int, int);
int pxltopk(int, int);
void usage(void);
int get_bit(void);
void decode_pk(unsigned int, unsigned int, unsigned int, unsigned int);
void decode_jxl(unsigned int, unsigned int, unsigned int, int);
void raster_by_bits(unsigned int, unsigned int, unsigned int);
long get_pknum(void);
void printx(void);
void to_xor(int, int);
void decode_xor(int, int);
unsigned int to_pk(int, int);
unsigned int to_jxl(int, int);

void main(int argc, char **argv)
{
	FILE *fp, *fpk;
	unsigned int i, j, k;
	unsigned int size, size2, block;
	unsigned int width, height, b_width, f_deb, f_rev;
	unsigned int max_num;
	long pos;

	if (argc < 4)
		usage();

	if ((w_buf = malloc(WORK_BUF_SIZE)) == NULL){
			printf("Not enough memory!\n");
			exit(1);
	}

	if (tolower(*argv[3]) == 'r') {
		f_rev = 1;
	}
	else {
		f_rev = 0;
		width = atoi(argv[3]);
		if (argc >= 5)
			height = atoi(argv[4]);
		else
			height = width;
	}
	if (f_rev == 0 && (width == 0 || height == 0))
		usage();

	f_deb = 0;
	for (i = 5; i < argc; i++) {
		if (tolower(argv[i][0]) == 'f') {
			printf("Do you really want 'f' option (it becomes very slow)? "
				   "Input (y/n):");
			while ((j = tolower(getch())) != 'y' && j != 'n');
			if (j == 'n')
				exit(1);
			f_deb = 1;
		}
		if (tolower(argv[i][0]) == 'h') pktype = 5;
		if (argv[i][0] == '+') {
			max_char += atoi(argv[i] + 1);
		}
	}

	if ((fp = fopen(argv[1], "rb")) == NULL) {
		fprintf(stderr, "Cannot open [%s]", argv[1]);
		exit(1);
	}
	if ((fpk = fopen(argv[2], "wb")) == NULL) {
		fprintf(stderr, "Cannot open [%s]", argv[2]);
		exit(1);
	}
	fread(t_head, 32, 1, fp);
	if (t_head[0] == 0
		&& t_head[1] == 0
		&& t_head[2] == ((KNJ_PK_ID >> 8) & 0xff)
		&& (t_head[3] == 'K' || t_head[3] == 'H') ){
		if (f_rev == 0) {
			fprintf(stderr, "%s is a Kanji PK Font file.", argv[1]);
			exit(1);
		}
											/* expand routine */
		if (t_head[3] == 'H') pktype = 5;
		width = *(int *)(t_head + 4);
		height = *(int *)(t_head + 6);
		b_width = *(int *)(t_head + 8);
		size = b_width * height;

		fread(row, 5, 1, fp);

		power[0] = 1;
		for (i = 1; i <= 7; i++)
			power[i] = power[i - 1] * 2;
		gpower[0] = 0;
		for (i = 1; i <= 8; i++)
			gpower[i] = gpower[i - 1] + power[i - 1];

		if (pktype == 3){
			pos = (((long)(row[0] & 0x3f)) << 16);
			pos += (((long)(row[1])) << 8) + row[2];
		}
		else pos = *(long *)(row+1);
		max_num = pos - 32L;
		if (max_num == (max_char - 5) * 3) {
			printf("This file is made by Ver 1.0.\n");
		}
		else if (max_num == (max_char - 4) * 3) {
			printf("This file is made by Ver 1.1 or 1.2.\n");
		}
		if (pktype == 5)
			printf("This file is of Huge format.\n");
		if ((f_dir = malloc(max_char * pktype)) == NULL) {
			printf("Not enough memory!\n");
			exit(1);
		}
		fseek(fp, 32L, SEEK_SET);
		fread(f_dir, max_num, 1, fp);
		printx();
		if (pktype == 5) max_num -= 5;
		for (i = 0; i < max_num; i += pktype) {
			if (pktype == 3){
				pos = (((long)(f_dir[i] & 0x3f)) << 16);
				pos += (((long)(f_dir[i + 1])) << 8) + f_dir[i + 2];
			}
			else{
				pos = *(long *)(f_dir+i+1);
				size = *(long *)(f_dir+i+6) - pos;
			}
			fseek(fp, pos, SEEK_SET);
			j = f_dir[i];
			if ((pktype == 3 && (j & 0x80) == 0)
			 || (pktype == 5 && (j & 0x0f) != 0x0f)){
				if (pktype == 3) k = (12 << 4) + ((j & 0x40) >> 3);
				else			 k = (12 << 4) + ((j & 0x80) >> 4);
				if (size) fread(f_buf, size, 1, fp);
				if (pktype == 5 && (j & 0x0f) == 0x0e){
					raster_by_bits(width, height, b_width);
				}
				else if (pktype == 5 && (j & 0x40)){
					*((long *)(f_buf + size)) = 0x0f0f0000L;
					decode_jxl(width, height, b_width, j);
				}
				else decode_pk(width, height, b_width, k);
			}
			else {
				fread(w_buf, size, 1, fp);
			}
write:		if (fwrite(w_buf, b_width, height, fpk) != height)
				goto w_err;
			if (i % (100 * pktype) == 0)
				printf("o");
		}
		if (max_num < max_char * pktype) {
			max_num += pktype;
			for (j = 0; j < size; j++)
				w_buf[j] = 0;
			goto write;
		}
		exit(0);
	}
	if (f_rev == 1) {
		fprintf(stderr, "%s is not a Kanji PK Font file.", argv[1]);
		exit(1);
	}
													/* compress routine */
	if ((f_dir = malloc(max_char * pktype)) == NULL
	  || (pktype == 5 && (w2_buf = malloc(WORK_BUF_SIZE)) == NULL) ) {
		printf("Not enough memory!\n");
		exit(1);
	}
	b_width = (width + 7) / 8;

	if (width > 400 || height > 400 ||
	  (block = b_width * height) > FONT_BUF_SIZE) {
		fprintf(stderr, "Too big FONT size");
		exit(1);
	}
	f_head[2] = (KNJ_PK_ID >> 8);
	if (pktype == 3) f_head[3] = 'K';
	else			 f_head[3] = 'H';
	*(int *)(f_head + 4) = width;
	*(int *)(f_head + 6) = height;
	*(int *)(f_head + 8) = b_width;

	if (pktype == 5) max_char++;
	pos = 32L + (max_char * pktype);

	if (fwrite(f_head, 32, 1, fpk) != 1
		|| fwrite(f_dir, max_char, pktype, fpk) != pktype) {
w_err:	fprintf(stderr, "Write error!\n");
		exit(1);
	}
	fseek(fp, 0L, 0);
	if (f_deb == 0)
		printx();
	for (i = 0; ; i++) {
		k = i * pktype;
		if (pktype == 3){
			if (i >= max_char) break;
			f_dir[k++] = (pos >> 16);
			f_dir[k++] = ((pos >> 8) & 0xff);
			f_dir[k] = (pos & 0xff);
		}
		else{
			f_dir[k++] = 12;
			*(long *)(f_dir+k) = pos;
			if (i >= max_char - 1) break;
		}
		fread(f_buf, b_width, height, fp);
		if (f_deb != 0) {
			k = width * height;
			for (j = 0; j < k; j++) {
				if (j % width == 0)
					printf("\n");
				if ((f_buf[j >> 3] & bit[j & 7]) != 0)
					printf("*");
				else
					printf(" ");
			}
			printf("\n");
		}
		size = to_pk(width, height);
		if (pktype == 5) size2 = to_jxl(width, height);
		else  size2 = size + 1;
		if (f_deb != 0) {
			for (k = 0; k < size; k++) {
				printf("%d ", w_buf[k]);
			}
			printf("[%d]\n", size);
			if (pktype == 5){
				for (k = 0; k < size2; k++) {
					printf("%d ", w2_buf[k]);
				}
				printf("[%d]\n", size2);
			}
		}
		j = (size + 1) / 2;
		if (size2 < size) j = (size2 + 1) /2;
		if (j < block) {
			if ((f_buf[0] & 0x80) != 0){
				if (pktype == 3) f_dir[pktype * i] |= 0x40;
				else			 f_dir[pktype * i] |= 0x80;
			}
			if (size2 < size){
				f_dir[pktype *i] |= 0x40;
				for (k = 0; k < j; k++)
					f_buf[k] = (w2_buf[k + k] << 4) + w2_buf[k + k + 1];
			}
			else{
				for (k = 0; k < j; k++)
					f_buf[k] = (w_buf[k + k] << 4) + w_buf[k + k + 1];
			}
			if (j && fwrite(f_buf, j, 1, fpk) != 1)
				goto w_err;
			pos += j;
		}
		else {
			if (pktype == 3) f_dir[pktype * i] |= 0x80;
			else{			 f_dir[pktype * i] = 15;
				decode_xor(b_width, height);
			}
			if (fwrite(f_buf, block, 1, fpk) != 1)
				goto w_err;
			pos += block;
		}
		if (f_deb == 0 && i % 100 == 0)
			printf("o");
		if (pos >= 0x400000L && pktype == 3) {
			fprintf(stderr, "Too big FONT file.  Use H option!");
			exit(1);
		}
	}
	fseek(fpk, 32L, 0);
	if (fwrite(f_dir, max_char, pktype, fpk) != pktype)
		goto w_err;
}

void printx()
{
	int i;

	for (i = 0; i <= max_char / 100; i++)
		printf(".");
	printf("\x0d");
}

void set_num(unsigned char **ptr, long num)
{
	int i, count, temp[6];

	if (num <= DYN_F) {
		*((*ptr)++) = num;
	}
	else {
		if (num <= DYN_2) {
			num -= (DYN_F + 1);
			*((*ptr)++) = num / 16 + (DYN_F + 1);
			*((*ptr)++) = (num & 15);
		}
		else {
			for (num -= DYN_L, count = 0; num != 0; num /= 16) {
				temp[count++] = (num & 15);
			}
			for (i = count; i > 1; i--)
				*((*ptr)++) = 0;
			while (count-- > 0)
				*((*ptr)++) = temp[count];
		}
	}
}

int rpt_cnt(int c_len, int len, int width)
{
	unsigned char *f_buf0, *f_buf1;
	int i, len_pt;

	f_buf0 = f_buf + (unsigned int)width * (unsigned int)c_len;

	for (len_pt = c_len + 1; len_pt < len; len_pt++) {
		f_buf1 = f_buf + (unsigned int)width * (unsigned int)len_pt;
		for (i = 0; i < width; i++) {
			if (f_buf0[i] != f_buf1[i])
				goto quit;
		}
	}
quit:
	return (len_pt - c_len - 1);
}

void decode_xor(int b_wid, int len)
{
	unsigned char *w_ptr;
	unsigned char *w_end;

	w_ptr = f_buf + b_wid, w_end = f_buf + b_wid * len;
	for ( ; w_ptr < w_end; w_ptr++)
		*w_ptr ^= *(w_ptr-b_wid);
}

void to_xor(int b_wid, int len)
{
	unsigned char *w_ptr;

	for (w_ptr = f_buf + b_wid*(len-1); w_ptr > f_buf; ){
		w_ptr--;
		w_ptr[b_wid] ^= *w_ptr;
	}
}

unsigned int to_jxl(int width, int len)
{
	unsigned char *ptr;
	unsigned char *w_ptr;
	int data, shift, b_wid, c_len, c_wid, f_b;
	long b_cnt, b_bcnt, b_brep;
	unsigned int  total;

	ptr = f_buf;
	b_cnt = 1;
	b_brep = f_b = 0;
	b_wid = (width + 7) / 8;
	total = (unsigned int)b_wid * (unsigned int)len * 2;

	to_xor(b_wid, len);

	for (w_ptr = w2_buf, c_len = 0; c_len <= len; c_len++) {
		if (c_len == len){
			data = 0;
			goto last;
		}
		for (c_wid = 0; c_wid < width; c_wid++) {
			if ((shift = (c_wid & 7)) == 0) {
				data = *ptr++;
			}
last:		if ((data & bit[shift]) != 0) {
				if (f_b != 0)			/* on -> on */
					b_bcnt++;
				else {
					f_b = 1;			/* off -> on */
					b_bcnt = 0;
				}
			}
			else{
				if (f_b != 0){			/* on -> off */
					if (b_bcnt){
						if (b_bcnt == b_brep)
							*w_ptr++ = 15;
						else{
							*w_ptr++ = REPT;
							set_num(&w_ptr, b_brep = b_bcnt);
						}
					}
					set_num(&w_ptr, b_cnt);
					b_cnt = 1;
					f_b = 0;
				}
				else					/* off -> off */
					b_cnt++;
			}
		}
		if (w_ptr - w2_buf >= total) break;
	}
	w_ptr[0] = 0;
	return (w_ptr - w2_buf);
}

unsigned int to_pk(int width, int len)
{
	unsigned char *ptr;
	unsigned char *w_ptr;
	int data, shift, b_wid, w_b_ptr, c_len, c_wid,  r_cnt, f_b;
	long b_cnt;
	unsigned int  total;

	ptr = f_buf;
	w_ptr = w_buf;
	b_cnt = 0;
	b_wid = (width + 7) / 8;
	total = (unsigned int)b_wid * (unsigned int)len * 2;
	f_b = ((*ptr & 0x80) == 0) ? 0 : 1;

	for (c_len = 0; c_len < len; c_len++) {
		for (r_cnt = -1, c_wid = 0; c_wid < width; c_wid++) {
			if ((shift = (c_wid & 7)) == 0) {
				data = *ptr++;
			}
			if ((data & bit[shift]) != 0) {
				if (f_b != 0)
					b_cnt++;
				else {
					f_b = 1;
					goto rep_end;
				}
			}
			else {
				if (f_b == 0)
					b_cnt++;
				else {
					f_b = 0;
rep_end:			set_num(&w_ptr, b_cnt);
					b_cnt = 1;
					if (r_cnt < 0
						&& (r_cnt = rpt_cnt(c_len, len, b_wid)) != 0) {
						/* repeated same lines */
						if (r_cnt == 1)
							*w_ptr++ = 15;
						else {
							*w_ptr++ = REPT;
							set_num(&w_ptr, r_cnt);
						}
						ptr += (unsigned int)r_cnt * (unsigned int)b_wid;
						c_len += r_cnt;
					}
				}
			}
		}						/* end a line */
		if ((w_ptr - w_buf) >= total)
			return (w_ptr - w_buf);
	}
	set_num(&w_ptr, b_cnt);
	w_ptr[0] = 0;
	return (w_ptr - w_buf);
}

void usage()
{
	printf(
			  "<<< knjtopk: kanji font <-> PK file, supported by dviprt, dviout v2.07- >>>\n"
			  "\t\t\t\tver 1.8, 1990-93, written by SHIMA\n\n"
			  "Usage: knjtopk orig_file pk_file width [height [+number] [H] [f]] (Make PK file)\n"
			  "       knjtopk pk_file orig_file r\t\t\t (Recover original file)\n\n"
			  "original_file: JIS CODE 2121-7424 (default)\n"
			  "width        : width by dots  (< 401) \n"
			  "height       : height by dots (< 401) \n"
			  "number       : End of JIS Code = 7424 + number\n"
			  "\t\t-4 (JIS 78), 0 (JIS 83), 2 (JIS 90)\n"
			  "H            : Huge format with an expanded compression (v2.39- )\n"
			  "f            : flag for debug\n"
	);
	exit(1);
}

int bit_weight, input_byte, buf_pt, expf;
long lastrep, repeat_count;

#define	get_ubyte()	f_buf[buf_pt++]

#define	get_nyb() ((bit_weight^=1)?((input_byte=get_ubyte( ))>>4):(input_byte & 0x0f))

int dyn_f;
typedef enum {
	FALSE = 0, TRUE = 1
} BOOL;


void raster_by_bits(unsigned c_width, unsigned c_height, 
	unsigned byte_width)
{
	unsigned char *buf, *pk_data;
	int i, j;
	int shift, mode;
	buf = w_buf;
	pk_data = f_dir;

	if ((mode = (c_width & 0x7)) == 0) {
		for (i = byte_width * c_height; i > 0; i--) {
			*buf++ = *pk_data++;
		}
		return;
	}
	shift = 0;
	for (j = 0; j < c_height; ++j) {
		for (i = byte_width - 1; i > 0; i--) {
			*buf =  *pk_data<< shift;
			*buf++ |= *(++pk_data) >> (8 - shift);
		}
		*buf = *pk_data << shift;
		if (mode + shift >= 8) {
			*buf |= *(++pk_data) >> (8 - shift);
		}
		*buf++ &= 0xff << (8 - mode);
		shift = ((shift + mode) & 0x7);
	}
}


void decode_jxl(unsigned c_width, unsigned c_height, 
	unsigned byte_width, int flag_byte)
{
	unsigned char *buf;
	int r_p,	/* 水平方向の現在位置（バイト単位） */
	    h_bit,	/* １ラスター中で右側に残っているビット数 */
	    rows_left,	/* 残りのrowの数 */
	    byte_weight,	/* 現在位置 */
	    i;

	long count;	/* 現在の色が後何ビット残っているか */

	dyn_f = flag_byte & 0xf;
	rows_left = c_height;
	h_bit = c_width + 1;
	bit_weight = r_p = repeat_count = buf_pt = 0;
	byte_weight = expf = 1;
	buf = (unsigned char *)w_buf;

	for (i = 1; i <= c_width; i++)
		row[i] = 0;
	for (;;) {
		count = get_pknum();
		while (count >= h_bit) {	/* skip to next line */
			for (i = 1; i <= byte_width; i++) *buf++ = row[i];
			if (--rows_left <= 0) return;
			count -= h_bit;
			r_p = 1;
			h_bit = c_width;
			byte_weight = 8;
		}
		while (count >= byte_weight) {	/* skip to next byte */
			count -= byte_weight;
			h_bit -= byte_weight;
			r_p++;
			byte_weight = 8;
		}						/* In this byte */

		byte_weight -= count;	/* should be positive */
		h_bit -= count;			/* should be positive */
		repeat_count++;
		while (repeat_count >= h_bit) {	/* next ptr is next row */

			if (byte_weight < h_bit) {	/* not the last byte */
				row[r_p++] ^= gpower[byte_weight];
				h_bit -= byte_weight;
				repeat_count -= byte_weight;

			}
			else {				/* last byte in the row */
				row[r_p] ^= (gpower[byte_weight] - gpower[byte_weight - h_bit]);
				for (i = 1; i <= byte_width; i++) *buf++ = row[i];
				if (--rows_left <= 0) return;
				r_p = 1;
				repeat_count -= h_bit;
				h_bit = c_width;
			}
			byte_weight = 8;
		}
		/* next ptr is in this row */
		while (repeat_count) {
			if (byte_weight > repeat_count) {	/* next ptr is in this byte */
				row[r_p] ^=
					(gpower[byte_weight] - gpower[byte_weight - repeat_count]);
				byte_weight -= repeat_count;
				h_bit -= repeat_count;
				repeat_count = 0;
				break;
			}
			else {				/* next ptr is not in this byte */
				row[r_p++] ^= gpower[byte_weight];
				h_bit -= byte_weight;
				repeat_count -= byte_weight;
				byte_weight = 8;
			}
		}
	}
}

void decode_pk(unsigned int c_width, unsigned int c_height, 
	unsigned int byte_width, unsigned int flag_byte)
{
	BOOL turn_on;	/* black or white */
	unsigned char *buf;
	int h, v;
	int r_p,	/* 水平方向の現在位置（バイト単位） */
	    h_bit,	/* １ラスター中で右側に残っているビット数 */
	    rows_left,	/* 残りのrowの数 */
	    byte,	/* raster-dataを一時的に入れる */
	    byte_weight;	/* 現在位置 */
	long count;	/* 現在の色が後何ビット残っているか */

	bit_weight = buf_pt = expf = 0;
	buf = (unsigned char *)w_buf;
	dyn_f = flag_byte >> 4;
	turn_on = ((flag_byte & 8) != 0);

	if (dyn_f == 14) {
		for (v = 1; v <= c_height; v++) {
			byte = 0;
			byte_weight = 7;
			for (h = 1; h <= c_width; h++) {
				if (get_bit())
					byte |= power[byte_weight];
				byte_weight--;
				if (byte_weight == -1) {
					*buf++ = byte;
					byte = 0;
					byte_weight = 7;
				}
			}
			if (byte_weight < 7)
				*buf++ = byte;
		}
	}
	else {
		rows_left = c_height;
		h_bit = c_width;
		repeat_count = 0;
		byte_weight = 8;
		byte = 0;
		r_p = 1;

		while (rows_left > 0) {
			count = get_pknum();
			while (count > 0) {
				if ((count < byte_weight) && (count < h_bit)) {
					if (turn_on)
						byte |= (gpower[byte_weight] -
								 gpower[byte_weight - count]);

					h_bit -= count;
					byte_weight -= count;
					break;
				}
				else if ((count >= h_bit) &&
						 (h_bit <= byte_weight)) {
					if (turn_on)
						byte |= (gpower[byte_weight] -
								 gpower[byte_weight - h_bit]);

					row[r_p] = byte;

					for (v = 0; v <= repeat_count; v++)
						for (h = 1; h <= byte_width; h++)
							*buf++ = row[h];

					rows_left = rows_left - repeat_count - 1;
					repeat_count = 0;
					r_p = 1;
					byte = 0;
					byte_weight = 8;
					count -= h_bit;
					h_bit = c_width;
				}
				else {
					if (turn_on)
						byte |= gpower[byte_weight];

					row[r_p] = byte;
					r_p++;
					byte = 0;
					count -= byte_weight;
					h_bit -= byte_weight;
					byte_weight = 8;
				}
			}
			turn_on = (!turn_on);
		}
		if ((rows_left != 0) || (h_bit != c_width)) {
			printf("More bits than required!");
			exit(1);
		}
	}
}

int get_bit(void)
{
	if (bit_weight == 0) {
		input_byte = get_ubyte();
		bit_weight = 8;
	}
	return ((input_byte & power[--bit_weight]) != 0);
}

long get_pknum(void)
{
	long i, j;

	i = get_nyb();
	if (i == 0) {
		do {
			j = get_nyb();
			i++;
		} while (j == 0);
		while (i > 0) {
			j = (j << 4) + get_nyb();
			i--;
		}
		return (j - 15 + ((unsigned)(13 - dyn_f) << 4) + dyn_f);
	}
	else if (i <= dyn_f)
		return (i);
	else if (i < 14)
		return (((i - dyn_f - 1) << 4) + get_nyb() + dyn_f + 1);
	else {
		if (i == 14)
			lastrep = repeat_count = get_pknum();
		else
			repeat_count = (expf == 0) ? 1 : lastrep;
		return (get_pknum());
	}
}
