#include <stdio.h>
#include <limits.h>
#include <time.h>

typedef unsigned char   BYTE;
typedef signed char     CHAR;
typedef unsigned short  USHORT;
typedef signed short    SHORT;
typedef unsigned long   ULONG;
typedef signed long     LONG;
typedef unsigned long   FIXED;
typedef unsigned short  FUNIT;
typedef signed short    FWORD;
typedef unsigned short  UFWORD;
typedef unsigned short  F2DOT14;

#define BYTE_SIZE       1
#define CHAR_SIZE       1
#define USHORT_SIZE     2
#define SHORT_SIZE      2
#define ULONG_SIZE      4
#define LONG_SIZE       4
#define FIXED_SIZE      4
#define FWORD_SIZE      2
#define UFWORD_SIZE     2
#define F2DOT14_SIZE    2

#define GET_TYPE(t)  	((t)getnum(t##_SIZE))

#define GET_BYTE()      GET_TYPE(BYTE)
#define GET_CHAR()      GET_TYPE(CHAR)
#define GET_USHORT()    GET_TYPE(USHORT)
#define GET_SHORT()     GET_TYPE(SHORT)
#define GET_ULONG()     GET_TYPE(ULONG)
#define GET_LONG()      GET_TYPE(LONG)
#define GET_FIXED()     GET_TYPE(FIXED)
#define GET_FUNIT()     GET_TYPE(FUNIT)
#define GET_FWORD()     GET_TYPE(FWORD)
#define GET_UFWORD()    GET_TYPE(UFWORD)
#define GET_F2DOT14()   GET_TYPE(F2DOT14)

#define NTABS           24

#define AND             ,
#define FAIL(S) do {\
    fprintf(stderr, "Error: ");\
    if (filename != NULL)\
        fprintf(stderr, "file `%s': ", filename);\
    fprintf(stderr, S);\
    fprintf(stderr, "\n");\
    _exit(-1);\
} while (0)

#define WARN(S) do {\
    fprintf(stderr, "Warning: ");\
    if (filename != NULL)\
        fprintf(stderr, "file `%s': ", filename);\
    fprintf(stderr, S);\
    fprintf(stderr, "\n");\
} while (0)

typedef struct _dirtab_entry {
    char tag[4];
    ULONG checksum;
    ULONG offset;
    ULONG length;
} dirtab_entry;

#define NCHARS          1024
#define STRPOOL_SIZE    10240
#define NMACGLYPHS      258

#define SKIP(n)         getnum(n)
#define PRINT_STR(S)    if (S != NULL ) printf(#S " %s\n", S)
#define PRINT_NUM(N)    if (N != LONG_MIN) printf(#N " %i\n", N)

#define FUNIT(n) \
    (n < 0 ? -((-n/upem)*1000 + ((-n%upem)*1000)/upem) :\
    ((n/upem)*1000 + ((n%upem)*1000)/upem))

char *FontName = NULL;
char *FullName = NULL;
char *Notice = NULL;
LONG ItalicAngle = LONG_MIN;
LONG IsFixedPitch = LONG_MIN;
LONG FontBBox1 = LONG_MIN;
LONG FontBBox2 = LONG_MIN;
LONG FontBBox3 = LONG_MIN;
LONG FontBBox4 = LONG_MIN;
LONG UnderlinePosition = LONG_MIN;
LONG UnderlineThickness = LONG_MIN;
LONG CapHeight = LONG_MIN;
LONG XHeight = LONG_MIN;
LONG Ascender = LONG_MIN;
LONG Descender = LONG_MIN;

typedef struct _mtx_entry {
    ULONG WX;
    union { 
        char *name;
        USHORT index;
    } N;
    union {
        LONG B[4];
        LONG offset;
    } glyph;
    unsigned char used;
} mtx_entry;

