/*
 * psfxtable.c
 *
 * Manipulate headers and Unicode tables for psf fonts
 *
 * Copyright (C) 1999 Andries E. Brouwer
 *  derived from sources that were
 * Copyright (C) 1994 H. Peter Anvin
 *
 * This program may be freely copied under the terms of the GNU
 * General Public License (GPL), version 2, or at your option
 * any later version.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sysexits.h>
#include "nls.h"
#include "version.h"
#include "psf.h"
#include "xmalloc.h"
#include "psffontop.h"

/*
 * call: psfxtable -i infont -o outfont -it intable -ot outtable
 *       psfaddtable infont intable [outfont]
 *	 psfgettable infont [outtable]
 *	 psfstriptable infont [outfont]
 *
 * When outfont is requested it will get psf1 header when
 * infont had psf1 header and intable does not have sequences
 * and psf2 header otherwise.
 */

/*
 * Parse humanly readable unicode table.
 * Format: lines
 *   <fontposition><tab><uc_defs>
 * or
 *   <fontrange><tab><uc_defs>
 * or
 *   <fontrange><tab><uc_range>
 *  where
 *   <uc_defs> :: <empty> | <uc_def><space><uc_defs>
 *   <uc_def> :: <uc> | <uc>,<uc_def>
 *   <uc> :: U+<h><h><h><h>
 *   <h> :: <hexadecimal digit>
 *   <range> :: <value>-<value>
 *  Blank lines and lines starting with # are ignored.
 */
struct unicode_list *uclistheads;

static void
addpair(int fontpos, 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                  = uclistheads[fontpos].prev;
	ul->prev->next            = ul;
	ul->next                  = NULL;
	uclistheads[fontpos].prev = ul;
}

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

	ul             = uclistheads[fontpos].prev;
	us             = xmalloc(sizeof(struct unicode_seq));
	us->uc         = uc;
	us->prev       = ul->seq->prev;
	us->prev->next = us;
	us->next       = NULL;
	ul->seq->prev  = us;
}

static int
getunicode(char **p0)
{
	char *p = *p0;

	while (*p == ' ' || *p == '\t')
		p++;
	if (*p != 'U' || p[1] != '+' ||
	    !isxdigit(p[2]) || !isxdigit(p[3]) || !isxdigit(p[4]) ||
	    !isxdigit(p[5]) || isxdigit(p[6]))
		return -1;
	*p0 = p + 6;
	return strtol(p + 2, 0, 16);
}

static void
parse_itab_line(char *buf, int fontlen)
{
	char *p, *p1;
	int i;
	long fp0, fp1, un0, un1;

	if ((p = strchr(buf, '\n')) != NULL)
		*p = 0;
	else {
		char *u = _("%s: Warning: line too long\n");
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}

	p = buf;

	while (*p == ' ' || *p == '\t')
		p++;
	if (!*p || *p == '#')
		return;

	fp0 = strtol(p, &p1, 0);
	if (p1 == p) {
		char *u = _("%s: Bad input line: %s\n");
		fprintf(stderr, u, progname, buf);
		exit(EX_DATAERR);
	}
	p = p1;

	if (*p == '-') {
		p++;
		fp1 = strtol(p, &p1, 0);
		if (p1 == p) {
			char *u = _("%s: Bad input line: %s\n");
			fprintf(stderr, u, progname, buf);
			exit(EX_DATAERR);
		}
		p = p1;
	} else
		fp1 = 0;

	if (fp0 < 0 || fp0 >= fontlen) {
		char *u = _("%s: Glyph number (0x%lx) past end of font\n");
		fprintf(stderr, u, progname, fp0);
		exit(EX_DATAERR);
	}
	if (fp1 && (fp1 < fp0 || fp1 >= fontlen)) {
		char *u = _("%s: Bad end of range (0x%lx)\n");
		fprintf(stderr, u, progname, fp1);
		exit(EX_DATAERR);
	}

	if (fp1) {
		/* we have a range; expect the word "idem"
		   or a Unicode range of the same length */
		while (*p == ' ' || *p == '\t')
			p++;
		if (!strncmp(p, "idem", 4)) {
			for (i = fp0; i <= fp1; i++)
				addpair(i, i);
			p += 4;
		} else {
			un0 = getunicode(&p);
			while (*p == ' ' || *p == '\t')
				p++;
			if (*p != '-') {
				char *u = _("%s: Corresponding to a range of "
				            "font positions, there should be "
				            "a Unicode range\n");
				fprintf(stderr, u, progname);
				exit(EX_DATAERR);
			}
			p++;
			un1 = getunicode(&p);
			if (un0 < 0 || un1 < 0) {
				char *u = _("%s: Bad Unicode range "
				            "corresponding to font position "
				            "range 0x%x-0x%x\n");
				fprintf(stderr, u, progname, fp0, fp1);
				exit(EX_DATAERR);
			}
			if (un1 - un0 != fp1 - fp0) {
				char *u = _("%s: Unicode range U+%x-U+%x not "
				            "of the same length as font "
				            "position range 0x%x-0x%x\n");
				fprintf(stderr, u, progname,
				        un0, un1, fp0, fp1);
				exit(EX_DATAERR);
			}
			for (i = fp0; i <= fp1; i++)
				addpair(i, un0 - fp0 + i);
		} /* not idem */
	} else {  /* no range */
		while ((un0 = getunicode(&p)) >= 0) {
			addpair(fp0, un0);
			while (*p++ == ',' && (un1 = getunicode(&p)) >= 0) {
				addseq(fp0, un1);
			}
			p--;
		}
		while (*p == ' ' || *p == '\t')
			p++;
		if (*p && *p != '#') {
			char *u = _("%s: trailing junk (%s) ignored\n");
			fprintf(stderr, u, progname, p);
		}
	}
}

