static char rcsid[] = "$Header: /u/jjc/dvitops/RCS/aftopl.c,v 1.3 89/02/22 11:40:31 jjc Rel $";

#include "util.h"

#define MAXNAMES 500
#define NOT_DEFINED 100000.0
#ifndef PI
#define PI 3.14159265358979324
#endif

struct ligatures {
	char *ch;
	char *succ;
	char *lig;
};

char *program_name = "aftopl";

static int lineno = 0;

FILE *infp;
FILE *outfp;
#ifdef PROTO
int lookup(const char *);
char *getkeyvalue(char *, int);
void scan_char_metric(char *);
void compute_font_dimens(void);
void do_kern_pairs(int);
void do_char_metrics(int);
void do_afms(void);
void print_font_dimens(void);
void do_encoding(char *);
void print_char_metrics(void);
void print_lig_kerns(void);
void do_ligatures(struct ligatures *);
#endif

#define bad_afm_file() message(FATAL_ERROR, "bad afm file, line %d", lineno)

struct lig_kern_list {
	struct lig_kern_list *next;
	int succ; 
	int lig; /* -1 if its a kern */
	double x;
};


struct {
	struct lig_kern_list *p;
	char *name;
	int code;
	double llx;
	double lly;
	double urx;
	double ury;
	double wx;
} table[MAXNAMES];

struct  {
	int is_fixed_pitch;
	double italic_angle;
	double x_height;
	/* we use max(urx - wx + italic_correction_space, 0) 
	   as the italic correction for each character */
	double italic_correction_space;
	double normal_space;
	double normal_stretch;
	double normal_shrink;
	double extra_space;
	double quad;
	double slant;
} font = { 0, 0.0, NOT_DEFINED, NOT_DEFINED, NOT_DEFINED, NOT_DEFINED, 
NOT_DEFINED, NOT_DEFINED, NOT_DEFINED, NOT_DEFINED};



int lookup(s)
const char *s;
{
	int i;
	for (i = 0; i < MAXNAMES; i++)
		if (table[i].name == NULL)
			break;
		else if (strcmp(table[i].name, s) == 0)
			return i;
	if (i == MAXNAMES)
		message(FATAL_ERROR, "too many names");
	if ((table[i].name = malloc(strlen(s) + 1)) == NULL)
		message(FATAL_ERROR, "out of memory");
	strcpy(table[i].name, s);
	table[i].code = -2;
	table[i].llx = table[i].lly = 0.0;
	table[i].urx = table[i].ury = 0.0;
	table[i].wx = 0.0;
	table[i].p = NULL;
	return i;
}


char *getkeyvalue(s, c)
char *s; 
char c;
{
	while (*s != '\0') {
		while (isspace(*s))
			s++;
		if (*s == c)
			return s + 1;
		while (*s != '\0')
			if (*s++ == ';')
				break;
	}
	return NULL;
}

