/*
 * PostScript Type 1 font file support for Halibut
 */
/*
 * Type 1 font file formats are specified by Adobe Technical Note
 * #5040: "Supporting Downloadable PostScript Language Fonts".
 * Halibut supports hexadecimal format (section 3.1) and IBM PC format
 * (section 3.3), commonly called PFA and PFB respectively.
 */

#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include "halibut.h"
#include "paper.h"

#define PFB_ASCII	1
#define PFB_BINARY	2
#define PFB_EOF		3

typedef struct t1_font_Tag t1_font;
typedef struct t1_data_Tag t1_data;

font_info *all_fonts;

struct t1_font_Tag {
    t1_data *data;
    size_t length1;
    size_t length2;
    filepos pos;
};

struct t1_data_Tag {
    char type;
    size_t length;
    unsigned char *data;
    t1_data *next;
};

typedef struct pfstate_Tag {
    t1_data *data;
    t1_data *curblock;
    size_t offset;
} pfstate;

static void pf_identify(t1_font *tf);

static t1_data *load_pfb_file(FILE *fp, filepos *pos) {
    t1_data *head = NULL, *tail = NULL;
    int c, i;
    char type;

    pos->line = 0;
    for (;;) {
	if (fgetc(fp) != 128) abort();
	type = fgetc(fp);
	if (type == PFB_EOF) return head;
	if (tail) {
	    tail->next = snew(t1_data);
	    tail = tail->next;
	} else {
	    head = snew(t1_data);
	    tail = head;
	}
	tail->type = type;
	tail->length = 0;
	for (i = 0; i < 4; i++) {
	    c = fgetc(fp);
	    if (c == EOF) abort();
	    tail->length |= c << (8 * i);
	}
	tail->data = snewn((int)tail->length, unsigned char);
	if (fread(tail->data, 1, tail->length, fp) != tail->length) abort();
    }
}

static t1_data *load_pfa_file(FILE *fp, filepos *pos) {
    t1_data *ret = snew(t1_data);
    size_t off = 0, len, got;

    pos->line = 0;
    ret->type = PFB_ASCII;
    len = 32768;
    ret->data = snewn((int)len, unsigned char);
    for (;;) {
	got = fread(ret->data + off, 1, len - off, fp);
	off += got;
	if (off != len) break;
	len *= 2;
	ret->data = sresize(ret->data, (int)len, unsigned char);
    }
    ret->data = sresize(ret->data, (int)off, unsigned char);
    ret->length = off;
    return ret;
}

void read_pfa_file(input *in) {
    t1_font *tf = snew(t1_font);

    tf->data = load_pfa_file(in->currfp, &in->pos);
    tf->pos = in->pos;
    tf->length1 = tf->length2 = 0;
    fclose(in->currfp);
    pf_identify(tf);
}

void read_pfb_file(input *in) {
    t1_font *tf = snew(t1_font);

    tf->data = load_pfb_file(in->currfp, &in->pos);
    tf->pos = in->pos;
    tf->length1 = tf->length2 = 0;
    fclose(in->currfp);
    pf_identify(tf);
}
static char *pf_read_token(pfstate *);

/*
 * Read a character from the initial plaintext part of a Type 1 font
 */
static int pf_getc(pfstate *pf) {
    if (pf->offset == pf->curblock->length) {
	if (pf->curblock->next == NULL) return EOF;
	pf->curblock = pf->curblock->next;
	pf->offset = 0;
    }
    if (pf->curblock->type != PFB_ASCII) return EOF;
    return pf->curblock->data[pf->offset++];
}

static void pf_ungetc(int c, pfstate *pf) {
    assert(pf->offset > 0);
    pf->offset--;
    assert(c == pf->curblock->data[pf->offset]);
}

static void pf_rewind(pfstate *pf) {
    pf->curblock = pf->data;
    pf->offset = 0;
}

static void pf_seek(pfstate *pf, size_t off) {
    t1_data *td = pf->data;

    while (td->length < off) {
	off -= td->length;
	td = td->next;
    }
    pf->curblock = td;
    pf->offset = off;
}

static size_t pf_tell(pfstate *pf) {
    t1_data *td = pf->data;
    size_t o = 0;

    while (td != pf->curblock) {
	o += td->length;
	td = td->next;
    }
    return o + pf->offset;
}