static void
read_itable(FILE *itab, int fontlen, struct unicode_list **uclistheadsp)
{
	char buf[65536];
	int i;

	if (uclistheadsp) {
		*uclistheadsp = xrealloc(*uclistheadsp,
		                         fontlen * sizeof(struct unicode_list));
		for (i = 0; i < fontlen; i++) {
			struct unicode_list *up = &((*uclistheadsp)[i]);
			up->next                = NULL;
			up->seq                 = NULL;
			up->prev                = up;
		}
		while (fgets(buf, sizeof(buf), itab) != NULL)
			parse_itab_line(buf, fontlen);
	}
}

int debug = 0;

int main(int argc, char **argv)
{
	char *ifname, *ofname, *itname, *otname;
	FILE *ifil, *ofil, *itab, *otab;
	int psftype, fontlen, charsize, hastable, notable;
	int i;
	int width = 8, bytewidth, height;
	char *inbuf, *fontbuf;
	int inbuflth, fontbuflth;

	set_progname(argv[0]);

	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE_NAME, LOCALEDIR);
	textdomain(PACKAGE_NAME);

	if (argc == 2 && !strcmp(argv[1], "-V"))
		print_version_and_exit();

	ifil = ofil = itab = otab = NULL;
	ifname = ofname = itname = otname = NULL;
	fontbuf                           = NULL;
	notable                           = 0;

	if (!strcmp(progname, "psfaddtable")) {
		/* Do not send binary data to stdout without explicit "-" */
		if (argc != 4) {
			char *u = _("Usage:\n\t%s infont intable outfont\n");
			fprintf(stderr, u, progname);
			exit(EX_USAGE);
		}
		ifname = argv[1];
		itname = argv[2];
		ofname = argv[3];
	} else if (!strcmp(progname, "psfgettable")) {
		if (argc < 2 || argc > 3) {
			char *u = _("Usage:\n\t%s infont [outtable]\n");
			fprintf(stderr, u, progname);
			exit(EX_USAGE);
		}
		ifname = argv[1];
		otname = (argc == 3) ? argv[2] : "-";
	} else if (!strcmp(progname, "psfstriptable")) {
		/* Do not send binary data to stdout without explicit "-" */
		if (argc != 3) {
			char *u = _("Usage:\n\t%s infont outfont\n");
			fprintf(stderr, u, progname);
			exit(EX_USAGE);
		}
		ifname  = argv[1];
		ofname  = argv[2];
		notable = 1;
	} else {
		for (i = 1; i < argc; i++) {
			if ((!strcmp(argv[i], "-i") || !strcmp(argv[i], "-if")) && i < argc - 1)
				ifname = argv[++i];
			else if ((!strcmp(argv[i], "-o") || !strcmp(argv[i], "-of")) && i < argc - 1)
				ofname = argv[++i];
			else if (!strcmp(argv[i], "-it") && i < argc - 1)
				itname = argv[++i];
			else if (!strcmp(argv[i], "-ot") && i < argc - 1)
				otname = argv[++i];
			else if (!strcmp(argv[i], "-nt"))
				notable = 1;
			else
				break;
		}
		if (i < argc || argc <= 1) {
			char *u = _("Usage:\n\t%s [-i infont] [-o outfont] "
			            "[-it intable] [-ot outtable] [-nt]\n");
			fprintf(stderr, u, progname);
			exit(EX_USAGE);
		}
	}

	if (!ifname)
		ifname = "-";
	if (!strcmp(ifname, "-"))
		ifil = stdin;
	else {
		ifil = fopen(ifname, "r");
		if (!ifil) {
			perror(ifname);
			exit(EX_NOINPUT);
		}
	}

	if (!itname)
		/* nothing */;
	else if (!strcmp(itname, "-"))
		itab = stdin;
	else {
		itab = fopen(itname, "r");
		if (!itab) {
			perror(itname);
			exit(EX_NOINPUT);
		}
	}

	/* Refuse ifil == itab == stdin ? Perhaps not. */

	if (!ofname)
		/* nothing */;
	else if (!strcmp(ofname, "-"))
		ofil = stdout;
	else {
		ofil = fopen(ofname, "w");
		if (!ofil) {
			perror(ofname);
			exit(EX_CANTCREAT);
		}
	}

	if (!otname)
		/* nothing */;
	else if (!strcmp(otname, "-"))
		otab = stdout;
	else {
		otab = fopen(otname, "w");
		if (!otab) {
			perror(otname);
			exit(EX_CANTCREAT);
		}
	}

	if (readpsffont(ifil, &inbuf, &inbuflth, &fontbuf, &fontbuflth,
	                &width, &fontlen, 0,
	                itab ? NULL : &uclistheads) == -1) {
		char *u = _("%s: Bad magic number on %s\n");
		fprintf(stderr, u, progname, ifname);
		exit(EX_DATAERR);
	}
	fclose(ifil);

	charsize  = fontbuflth / fontlen;
	bytewidth = (width + 7) / 8;
	if (!bytewidth)
		bytewidth = 1;
	height            = charsize / bytewidth;

	hastable = (uclistheads != NULL);

	if (PSF1_MAGIC_OK((unsigned char *)inbuf)) {
		psftype = 1;
	} else if (PSF2_MAGIC_OK((unsigned char *)inbuf)) {
		psftype = 2;
	} else {
		char *u = _("%s: psf file with unknown magic\n");
		fprintf(stderr, u, progname);
		exit(EX_DATAERR);
	}

	if (itab) {
		read_itable(itab, fontlen, &uclistheads);
		fclose(itab);
	}

	if (otab) {
		struct unicode_list *ul;
		struct unicode_seq *us;
		char *sep;

		if (!hastable) {
			char *u = _("%s: input font does not have an index\n");
			fprintf(stderr, u, progname);
			exit(EX_DATAERR);
		}
		fprintf(otab,
		        "#\n# Character table extracted from font %s\n#\n",
		        ifname);
		for (i = 0; i < fontlen; i++) {
			fprintf(otab, "0x%03x\t", i);
			sep = "";
			ul  = uclistheads[i].next;
			while (ul) {
				us = ul->seq;
				while (us) {
					fprintf(otab, "%sU+%04x", sep, us->uc);
					us  = us->next;
					sep = ", ";
				}
				ul  = ul->next;
				sep = " ";
			}
			fprintf(otab, "\n");
		}
		fclose(otab);
	}

	if (ofil) {
		writepsffont(ofil, fontbuf, width, height, fontlen, psftype,
		             notable ? NULL : uclistheads);
		fclose(ofil);
	}

	return EX_OK;
}