dirtab_entry dirtab[NTABS];
mtx_entry mtxtab[NCHARS];
FWORD kern_data[NCHARS][NCHARS];
char *filename = NULL;
FILE *ttfont;
USHORT upem;
USHORT ntabs = 0;
int nhmtx;
int post_format;
int loca_format;
int nglyphs;
int nkernpairs = 0;
int char_code = 0;
char str_pool[STRPOOL_SIZE], *pool_ptr = str_pool;
char *ps_glyphs, *enc_glyphs[256];
short enc_index[256];
char notdef[] = ".notdef";
char *mac_glyph[NMACGLYPHS] = {
notdef, ".null", "CR", "space", "exclam", "quotedbl", "numbersign", "dollar",
"percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk",
"plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three",
"four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less",
"equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
"underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
"braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring",
"Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave",
"acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave",
"ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis",
"ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute",
"ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling",
"section", "bullet", "paragraph", "germandbls", "registered", "copyright",
"trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity",
"plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "Sigma",
"Pi", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
"oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin",
"approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis",
"nbspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash",
"quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide",
"lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft",
"guilsinglright", "fi", "fl", "daggerdbl", "middot", "quotesinglbase",
"quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute",
"Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave",
"Oacute", "Ocircumflex", "applelogo", "Ograve", "Uacute", "Ucircumflex",
"Ugrave", "dotlessi", "circumflex", "tilde", "overscore", "breve",
"dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash",
"lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth",
"Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", "onesuperior",
"twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters",
"franc", "Gbreve", "gbreve", "Idot", "Scedilla", "scedilla", "Cacute",
"cacute", "Ccaron", "ccaron", "dmacron"
};

long getnum(int s)
{
    long i = 0;
    int c;
    while (s > 0) {
        if ((c = getc(ttfont)) < 0) {
            if (c == EOF)
                FAIL("unexpected EOF");
            else
                FAIL("getc() failed");
        }
        i = (i << 8) + c;
        s--;
    }
    return i;
}

dirtab_entry *name_lookup(char *s)
{
    dirtab_entry *p;
    for (p = dirtab; p - dirtab < ntabs; p++)
        if (strncmp(p->tag, s, 4) == 0)
            break;
    if (p - dirtab == ntabs)
        p = NULL;
    return p;
}

void seek_tab(char *name, LONG offset)
{
    dirtab_entry *pd = name_lookup(name);
    if (pd == NULL)
        FAIL("can't find table `%s'" AND name);
    if (fseek(ttfont, pd->offset + offset, SEEK_SET) < 0)
        FAIL("fseek() failed while reading `%s' table" 
        AND name);
}

void seek_off(char *name, LONG offset)
{
    if (fseek(ttfont, offset, SEEK_SET) < 0)
        FAIL("fseek() failed while reading `%s' table" 
        AND name);
}

#define GET_LINE()  \
{ \
    p = buf; \
    do { \
        if ((c = getc(encf)) < 0) \
            if (c == EOF) \
                FAIL("unexpected EOF while reading encoding file"); \
            else \
                FAIL("getc() failed"); \
        if (c == '\t') \
            c = ' '; \
        if (c == '\r') \
            c = '\n'; \
        if (c != ' ' || (p > buf && p[-1] != ' ')) \
            *p++ = c; \
    } while (c != '\n'); \
    if (p - buf > 1 && p[-2] == ' ') { \
        p[-2] = '\n'; \
        p--; \
    } \
    *p = 0; \
}

void read_encoding(char *f)
{
    FILE *encf;
    char buf[1024], *p, *q, **e;
    int c;
    if ((encf = fopen(f, "rb")) == NULL)
        FAIL("can't open encoding file for reading");
    for (;;) {
        GET_LINE();
        if (p - buf <= 1 || *buf == '%')
            continue;
        if (p > buf && *buf == '/' && p[-2] == '[')
                break;
        FAIL("Encoding name not found:\n%s" AND buf);
    }
    q = pool_ptr;
    e = enc_glyphs;
    for (;;) {
        GET_LINE();
        p = buf;
        do {
            if (*p == '%')
                break;
            if (*p == ']')
                return;
            if (*p++ != '/')
                FAIL("Missing slash:\n%s" AND buf);
            if ((char *)strstr(p, ".notdef") == p) {
                *e++ = notdef;
                p += 7;
            }
            else {
                while (*p != ' ' && *p != '\n' && *p != ']') {
                    *q++ = *p++;
                    if (q - str_pool >= STRPOOL_SIZE)
                        FAIL("overflow STRPOOL_SIZE (%d); \
                        increase it and rebuilt" AND q - str_pool);
                }
                *q++ = 0;
                *e++ = pool_ptr;
                pool_ptr = q;
            }
            if (char_code++ > 255)
                FAIL("encoding file contains more than 256 characters");
            while (*p == ' ' || *p == '\n')
                p++;
        } while (*p != 0 && *p != ']');
        if (*p == ']')
            return;
    }
}

