/*
 * psffontop.c - aeb@cwi.nl, 990921
 */

#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include "xmalloc.h"
#include "nls.h"
#include "psf.h"
#include "psffontop.h"
#include "utf8.h"
#include "paths.h"

extern char *progname;

static void
addpair(struct unicode_list *up, unsigned int uc) {
	struct unicode_list *ul;
	struct unicode_seq *us;

	ul = xmalloc(sizeof(struct unicode_list));
	us = xmalloc(sizeof(struct unicode_seq));
	us->uc = uc;
	us->prev = us;
	us->next = NULL;
	ul->seq = us;
	ul->prev = up->prev;
	ul->prev->next = ul;
	ul->next = NULL;
	up->prev = ul;
}

static void
addseq(struct unicode_list *up, unsigned int uc) {
	struct unicode_seq *us;
	struct unicode_seq *usl;
	struct unicode_list *ul = up->prev;

	usl = ul->seq;
	while (usl->next) usl = usl->next;
	us = xmalloc(sizeof(struct unicode_seq));
	us->uc = uc;
	us->prev = usl;
	us->next = NULL;
	usl->next = us;
	//ul->seq->prev = us;
}

static unsigned int
assemble_int(unsigned char *ip) {
	return (ip[0] + (ip[1]<<8) + (ip[2]<<16) + (ip[3]<<24));
}

static void
store_int_le(unsigned char *ip, int num) {
	ip[0] = (num & 0xff);
	ip[1] = ((num >> 8) & 0xff);
	ip[2] = ((num >> 16) & 0xff);
	ip[3] = ((num >> 24) & 0xff);
}

static unsigned int
assemble_ucs2(char **inptr, int cnt) {
	unsigned int u1, u2;

	if (cnt < 2) {
		char *u = _("%s: short ucs2 unicode table\n");
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}

	u1 = (unsigned char) *(*inptr)++;
	u2 = (unsigned char) *(*inptr)++;
	return (u1 | (u2 << 8));
}

/* called with cnt > 0 and **inptr not 0xff or 0xfe */
static unsigned int
assemble_utf8(char **inptr, int cnt) {
	int err;
	unsigned long uc;
	char *u;

	uc = from_utf8(inptr, cnt, &err);
	if (err) {
		switch (err) {
		case UTF8_SHORT:
			u = _("%s: short utf8 unicode table\n");
			break;
		case UTF8_BAD:
			u = _("%s: bad utf8\n");
			break;
		default:
			u = _("%s: unknown utf8 error\n");
		}
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}
	return uc;
}

static void
clear_uni_entry(struct unicode_list *up) {
	up->next = NULL;
	up->seq = NULL;
	up->prev = up;
}	

/*
 * Read description of a single font position.
 */
static void
get_uni_entry(char **inptr, char **endptr, struct unicode_list *up, int utf8) {
	unsigned char uc;
	unicode unichar;
	int inseq = 0;

	up->next = NULL;
	up->seq = NULL;
	up->prev = up;

	while(1) {
		if (*endptr == *inptr) {
			char *u = _("%s: short unicode table\n");
			fprintf(stderr, u, progname);
			exit(EX_DATAERR);
		}
		if (utf8) {
			uc = *(*inptr)++;
			if (uc == PSF2_SEPARATOR)
				break;
			if (uc == PSF2_STARTSEQ) {
				inseq = 1;
				continue;
			}
			--(*inptr);
			unichar = assemble_utf8(inptr, *endptr - *inptr);
		} else {
			unichar = assemble_ucs2(inptr, *endptr - *inptr);
			if (unichar == PSF1_SEPARATOR)
				break;
			if (unichar == PSF1_STARTSEQ) {
				inseq = 1;
				continue;
			}
		}
		if (inseq < 2)
			addpair(up, unichar);
		else
			addseq(up, unichar);
		if (inseq)
			inseq++;
	}
}

/*
 * Read a psf font and return >= 0 on success and -1 on failure.
 * Failure means that the font was not psf (but has been read).
 * > 0 means that the Unicode table contains sequences.
 *
 * The font is read either from file (when FONT is non-NULL)
 * or from memory (namely from *ALLBUFP of size *ALLSZP).
 * In the former case, if ALLBUFP is non-NULL, a pointer to
 * the entire fontfile contents (possibly read from pipe)
 * is returned in *ALLBUFP, and the size in ALLSZP, where this
 * buffer was allocated using malloc().
 * In FONTBUFP, FONTSZP the subinterval of ALLBUFP containing
 * the font data is given.
 * The font width is stored in FONTWIDTHP.
 * The number of glyphs is stored in FONTLENP.
 * The unicodetable is stored in UCLISTHEADSP (when non-NULL), with
 * fontpositions counted from FONTPOS0 (so that calling this several
 * times can achieve font merging).
 */
extern char *progname;

