#ifndef lint
static char *sccsid = "@(#)textofont.c	1.4 (UKC) 6/1/88";
#endif  lint

/*
 *	textofont.c
 *	reads a text file (like that produced by fontotext)
 *	and converts it into a font-format file
 */

#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>
#include "font.h"

#define	BUFLEN	256	/* length of string buffers */
#define	STEP	500	/* when required, allocate this number more rasters */

extern char	*calloc(), *realloc();

static char	*progname = "textofont";
static u_long	*rasters;		/* start of main block of rasters */
static u_long	rasterlimit = 1000;	/* initial number of rasters */
static u_long	charlimit = 128;	/* initial number of characters */
static u_short	nrasters = 0;
static short	ch;		/* as specified in parameter lines */
static short	ewidth;		/* may also be specified in parameter lines */

static void	convert();
static int	getcharacter();
static charac	*morechars();
static void	parse();
static void	inc_nrasters();
static void	put16();
static void	put32();
static int	set_white();		/* used in parsing the parameter line */
static int	set_black();
static int	set_whiteref();
static int	set_blackref();
static int	set_ch();
static int	set_ewidth();

char	white = '.';	/* the default grid characters */
char	black = '@';
char	whiteref = '+';
char	blackref = '*';

main(argc, argv)
int	argc;
char	**argv;
{
	FILE	*ifp = stdin, *ofp = stdout;	/* default i/o streams  */

	if (argc > 3) {
		fprintf(stderr, "Usage: %s [textfile] [fontfile]\n", progname);
		exit(1);
	}
	if (argc >= 2) 
		if ((ifp = fopen(argv[1], "r")) == NULL) {
			fprintf(stderr, "%s: can't read %s\n",
				progname, argv[1]);
			exit(1);
		}
	if (argc == 3)
		if ((ofp = fopen(argv[2], "w")) == NULL) {
			fprintf(stderr, "%s: can't write to %s\n",
				progname, argv[2]);
			exit(1);
		}

	convert(ifp, ofp);
}


static void
convert(ifp, ofp)
FILE	*ifp, *ofp;
{
	charac	*character;
	charac	cbuf;
	short	lastchar = -1;
	short	which_char;
	int	i;

	/* get some memory for characters */
	character = (charac *) calloc((unsigned) charlimit, sizeof(charac));
	if (character == NULL) {
		fprintf(stderr, "%s: can't allocate memory for %u characters\n",
			progname, charlimit);
		exit(1);
	}

	/* initialise the characters to null */
	for (i = 0; i < charlimit; i++) {
		character[i].width = 0;
		character[i].height = 0;
	}

	/* get some memory for rasters */
	rasters = (u_long *) calloc((unsigned) rasterlimit, sizeof(u_long));
	if (rasters == NULL) {
		fprintf(stderr, "%s: can't allocate memory for %u rasters\n",
			progname, rasterlimit);
		exit(1);
	}

	/* main loop */
	while ((which_char = getcharacter(ifp, &cbuf)) != EOF) {
		if (which_char >= charlimit) {
			charlimit = which_char +1;
			character = morechars(character, charlimit);
		}
		if (which_char > lastchar)
			lastchar = which_char;
		character[which_char] = cbuf;
	}

	/* now write out the whole font */
	put16(ofp, (short) lastchar +1);
	put16(ofp, (short) nrasters);

	/* write the character structures */
	for (i = 0; i <= lastchar; i++) {
		put16(ofp, (short) character[i].width);
		put16(ofp, (short) character[i].height);
		put16(ofp, character[i].xoffs);
		put16(ofp, character[i].yoffs);
		put32(ofp, (long) character[i].raster_index);
		put32(ofp, (long) character[i].effective_width);
	}

	/* write the rasters */
	for (i = 0; i < nrasters; i++)
		put32(ofp, (long) rasters[i]);

	exit(0);
}

#define	isgrid(c)  ((c)==white || (c)==black || (c)==whiteref || (c)==blackref)

/*
 *	read and scan a character, and return it's index
 *	also fill in the structure pointed to by charbuf with the
 *	character's specifications
 */