void read_font()
{
    long i, j, k, l, n;
    char buf[1024], *p, **pe;
    dirtab_entry *pd;
    mtx_entry *pm;
    SKIP(FIXED_SIZE);
    ntabs = GET_USHORT();
    SKIP(3*USHORT_SIZE);
    for (pd = dirtab; pd - dirtab < ntabs; pd++) {
        pd->tag[0] = GET_CHAR();
        pd->tag[1] = GET_CHAR();
        pd->tag[2] = GET_CHAR();
        pd->tag[3] = GET_CHAR();
        SKIP(ULONG_SIZE);
        pd->offset = GET_ULONG();
        pd->length = GET_ULONG();
    }
    seek_tab("head", 2*FIXED_SIZE + 2*ULONG_SIZE + USHORT_SIZE);
    upem = GET_USHORT();
    SKIP(16);
    FontBBox1 = GET_FWORD();
    FontBBox2 = GET_FWORD();
    FontBBox3 = GET_FWORD();
    FontBBox4 = GET_FWORD();
    SKIP(USHORT_SIZE);
    SKIP(USHORT_SIZE + SHORT_SIZE);
    loca_format = GET_SHORT();
    seek_tab("hhea", FIXED_SIZE);
    Ascender = GET_FWORD();
    Descender = GET_FWORD();
    SKIP(FWORD_SIZE + UFWORD_SIZE + 3*FWORD_SIZE + 8*SHORT_SIZE);
    if ((nhmtx = GET_USHORT()) > NCHARS)
        FAIL("overflow NCHARS (%d); increase it and rebuilt" AND nhmtx);
    seek_tab("hmtx", 0);
    for (pm = mtxtab; pm - mtxtab < nhmtx; pm++) {
        pm->WX = GET_UFWORD();
        SKIP(FWORD_SIZE);
    }
    i = pm[-1].WX;
    while (pm - mtxtab < nglyphs)
        (pm++)->WX = i;
    seek_tab("maxp", FIXED_SIZE);
    nglyphs = GET_USHORT();
    for (pm = mtxtab; pm - mtxtab < nglyphs; pm++) {
        pm->N.name = NULL;
        pm->used = 0;
    }
    seek_tab("post", 0);
    post_format = GET_FIXED();
    ItalicAngle = GET_FIXED();
    UnderlinePosition = GET_FWORD();
    UnderlineThickness = GET_FWORD();
    IsFixedPitch = GET_ULONG();
    SKIP(4*ULONG_SIZE);
    switch (post_format) {
    case 0x00010000:
        for (pm = mtxtab; pm - mtxtab < NMACGLYPHS; pm++)
            pm->N.name = mac_glyph[pm -mtxtab];
        while (pm - mtxtab < nglyphs)
            pm->N.name = mac_glyph[0]; /* ".notdef" */
        break;
    case 0x00018000:
        for (pm = mtxtab; pm - mtxtab < nglyphs; pm++)
            pm->N.name = mac_glyph[GET_CHAR()];
        break;
    case 0x00020000:
        SKIP(USHORT_SIZE);
        for (pm = mtxtab; pm - mtxtab < nglyphs; pm++)
            pm->N.index = GET_USHORT();
        if ((pd = name_lookup("post")) == NULL)
            FAIL("can't find table `post'");
        n = pd->length - (ftell(ttfont) - pd->offset);
        if (n + pool_ptr - str_pool > STRPOOL_SIZE)
            FAIL("overflow STRPOOL_SIZE (%d); increase it and rebuilt" AND 
            n + pool_ptr - str_pool);
        for (ps_glyphs = p = pool_ptr; p - pool_ptr < n;) {
            for (i = GET_BYTE(); i > 0; i--)
                *p++ = GET_CHAR();
            *p++ = 0;
        }
        pool_ptr = p;
        for (pm = mtxtab; pm - mtxtab < nglyphs; pm++) {
            if (pm->N.index < NMACGLYPHS)
                pm->N.name = mac_glyph[pm->N.index];
            else {
                for (k = pm->N.index - NMACGLYPHS, p = ps_glyphs;  
                    k > 0; 
                    k--, p = (char *)strchr(p, 0) + 1);
                pm->N.name = p;
            }
        }
        break;
    default:
        FAIL("unsupported format (%8X) of `post' table" AND post_format);
    }
    seek_tab("loca", 0);
    for (pm = mtxtab; pm - mtxtab < nglyphs; pm++)
        pm->glyph.offset = 
        (loca_format == 1 ? GET_ULONG() : GET_USHORT() << 1);
    if ((pd = name_lookup("glyf")) == NULL)
        FAIL("can't find table `glyf'");
    for (n = pd->offset, pm = mtxtab; pm - mtxtab < nglyphs; pm++)
        if (pm->glyph.offset != pm[1].glyph.offset) {
            seek_off("glyf", n + pm->glyph.offset);
            SKIP(SHORT_SIZE);
            pm->glyph.B[0] = GET_FWORD();
            pm->glyph.B[1] = GET_FWORD();
            pm->glyph.B[2] = GET_FWORD();
            pm->glyph.B[3] = GET_FWORD();
        }
        else 
            pm->glyph.B[0] = 
            pm->glyph.B[1] = 
            pm->glyph.B[2] = 
            pm->glyph.B[3] = 0;
    seek_tab("name", USHORT_SIZE);
    i = ftell(ttfont);
    n = GET_USHORT();
    j = GET_USHORT() + i - USHORT_SIZE;
    i += 2*USHORT_SIZE;
    while (n-- > 0) {
        seek_off("name", i + 3*USHORT_SIZE);
        k = GET_USHORT();
        l = GET_USHORT();
        if (k == 0 || k == 4 || k == 6) {
            seek_off("name", j + GET_USHORT());
            p = pool_ptr;
            while (l-- > 0)
                *p++ = GET_CHAR();
            *p++ = 0;
            switch (k) {
            case 0:  Notice = pool_ptr; break;
            case 4:  FullName = pool_ptr; break;
            case 6:  FontName = pool_ptr; break;
            }
            pool_ptr = p;
            if (Notice != NULL && FullName != NULL && FontName != NULL)
                break;
        }
        i += 6*USHORT_SIZE;
    }
    if ((pd = name_lookup("PCLT")) != NULL) {
        seek_off("PCLT", pd->offset + FIXED_SIZE + ULONG_SIZE + USHORT_SIZE);
        XHeight = GET_USHORT();
        SKIP(2*USHORT_SIZE);
        CapHeight = GET_USHORT();
    }
    if ((pd = name_lookup("kern")) == NULL)
        return;
    seek_off("kern", pd->offset + USHORT_SIZE);
    for (n = GET_USHORT(); n > 0; n--) {
        SKIP(2*USHORT_SIZE);
        k = GET_USHORT();
        if (!(k & 1) || (k & 2) || (k & 4))
            return;
        if (k >> 8 != 0) {
            fprintf(stderr, "warning: only format 0 supported of `kern' \
            subtables, others are ignored\n");
            return;
        }
        k = GET_USHORT();
        SKIP(3*USHORT_SIZE);
        while (k-- > 0) {
            i = GET_USHORT();
            j = GET_USHORT();
            if (i >= NCHARS || j >= NCHARS)
                FAIL("out of range in kerning data (L=%li, R=%li)" AND i AND j);
            kern_data[i][j] = GET_FWORD();
            if (kern_data[i][j] != 0)
                nkernpairs++;
        }
    }
}