int
readpsffont(FILE *fontf, char **allbufp, int *allszp,
	    char **fontbufp, int *fontszp,
	    int *fontwidthp, int *fontlenp, int fontpos0,
	    struct unicode_list **uclistheadsp) {
	char *inputbuf = NULL;
	int inputbuflth = 0;
	int inputlth, fontlen, fontwidth, charsize, hastable, ftoffset, utf8;
	int i, k, n;

	/*
	 * We used to look at the length of the input file
	 * with stat(); now that we accept compressed files,
	 * just read the entire file.
	 */
	if (fontf) {
		inputbuflth = MAXFONTSIZE/4; 	/* random */
		inputbuf = xmalloc(inputbuflth);
		n = 0;

		while(1) {
			if (n >= inputbuflth) {
				inputbuflth *= 2;
				inputbuf = xrealloc(inputbuf, inputbuflth);
			}
			n += fread(inputbuf+n, 1, inputbuflth-n, fontf);
			if (ferror(fontf)) {
				char *u = _("%s: Error reading input font");
				fprintf(stderr, u, progname);
				exit(EX_DATAERR);
			}
			if (feof(fontf))
				break;
		}
		if (allbufp)
			*allbufp = inputbuf;
		if (allszp)
			*allszp = n;
		inputlth = n;
	} else {
		if (!allbufp || !allszp) {
			char *u = _("%s: Bad call of readpsffont\n");
			fprintf(stderr, u, progname);
			exit(EX_SOFTWARE);
		}
		inputbuf = *allbufp;
		inputbuflth = inputlth = n = *allszp;
	}

	if (inputlth >= sizeof(struct psf1_header) &&
	    PSF1_MAGIC_OK((unsigned char *)inputbuf)) {
		struct psf1_header *psfhdr;

		psfhdr = (struct psf1_header *) &inputbuf[0];

		if (psfhdr->mode > PSF1_MAXMODE) {
			char *u = _("%s: Unsupported psf file mode (%d)\n");
			fprintf(stderr, u, progname, psfhdr->mode);
			exit(EX_DATAERR);
		}
		fontlen = ((psfhdr->mode & PSF1_MODE512) ? 512 : 256);
		charsize = psfhdr->charsize;
		hastable = (psfhdr->mode & (PSF1_MODEHASTAB|PSF1_MODEHASSEQ));
		ftoffset = sizeof(struct psf1_header);
		fontwidth = 8;
		utf8 = 0;
	} else if (inputlth >= sizeof(struct psf2_header) &&
		   PSF2_MAGIC_OK((unsigned char *)inputbuf)) {
		struct psf2_header *psfhdr;
		int flags;

		psfhdr = (struct psf2_header *) &inputbuf[0];

		if (psfhdr->version > PSF2_MAXVERSION) {
			char *u = _("%s: Unsupported psf version (%d)\n");
			fprintf(stderr, u, progname, psfhdr->version);
			exit(EX_DATAERR);
		}
		fontlen = assemble_int((unsigned char *) &psfhdr->length);
		charsize = assemble_int((unsigned char *) &psfhdr->charsize);
		flags = assemble_int((unsigned char *) &psfhdr->flags);
		hastable = (flags & PSF2_HAS_UNICODE_TABLE);
		ftoffset = assemble_int((unsigned char *) &psfhdr->headersize);
		fontwidth = assemble_int((unsigned char *) &psfhdr->width);
		utf8 = 1;
	} else
		return -1;	/* not psf */

	/* tests required - we divide by these */
	if (fontlen == 0) {
		char *u = _("%s: zero input font length?\n");
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}
	if (charsize == 0) {
		char *u = _("%s: zero input character size?\n");
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}
	i = ftoffset + fontlen * charsize;
	if (i > inputlth || (!hastable && i != inputlth)) {
		char *u = _("%s: Input file: bad input length (%d)\n");
		fprintf(stderr, u, progname, inputlth);
		exit(EX_DATAERR);
	}

	if (fontbufp && allbufp)
		*fontbufp = *allbufp + ftoffset;
	if (fontszp)
		*fontszp = fontlen * charsize;
	if (fontlenp)
		*fontlenp = fontlen;
	if (fontwidthp)
		*fontwidthp = fontwidth;

	if (!uclistheadsp)
		return 0;	/* got font, don't need unicode_list */

	*uclistheadsp = xrealloc(*uclistheadsp,
		(fontpos0+fontlen)*sizeof(struct unicode_list));

	if (hastable) {
		char *inptr, *endptr;

		inptr = inputbuf + ftoffset + fontlen * charsize;
		endptr = inputbuf + inputlth;

		for (i=0; i<fontlen; i++) {
			k = fontpos0 + i;
			get_uni_entry(&inptr, &endptr,
				      &(*uclistheadsp)[k], utf8);
		}
		if (inptr != endptr) {
			char *u = _("%s: Input file: trailing garbage\n");
			fprintf(stderr, u, progname);
			exit(EX_DATAERR);
		}
	} else {
		for (i=0; i<fontlen; i++) {
			k = fontpos0 + i;
			clear_uni_entry(&(*uclistheadsp)[k]);
		}
	}

	return 0;		/* got psf font */
}

static int
has_sequences(struct unicode_list *uclistheads, int fontlen) {
	struct unicode_list *ul;
	struct unicode_seq *us;
	int i;

	for (i=0; i<fontlen; i++) {
		ul = uclistheads[i].next;
		while(ul) {
			us = ul->seq;
			if (us && us->next)
				return 1;
			ul = ul->next;
		}
	}
	return 0;
}