static void pf_identify(t1_font *tf) {
    rdstringc rsc = { 0, 0, NULL };
    char *p;
    size_t len;
    char *fontname;
    font_info *fi;
    int c;
    pfstate pfs, *pf = &pfs;

    pf->data = tf->data;
    pf_rewind(pf);
    do {
	c = pf_getc(pf);
	if (c == EOF) {
	    sfree(rsc.text);
	    error(err_pfeof, &tf->pos);
	    return;
	}
	rdaddc(&rsc, c);
    } while (c != 012 && c != 015);
    p = rsc.text;
    if ((p = strchr(p, ':')) == NULL) {
	sfree(rsc.text);
	error(err_pfhead, &tf->pos);
	return;
    }
    p++;
    p += strspn(p, " \t");
    len = strcspn(p, " \t");
    fontname = snewn((int)len + 1, char);
    memcpy(fontname, p, len);
    fontname[len] = 0;
    sfree(rsc.text);

    for (fi = all_fonts; fi; fi = fi->next) {
	if (strcmp(fi->name, fontname) == 0) {
	    fi->fontfile = tf;
	    sfree(fontname);
	    return;
	}
    }
    error(err_pfnoafm, &tf->pos, fontname);
    sfree(fontname);
}

/*
 * PostScript white space characters; PLRM3 table 3.1
 */
static int pf_isspace(int c) {
    return c == 000 || c == 011 || c == 012 || c == 014 || c == 015 ||
	c == ' ';
}

/*
 * PostScript special characters; PLRM3 page 27
 */
static int pf_isspecial(int c) {
    return c == '(' || c == ')' || c == '<' || c == '>' || c == '[' ||
	c == ']' || c == '{' || c == '}' || c == '/' || c == '%';
}

static size_t pf_findtoken(t1_font *tf, size_t off, char const *needle) {
    char *tok;
    pfstate pfs, *pf = &pfs;

    pf->data = tf->data;
    pf_seek(pf, off);
    for (;;) {
	tok = pf_read_token(pf);
	if (tok == NULL) {
	    if (pf->offset == 0 && pf->curblock->type == PFB_BINARY)
		pf->curblock = pf->curblock->next;
	    else
		return (size_t)-1;
	} else {
	    if (strcmp(tok, needle) == 0) {
		sfree(tok);
		return pf_tell(pf);
	    }
	    sfree(tok);
	}
    }
}

static size_t pf_length1(t1_font *tf) {
    size_t ret;

    ret = pf_findtoken(tf, 0, "eexec");
    if (ret == (size_t)-1) {
	error(err_pfeof, &tf->pos);
	return 0;
    }
    return ret;
}

static size_t pf_length2(t1_font *tf) {
    size_t ret;

    if (tf->length1 == 0)
	tf->length1 = pf_length1(tf);
    ret = pf_findtoken(tf, tf->length1, "cleartomark");
    if (ret == (size_t)-1) {
	error(err_pfeof, &tf->pos);
	return 0;
    }
    return ret - 12 - tf->length1; /* backspace over "cleartomark\n" */
}

static void pf_getascii(t1_font *tf, size_t off, size_t len,
			char **bufp, size_t *lenp) {
    t1_data *td = tf->data;
    size_t blk, i;
    char *p;

    while (td && off >= td->length) {
	off -= td->length;
	td = td->next;
    }
    *bufp = NULL;
    *lenp = 0;
    while (td && len) {
	blk = len < td->length ? len : td->length;
	if (td->type == PFB_ASCII) {
	    *bufp = sresize(*bufp, (int)(*lenp + blk), char);
	    memcpy(*bufp + *lenp, td->data + off, blk);
	    *lenp += blk;
	} else {
	    *bufp = sresize(*bufp, (int)(*lenp + blk * 2 + blk / 39 + 3), char);
	    p = *bufp + *lenp;
	    for (i = 0; i < blk; i++) {
		if (i % 39 == 0) p += sprintf(p, "\n");
		p += sprintf(p, "%02x", td->data[off + i]);
	    }
	    p += sprintf(p, "\n");
	    *lenp = p - *bufp;
	}
	len -= blk;
	td = td->next;
	off = 0;
    }   
}