static int
getcharacter(fp, charbuf)
FILE	*fp;
charac	*charbuf;
{
	static char	grid[BUFLEN][BUFLEN];
	static u_long	textline = 0;		/* for error reporting */
	int	xmax = -1, xmin = BUFLEN +1;	/* to find character bitmap */
	int	ymax = -1, ymin = BUFLEN +1;
	int	refx = -1, refy;		/* coords of ref point */
	int	ew = -1;		/* eff. width, if 2 ref points */
	int	x, y, lines = 1;
	u_long	bit;
	char	c;

	do {	/* skip blank and commented lines */
		if (fgets(grid[0], BUFLEN, fp) == NULL)
			return(EOF);
		textline++;
	} while (grid[0][0] == '\n' || grid[0][0] == '#');

	ch = ewidth = -1;

	/* set any parameters given before the grid starts */
	do {
		if (isgrid(grid[0][0])) {
			fprintf(stderr,
				"%s: line %u: no parameters specified\n",
				progname, textline);
			exit(1);
		}
		(void) parse(grid[0], textline);	/* set parameters  */
		if (fgets(grid[0], BUFLEN, fp) == NULL)
			return(EOF);
		textline++;
	} while (!isgrid(grid[0][0]));

	if (ch == -1) {
		fprintf(stderr, "%s: line %u: no value assigned to `ch'\n",
			progname, textline);
		exit(1);
	}

	/* read the character grid */
	do {
		if (lines >= BUFLEN) {
			fprintf(stderr, "%s: character %u is too large\n",
				progname, ch);
			exit(1);
		}
		if (fgets(grid[lines], sizeof(grid[lines]), fp) == NULL) {
			fprintf(stderr, "%s: premature EOF in character %u\n",
				progname, ch);
			exit(1);
		}
	} while (grid[lines++][0] != '\n');

	lines--;	/* don't count the terminating newline */

	/*
	 *	Scan across and down the grid, setting the maximum
	 *	and minimum x & y values for the character's outline.
	 *	Also, save the position of the first (or only) reference point.
	 */
	for (y = 0; y < lines; y++) {
		for (x = 0; (c = grid[y][x]) != '\n' && (c != '\0'); x++) {
			if (!isgrid(c)){
				fprintf(stderr,
					"%s: line %u: illegal character '%c'\n",
					progname, textline, c);
				exit(1);
			}
			if (c == black || c == blackref) {
				if (x < xmin) xmin = x;
				if (x > xmax) xmax = x;
				if (y < ymin) ymin = y;
				if (y > ymax) ymax = y;
			}
			if (refx == -1 && (c == blackref || c == whiteref)) {
				refx = x;
				refy = y;
			}
		}
		textline++;
	}
	if (refx == -1) {	/* no reference point found */
		fprintf(stderr, "%s: character %u has no reference point\n",
			progname, ch);
		exit(1);
	}

	/*
	 *	See if there is another reference point.
	 *	If so, the calculated effective width (ew) must agree with
	 *	the "ewidth=" value, if it was given.
	 */
	for (x = refx +1; grid[refy][x] != '\n'; x++)
		if (grid[refy][x] == blackref || grid[refy][x] == whiteref) {
			ew = x - refx;
			break;
		}
	
	/* the effective width must be specified one way or another */
	if (ew == -1 && ewidth == -1) {
		fprintf(stderr,
			"%s: no effective width specified for character %u\n",
			progname, ch);
		exit(1);
	}
	/* if both effective widths are given, they must agree */
	if ((ew != -1) && (ewidth != -1) && (ew != ewidth)) {
		fprintf(stderr,
			"%s: character %u has inconsistent effective widths\n",
			progname, ch);
		exit(1);
	}

	/* fill in the character's specifications */
	charbuf->width = (u_short) (xmax - xmin +1);
	charbuf->height = (u_short) (ymax - ymin +1);
	charbuf->xoffs = (short) (refx - xmin);
	charbuf->yoffs = (short) (refy - ymin);
	charbuf->raster_index = (u_long) nrasters;
	charbuf->effective_width  = (u_long) (ewidth == -1 ? ew : ewidth);

	 /* now fill in the rasters for this character */
	 for (y = ymin; y <= ymax; y++) {
	 	bit = 1 << 31;
		for (x = xmin; x <= xmax; x++) {
			if (bit == 0) {
				bit = 1 << 31;
				inc_nrasters();
			}
			if (grid[y][x] == black || grid[y][x] == blackref)
				rasters[nrasters] |= bit;
			bit >>= 1;
		}
		inc_nrasters();
	}
	return(ch);
}