void
appendunicode(FILE *fp, unsigned int uc, int utf8) {
	int n = 6;
	unsigned char out[6];

	if (uc & ~0x7fffffff) {
		fprintf(stderr, _("appendunicode: illegal unicode %u\n"), uc);
		exit(1);
	}
	if (!utf8) {
		out[--n] = ((uc >> 8) & 0xff);
		out[--n] = (uc & 0xff);
	} else if (uc < 0x80) {
		out[--n] = uc;
	} else {
		int mask = 0x3f;
		while (uc & ~mask) {
			out[--n] = 0x80 + (uc & 0x3f);
			uc >>= 6;
			mask >>= 1;
		}
		out[--n] = ((uc + ~mask + ~mask) & 0xff);
	}
	if (fwrite(out+n, 6-n, 1, fp) != 1) {
		perror("appendunimap");
		exit(1);
	}
	if (debug) {
		printf ("(");
		if (!utf8)
			printf ("U+");
		while (n < 6) printf ("%02x ", out[n++]);
		printf (")");
	}
}

void
appendseparator(FILE *fp, int seq, int utf8) {
	int n;

	if (utf8) {
		unsigned char u = (seq ? PSF2_STARTSEQ : PSF2_SEPARATOR);
		n = fwrite(&u, sizeof(u), 1, fp);
	} else {
		unsigned short u = (seq ? PSF1_STARTSEQ : PSF1_SEPARATOR);
		n = fwrite(&u, sizeof(u), 1, fp);
	}
	if (n != 1) {
		perror("appendseparator");
		exit(1);
	}
}

void
writepsffontheader(FILE *ofil, int width, int height, int fontlen,
		   int *psftype, int flags) {
	int bytewidth, charsize, ret;

	bytewidth = (width+7)/8;
	charsize = bytewidth * height;

	if ((fontlen != 256 && fontlen != 512) || width != 8)
		*psftype = 2;

	if (*psftype == 2) {
		struct psf2_header h;
		int flags2 = 0;

		if (flags & WPSFH_HASTAB)
			flags2 |= PSF2_HAS_UNICODE_TABLE;
		h.magic[0] = PSF2_MAGIC0;
		h.magic[1] = PSF2_MAGIC1;
		h.magic[2] = PSF2_MAGIC2;
		h.magic[3] = PSF2_MAGIC3;
		store_int_le((unsigned char *) &h.version, 0);
		store_int_le((unsigned char *) &h.headersize, sizeof(h));
		store_int_le((unsigned char *) &h.flags, flags2);
		store_int_le((unsigned char *) &h.length, fontlen);
		store_int_le((unsigned char *) &h.charsize, charsize);
		store_int_le((unsigned char *) &h.width, width);
		store_int_le((unsigned char *) &h.height, height);
		ret = fwrite(&h, sizeof(h), 1, ofil);
	} else {
		struct psf1_header h;

		h.magic[0] = PSF1_MAGIC0;
		h.magic[1] = PSF1_MAGIC1;
		h.mode = 0;
		if (fontlen == 512)
			h.mode |= PSF1_MODE512;
		if (flags & WPSFH_HASSEQ)
			h.mode |= PSF1_MODEHASSEQ;
		else if (flags & WPSFH_HASTAB)
			h.mode |= PSF1_MODEHASTAB;
		h.charsize = charsize;
		ret = fwrite(&h, sizeof(h), 1, ofil);
	}

	if (ret != 1) {
		fprintf(stderr, _("Cannot write font file header"));
		exit(EX_IOERR);
	}
}


int
writepsffont(FILE *ofil, char *fontbuf, int width, int height, int fontlen,
	     int psftype, struct unicode_list *uclistheads) {
	int bytewidth, charsize, flags, utf8, i;

	bytewidth = (width+7)/8;
	charsize = bytewidth * height;
	flags = 0;

	if (uclistheads) {
		flags |= WPSFH_HASTAB;
		if (has_sequences(uclistheads, fontlen))
			flags |= WPSFH_HASSEQ;
	}

	writepsffontheader(ofil, width, height, fontlen, &psftype, flags);
	utf8 = (psftype == 2);

	if ((fwrite(fontbuf, charsize, fontlen, ofil)) != fontlen) {
		fprintf(stderr, _("Cannot write font file"));
		exit(EX_IOERR);
	}

	/* unimaps: -1 => do nothing: caller will append map */
	if (uclistheads != NULL && uclistheads != (struct unicode_list*)-1) {
		struct unicode_list *ul;
		struct unicode_seq *us;

		for (i=0; i<fontlen; i++) {
			ul = uclistheads[i].next;
			while(ul) {
				us = ul->seq;
				if (us && us->next)
					appendseparator(ofil, 1, utf8);
				while(us) {
					appendunicode(ofil, us->uc, utf8);
					us = us->next;
				}
				ul = ul->next;
			}
			appendseparator(ofil, 0, utf8);
		}
	}
	return utf8;
}