void pf_writeps(font_info const *fi, FILE *ofp) {
    char *buf;
    size_t len;

    pf_getascii(fi->fontfile, 0, INT_MAX, &buf, &len);
    fwrite(buf, 1, len, ofp);
    sfree(buf);
}

static int hexval(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'A' && c <= 'F') return c - 'A' + 0xA;
    if (c >= 'a' && c <= 'f') return c - 'a' + 0xa;
    return 0;
}

static void pf_getbinary(t1_font *tf, size_t off, size_t len,
			 char **bufp, size_t *lenp) {
    t1_data *td = tf->data;
    size_t blk, i;
    int havenybble = 0;
    char *p, nybble;

    while (td && off >= td->length) {
	off -= td->length;
	td = td->next;
    }
    *bufp = NULL;
    *lenp = 0;
    while (td && len) {
	blk = len < td->length ? len : td->length;
	if (td->type == PFB_BINARY) {
	    *bufp = sresize(*bufp, (int)(*lenp + blk), char);
	    memcpy(*bufp + *lenp, td->data + off, blk);
	    *lenp += blk;
	} else {
	    *bufp = sresize(*bufp, (int)(*lenp + blk / 2 + 1), char);
	    p = *bufp + *lenp;
	    for (i = 0; i < blk; i++) {
		if (pf_isspace(td->data[off + i])) continue;
		if (!havenybble)
		    nybble = hexval(td->data[off+i]);
		else
		    *p++ = (nybble << 4) | hexval(td->data[off+i]);
		havenybble = !havenybble;
	    }
	    *lenp = p - *bufp;
	}
	len -= blk;
	td = td->next;
	off = 0;
    }   
}


/*
 * Return the initial, unencrypted, part of a font.
 */
void pf_part1(font_info *fi, char **bufp, size_t *lenp) {
    t1_font *tf = fi->fontfile;

    if (tf->length1 == 0)
	tf->length1 = pf_length1(tf);
    pf_getascii(tf, 0, tf->length1, bufp, lenp);
}

/*
 * Return the middle, encrypted, part of a font.
 */
void pf_part2(font_info *fi, char **bufp, size_t *lenp) {
    t1_font *tf = fi->fontfile;

    if (tf->length2 == 0)
	tf->length2 = pf_length2(tf);
    pf_getbinary(tf, tf->length1, tf->length2, bufp, lenp);
    if (*lenp >= 256)
	*lenp -= 256;
}

static char *pf_read_litstring(pfstate *pf) {
    rdstringc rsc = { 0, 0, NULL };
    int depth = 1;
    int c;

    rdaddc(&rsc, '(');
    do {
	c = pf_getc(pf);
	switch (c) {
	  case '(':
	    depth++; break;
	  case ')':
	    depth--; break;
	  case '\\':
	    rdaddc(&rsc, '\\');
	    c = pf_getc(pf);
	    break;
	}
	if (c != EOF) rdaddc(&rsc, c);
    } while (depth > 0 && c != EOF);
    return rsc.text;
}

static char *pf_read_hexstring(pfstate *pf) {
    rdstringc rsc = { 0, 0, NULL };
    int c;

    rdaddc(&rsc, '<');
    do {
	c = pf_getc(pf);
	if (c != EOF) rdaddc(&rsc, c);
    } while (c != '>' && c != EOF);
    return rsc.text;
}   

static char *pf_read_word(pfstate *pf, int c) {
    rdstringc rsc = { 0, 0, NULL };

    rdaddc(&rsc, c);
    if (c == '{' || c == '}' || c == '[' || c == ']')
	return rsc.text;
    for (;;) {
	c = pf_getc(pf);
	if (pf_isspecial(c) || pf_isspace(c) || c == EOF) break;
	rdaddc(&rsc, c);
    }
    if (pf_isspecial(c)) pf_ungetc(c, pf);
    return rsc.text;
}   

static char *pf_read_token(pfstate *pf) {
    int c;

    do {
	c = pf_getc(pf);
    } while (pf_isspace(c));
    if (c == EOF) return NULL;
    if (c == '%') {
	do {
	    c = pf_getc(pf);
	} while (c != 012 && c != 015);
	return pf_read_token(pf);
    }
    if (c == '(') return pf_read_litstring(pf);
    if (c == '<') return pf_read_hexstring(pf);
    return pf_read_word(pf, c);
}