void scan_char_metric(s)
char *s;
{
	char name[128];
	int code, n;
	double llx, lly, urx, ury;
	double wx, wy;
	char *p;
	while (isspace(*s))
		s++;
	if (*s == '\0') /* ignore blank line */
		return;
	p = getkeyvalue(s, 'N');
	if (p == NULL)
		bad_afm_file();
	/* sscanf(p, " %[A-Za-z0-9]", name); */
	/* Microsoft C scanf doesn't understand %[ */
	sscanf(p, "%s", name);
	if ((p = strchr(name, ';')) != NULL)
		*p = '\0';
	if ((p = getkeyvalue(s, 'C')) == NULL)
		bad_afm_file();
	if (sscanf(p, "%d", &code) != 1)
		bad_afm_file();
	p = getkeyvalue(s, 'B');
	if (p == NULL)
		bad_afm_file();
	if (sscanf(p, "%lg %lg %lg %lg", &llx, &lly, &urx, &ury) != 4)
		bad_afm_file();
	p = getkeyvalue(s, 'W');
	if (p == NULL)
		bad_afm_file();
	if (*p == 'X') {
		if (sscanf(p+1, "%lg", &wx) != 1)
			bad_afm_file();
		wy = 0.0;
	}
	else if (sscanf(p, "%lg %lg", &wx, &wy) != 2)
		bad_afm_file();
	n = lookup(name);
	table[n].code = code;
	table[n].wx = wx;
	table[n].llx = llx;
	table[n].lly = lly;
	table[n].urx = urx;
	table[n].ury = ury;
	while ((p = getkeyvalue(s, 'L')) != NULL) {
		char successor[128];
		char ligature[128];
		struct lig_kern_list *ptr;
		if (sscanf(p, " %s %s", successor, ligature) != 2)
			bad_afm_file();
		else if ((s = strchr(ligature, ';')) != NULL)
				*s = '\0';
		if ((s = strchr(p, ';')) == NULL)
			bad_afm_file();
		s++;
		if ((ptr = (struct lig_kern_list *)malloc(sizeof(struct lig_kern_list))) == NULL)
			message(FATAL_ERROR, "out of memory");
		ptr->succ = lookup(successor);
		ptr->lig = lookup(ligature);
		ptr->next = table[n].p;
		table[n].p = ptr;
	}
}


void compute_font_dimens()
{
	if (font.normal_space == NOT_DEFINED) {
		int n = lookup("space");
		if (table[n].code == -2)
			message(FATAL_ERROR, "no char metric for space character");
		else
			font.normal_space = table[n].wx;
	}
	if (font.quad == NOT_DEFINED)
		font.quad = (font.is_fixed_pitch?2.0:3.0)*font.normal_space;
	if (font.italic_correction_space == NOT_DEFINED)
		font.italic_correction_space = font.normal_space/12.0;
	if (font.normal_shrink == NOT_DEFINED)
		font.normal_shrink = font.is_fixed_pitch ? 0.0 :
						font.normal_space/3.0;
	if (font.normal_stretch == NOT_DEFINED)
		font.normal_stretch = font.is_fixed_pitch ? 0.0 :
						font.normal_space/2.0;
	if (font.extra_space == NOT_DEFINED)
		font.extra_space = (font.is_fixed_pitch ? 1.0 :0.5)
					*font.normal_space;
	if (font.slant == NOT_DEFINED)
		if (font.italic_angle == 0.0)
			font.slant = 0.0;
		else
			font.slant = sin(-font.italic_angle*PI/180.0);
	if (font.x_height == NOT_DEFINED)
		font.x_height = table[lookup("x")].ury;
}

void do_kern_pairs(n)
int n;
{
	char buf[512];
	char name1[128];
	char name2[128];
	double x, y;
	for (;;) {
		int i;
		struct lig_kern_list *ptr;
		lineno++;
		if (fgets(buf, 512, infp) == NULL)
			bad_afm_file();
		if (buf[0] == '\n')
			continue;
		if (strcmp(buf, "EndKernPairs\n") == 0)
			break;
		if (sscanf(buf, "KPX %s %s %lg", name1, name2, &x) != 3) {
			if (sscanf(buf, "KP %s %s %lg %lg", name1, name2,
					&x, &y) != 4)
				bad_afm_file();
		}
		else
			y = 0.0;
		i = lookup(name1);
		if ((ptr = (struct lig_kern_list *)malloc(sizeof(struct lig_kern_list))) == NULL)
			message(FATAL_ERROR, "out of memory");
		ptr->succ = lookup(name2);
		ptr->lig = -1;
		ptr->x = x;
		ptr->next = table[i].p;
		table[i].p = ptr;
		n--;
	}
	if (n != 0)
		message(WARNING, "wrong number of kern pairs");
}


void do_char_metrics(n)
int n;
{
	char buf[512];
	for (;;) {
		char *ptr = buf;
		lineno++;
		if (fgets(buf, 512, infp) == NULL)
			bad_afm_file();
		if (strcmp(buf, "EndCharMetrics\n") == 0)
			break;
		while (isspace(*ptr))
			ptr++;
		if (*ptr == '\0')
			continue;
		scan_char_metric(buf);
		n--;
	}
	if (n != 0) 
		message(WARNING, "wrong number of char metrics");
}