void print_afm()
{
    int i, j;
    mtx_entry *pm;
    short *idx;
    char **pe;
    printf("\nStartFontMetrics 2.0\n");
    PRINT_STR(FontName);
    PRINT_STR(FullName);
    PRINT_STR(Notice);
    printf("ItalicAngle %i", ItalicAngle/0x10000);
    if (ItalicAngle%0x10000 > 0)
        printf(".%i", ((ItalicAngle%0x10000)*1000)/0x10000);
    printf("\n");
    printf("IsFixedPitch %s\n", IsFixedPitch ? "true" : "false");
    printf("FontBBox %i %i %i %i\n", FUNIT(FontBBox1), FUNIT(FontBBox2),
    FUNIT(FontBBox3), FUNIT(FontBBox4));
    PRINT_NUM(UnderlinePosition);
    PRINT_NUM(UnderlineThickness);
    PRINT_NUM(CapHeight);
    PRINT_NUM(XHeight);
    PRINT_NUM(Ascender);
    PRINT_NUM(Descender);
    if (char_code == 0) {
        printf("\nStartCharMetrics %u\n", nglyphs);
        for (pm = mtxtab; pm - mtxtab < nglyphs; pm++)
            if (pm->N.name != NULL)
                printf("C %d ; WX %i ; N %s ; B %i %i %i %i ;\n", 
                i < 256 ? i : -1,
                FUNIT(pm->WX), 
                pm->N.name,
                FUNIT(pm->glyph.B[0]),
                FUNIT(pm->glyph.B[1]),
                FUNIT(pm->glyph.B[2]),
                FUNIT(pm->glyph.B[3]));
    }
    else {
        for (i = 0, pe = enc_glyphs; pe - enc_glyphs < char_code; pe++) {
            if (*pe == notdef)
                continue;
            for (pm = mtxtab; pm - mtxtab < nglyphs; pm++)
                if (!strcmp(*pe, pm->N.name))
                    break;
            if (pm - mtxtab < nglyphs) {
                i++;
                pm->used = 1;
                enc_index[pe - enc_glyphs] = pm - mtxtab;
            }
            else
                WARN("glyph `%s' not found" AND *pe);
        }
        printf("\nStartCharMetrics %u\n", i);
        for (idx = enc_index, pe = enc_glyphs; pe - enc_glyphs < char_code; 
        idx++, pe++)
            if (*idx > 0 && (mtxtab + *idx)->used > 0)
                printf("C %d ; WX %i ; N %s ; B %i %i %i %i ;\n", 
                pe - enc_glyphs, 
                FUNIT((mtxtab + *idx)->WX), 
                (mtxtab + *idx)->N.name,
                FUNIT((mtxtab + *idx)->glyph.B[0]),
                FUNIT((mtxtab + *idx)->glyph.B[1]),
                FUNIT((mtxtab + *idx)->glyph.B[2]),
                FUNIT((mtxtab + *idx)->glyph.B[3]));
    }
    printf("EndCharMetrics\n");
    if (char_code) {
        nkernpairs = 0;
        for (i = 0; i < nglyphs; i++)
            for (j = 0; j < nglyphs; j++)
                if (mtxtab[i].used && mtxtab[j].used && kern_data[i][j] != 0)
                    nkernpairs++;
    }
    if (nkernpairs > 0) {
        printf("\nStartKernData\nStartKernPairs %i\n", nkernpairs);
        for (i = 0; i < nglyphs; i++)
            for (j = 0; j < nglyphs; j++)
                if ((!char_code || (mtxtab[i].used && mtxtab[j].used))
                && kern_data[i][j] != 0)
                    printf("KPX %s %s %i\n", 
                    mtxtab[i].N.name, mtxtab[j].N.name,
                    FUNIT(kern_data[i][j]));
        printf("EndKernPairs\nEndKernData\n");
    }
    printf("EndFontMetrics\n");
}

main(int argc, char **argv)
{
    char buf[128];
    time_t t = time(&t);
    if (argc < 2)
        FAIL("Font file name missing\nUsage: ttf2afm fontname [encodingfile]");
    filename = argv[1];
    if ((ttfont = fopen(argv[1], "rb")) == NULL)
        FAIL("can't open font file for reading");
    if (argc > 2) {
        filename = argv[2];
        read_encoding(argv[2]);
        filename = argv[1];
    }
    read_font();
    sprintf(buf, "%s", ctime(&t));
    *(char *)strchr(buf, '\n') = 0;
    printf("Comment Converted at %s by ttf2afm from font file `%s'", buf, argv[1]);
    print_afm();
    close(ttfont);
    return 0;
}