#define	STREQ(a, b, c)	strncmp((a), (b), (c)) == 0

/*
 *	set any parameters which may be given in the string buf
 */
static void
parse(buf, line)
char	*buf;
u_long	line;
{
	static struct entry {	/* table of parameters and functions */
		char	*name;
		int	(*func)();
	} spec[] = {
		{"white",	set_white},
		{"black",	set_black},
		{"whiteref",	set_whiteref},
		{"blackref",	set_blackref},
		{"ch",		set_ch},
		{"ewidth",	set_ewidth},
		{"",		NULL}
	};
	int	i, j, start = 0;

	if (buf[0] == '\n' || buf[0] == '#')
		return;
	for (i = 0; buf[i] != '\0'; i++) {
		if (isspace(buf[i]))
			start = i+1;
		if (buf[i] == '=') {
			for (j = 0; spec[j].name[0] != '\0'; j++)
				if (STREQ(&buf[start], spec[j].name, i-start)) {
					(void) (*spec[j].func)(&buf[i+1], line);
					break;
				}
			if (spec[j].name[0] == '\0') {
				fprintf(stderr,
				"%s: bad parameter \"%.*s\" at line %u\n",
				progname, i-start, &buf[start], line);
				exit(1);
			}
		}
	}
}

/*
 *	increment the index into the raster block,
 *	and get more memory if necessary
 */
static void
inc_nrasters()
{
	nrasters++;
	if  (nrasters >= rasterlimit) {	/* allocate more space for rasters */
		rasters = (u_long *) realloc((char *) rasters,
			(unsigned) ((rasterlimit += STEP) * sizeof(u_long)));
		if (rasters == NULL) {
			fprintf(stderr, "%s: not enough memory for %d rasters\n",
				progname, rasterlimit);
			exit(1);
		}
	}
}

/* return a pointer to a larger block of characters */
static charac *
morechars(oldptr, n)
charac *oldptr;
u_long	n;
{
	charac	*ptr;

	ptr = (charac *) realloc((char *)oldptr, (unsigned)(n*sizeof(charac)));
	if (ptr == NULL) {
		fprintf(stderr, "%s: not enough memory for %d characters\n",
			progname, n);
		exit(1);
	}
	return(ptr);
}

/* write a short to stream fp */
static void
put16(fp, i)
FILE	*fp;
short	i;
{
	putc((char) (i >> 8) & 255, fp);
	putc((char) i & 255, fp);
}

/* write a long to stream fp */ 
static void
put32(fp, i)
FILE	*fp;
long	i;
{
	putc((char) (i >> 24) & 255, fp);
	putc((char) (i >> 16) & 255, fp);
	putc((char) (i >> 8) & 255, fp);
	putc((char) i & 255, fp);
}

static int
set_white(st, line)
char	*st;
u_long	line;
{
	if (*st == '\n') {
		fprintf(stderr, "/%s: line %u: no value given for `white'\n",
			progname, line);
		exit(1);
	}
	white = *st;
}

static int
set_black(st, line)
char	*st;
u_long	line;
{
	if (*st == '\n') {
		fprintf(stderr, "%s: line %u: no value given for `black'\n",
			progname, line);
		exit(1);
	}
	black = *st;
}

static int
set_whiteref(st, line)
char	*st;
u_long	line;
{
	if (*st == '\n') {
		fprintf(stderr, "%s: line %u: no value given for `whiteref'\n",
			progname, line);
		exit(1);
	}
	whiteref = *st;
}

static int
set_blackref(st, line)
char	*st;
u_long	line;
{
	if (*st == '\n') {
		fprintf(stderr, "%s: line %u: no value given for `whiteref'\n",
			progname, line);
		exit(1);
	}
	blackref = *st;
}

static int
set_ch(st, line)
char	*st;
u_long	line;
{
	if (!isdigit(*st)) {
		fprintf(stderr,
			"%s: line %u: `ch' does not have a numeric value\n",
			progname, line);
		exit(1);
	}
	ch = atoi(st);
}

static int
set_ewidth(st, line)
char	*st;
u_long	line;
{
	if (!isdigit(*st)) {
		fprintf(stderr,
			"%s: line %u: `ewidth' does not have a numeric value\n",
			progname, line);
		exit(1);
	}
	ewidth = atoi(st);
}