void do_afms()
{
	char buf[512];
	lineno = 0;

	while (fgets(buf, 512, infp) != NULL) {
		char *key, *value;
		key = buf;
		lineno++;
		while (isspace(*key))
			key++;
		value = key;
		while (*value != '\0' && !isspace(*value))
			value++;
		if (*value != '\0') {
			/* strip trailing white space */
			char *p;
			*value++ = '\0';
			while (isspace(*value))
				value++;
			p = value;
			while (*p != '\0')
				p++;
			while (p > value && isspace(*(p-1)))
				p--;
			*p = '\0';
		}
		if (strcmp(key, "IsFixedPitch") == 0)
			font.is_fixed_pitch = (strcmp(value, "true") == 0);
		else if (strcmp(key, "XHeight") == 0)
			font.x_height = atof(value);
		else if (strcmp(key, "StartCharMetrics") == 0)
			do_char_metrics(atoi(value));
		else if (strcmp(key, "ItalicAngle") == 0)
			font.italic_angle = atof(value);
		else if (strcmp(key, "StartKernPairs") == 0)
			do_kern_pairs(atoi(value));
		else if (strcmp(key, "italicCorrectionSpace") == 0)
			font.italic_correction_space = atof(value);
	}

}


void print_font_dimens()
{
	fprintf(outfp, "(FONTDIMEN\n");
    fprintf(outfp, "    (SLANT R %f)\n", font.slant);
	fprintf(outfp, "    (SPACE R %f)\n", font.normal_space/1000.0);
	fprintf(outfp, "    (SHRINK R %f)\n", font.normal_shrink/1000.0);
	fprintf(outfp, "    (STRETCH R %f)\n", font.normal_stretch/1000.0);
	fprintf(outfp, "    (EXTRASPACE R %f)\n", font.extra_space/1000.0);
	fprintf(outfp, "    (QUAD R %f)\n", font.quad/1000.0);
	fprintf(outfp, "    (XHEIGHT R %f)\n", font.x_height/1000.0);
	fprintf(outfp, "    )\n");
}


void do_encoding(encoding)
char *encoding;
{
	int i;
	char name[256];
	FILE *fp;
	char *texfonts;
	fprintf(outfp, "(CODINGSCHEME %s)\n", encoding);
	texfonts = getenv("TEXFONTS");
	if (texfonts == NULL)
		texfonts = TEXFONTS;
	if ((fp = xfopen(encoding, FALSE, texfonts, ".enc")) == NULL)
		message(FATAL_ERROR, "can't find %s", encoding);
	for (i = 0; i < MAXNAMES; i++)
		if (table[i].code >= -1)
			table[i].code = -1;
	for (i = 0; i < 256 && fscanf(fp, " %s ", name) == 1;  i++) {
		int n;
		if (strcmp(".notdef", name) == 0)
			continue;
		n = lookup(name);
		if (table[n].code == -2)
			message(WARNING, "%s in encoding but not in font", name);
		else if (table[n].code == -1)
			table[n].code = i;
		else
			message(ERROR, "%s appears more than once in encoding", name);
	}
}



void print_char_metrics()
{
	int i;
	for (i = 0; i < MAXNAMES; i++)
		if (table[i].name != NULL && table[i].code > 0) {
			double charic;
			fprintf(outfp, "(CHARACTER O %03o\n", table[i].code);
			fprintf(outfp, "    (COMMENT %s)\n", table[i].name);
			fprintf(outfp, "    (CHARWD R %f)\n", 
							table[i].wx/1000.0);
			/* negative heights and depths mess up \mathaccent */
			if (table[i].ury > 0.0)
				fprintf(outfp, "    (CHARHT R %f)\n", 
							table[i].ury/1000.0);
			if (table[i].lly < 0.0)
				fprintf(outfp, "    (CHARDP R %f)\n",
							-table[i].lly/1000.0);
			charic = table[i].urx - table[i].wx + font.italic_correction_space;
			if (charic > 0.0)
				fprintf(outfp, "    (CHARIC R %f)\n", 
					charic/1000.0);
			fputs("    )\n", outfp);
		}
}



void print_lig_kerns()
{
	int i;
	fputs("(LIGTABLE\n", outfp);
	for (i = 0; i < MAXNAMES; i++) {
		struct lig_kern_list *p;
		char label_line[128];
		if (table[i].name == NULL || table[i].code < 0)
			continue;
		sprintf(label_line, "    (LABEL O %03o)\n", table[i].code);
		/* print the ligatures */
		for (p = table[i].p; p != NULL; p = p->next)
			if (p->lig != -1 && table[p->lig].code > 0
					&& table[p->succ].code > 0) {
				fputs(label_line, outfp);
				label_line[0]= '\0';
				fprintf(outfp,"    (LIG O %03o O %03o)\n",
					table[p->succ].code,
					table[p->lig].code);
			}
		for (p = table[i].p; p != NULL; p = p->next)
			if (p->lig == -1 && table[p->succ].code > 0) {
				fputs(label_line, outfp);
				label_line[0] = '\0';
				fprintf(outfp, "    (KRN O %03o R %f)\n",
					table[p->succ].code,
					p->x/1000.0);
			}
		if (label_line[0] == '\0')
			fputs("    (STOP)\n", outfp);
	}
	fputs("    )\n", outfp);
}


struct ligatures standard_ligatures[] = {
	"hyphen", "hyphen", "endash",
	"endash", "hyphen", "emdash",
	"quoteleft", "quoteleft", "quotedblleft",
	"quoteright", "quoteright", "quotedblright",
	"exclam", "quoteleft", "exclamdown",
	"question", "quoteleft", "questiondown",
	NULL, NULL, NULL 
	};

struct ligatures tt_ligatures[] = {
	"exclam", "quoteleft", "exclamdown",
	"question", "quoteleft", "questiondown",
	NULL, NULL, NULL
	};


void do_ligatures(lig)
struct ligatures *lig;
{
	int i;
	for (i = 0; lig[i].lig != NULL; i++) {
		int n;
		struct lig_kern_list *p;
		n = lookup(lig[i].ch);
		if ((p = (struct lig_kern_list *)malloc(sizeof(struct lig_kern_list))) == NULL)
			message(FATAL_ERROR, "out_of_memory");
		p->succ = lookup(lig[i].succ);
		p->lig = lookup(lig[i].lig);
		p->next = table[n].p;
		table[n].p = p;
	}
}



int main(argc, argv)
int argc;
char **argv;
{
	char filename[128];
	int c;
	char *efile = NULL;
	while ((c = getopt(argc, argv, "e:")) != EOF)
		switch(c) {
		case 'e':
			efile = optarg;
			break;
		case '?':
			goto usage;
		}
	if (argc - optind < 1)
		goto usage;
	strcpy(filename, argv[optind]);
	if (strchr(filename, '.') == NULL)
		strcat(filename, ".afm");
	if ((infp = fopen(filename, "r")) == NULL)
		message(FATAL_ERROR, "can't open %s", filename);
	if (argc - optind == 1)
		strcpy(strchr(filename, '.'), ".pl");
	else if (argc - optind == 2) {
		strcpy(filename, argv[optind+1]);
		if (strchr(filename, '.') == NULL)
			strcat(filename, ".pl");
	}
	else
		goto usage;
	if ((outfp = fopen(filename, "w")) == NULL)
		message(FATAL_ERROR, "can't open %s", filename);
	do_afms();
	if (efile != NULL)
		do_encoding(efile);
	if (font.is_fixed_pitch)
		do_ligatures(tt_ligatures);
	else
		do_ligatures(standard_ligatures);
	compute_font_dimens();
	print_font_dimens();
	print_char_metrics();
	print_lig_kerns();
	exit(history);
usage:
	message(FATAL_ERROR, "usage: aftopl [-e encoding] afmfile [plfile]");
}
