/* Copyright 2014 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
   This software is licensed as OpenSource, under the Apache License, Version 2.0.
   This license is available at: http://opensource.org/licenses/Apache-2.0. */

/*
 * PostScript font file parser. 
 *
 * Handles single master, multiple master, synthetic, and CID fonts.
 *
 * Print stream formats for name-keyed and cid-keyed fonts are also supported.
 * These are generated by xcf and/or CoolType when downloading a font to a
 * printer. Generally, these will need to be extracted from the print stream
 * before submitting them to this library. It may also be necessary to fix up
 * some constructs that don't follow the guidelines specified in the "Adobe
 * Type Manager Compatibility" chapter in the Black Book.
 */

#include "t1read.h"
#include "t1cstr.h"
#include "dynarr.h"
#include "pstoken.h"
#include "ctutil.h"
#include "txops.h"
#include "supportexcept.h"

#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>

#define RND(v) ((float)floor((v) + 0.5))

/* ----------------------- PostScript Dictionary Keys ---------------------- */

/* Note: t1read_keys.h must be ascii sorted by key name */

enum /* Key enumeration */
{
#define DCL_KEY(key, index) index,
#include "t1read_keys.h"
#undef DCL_KEY
    kKeyCount
};

static const char *keys[] =
    {
#define DCL_KEY(key, index) key,
#include "t1read_keys.h"
#undef DCL_KEY
};

#define MAX_AXES 4 /* Pratical multiple master axis limit */

typedef unsigned short STI; /* String index */
#define STI_UNDEF 0xffff    /* Undefined string index */
#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))

typedef struct /* Map glyph name to its standard encoding */
{
    unsigned char code;
    char *gname;
} StdMap;

typedef abfGlyphInfo Char;  /* Character record */
typedef unsigned short CHI; /* Char record index */
typedef long Offset;

typedef struct /* FDArray element */
{
    abfFontDict *fdict; /* Font dict */
    struct              /* Subrs */
    {
        ctlRegion region; /* cstr data region */
        dnaDCL(long, offset);
    } subrs;
    t1cAuxData aux; /* Auxiliary charstring data */
    struct          /* Dict key info */
    {
        long lenIV;                /* Length random cipher bytes */
        long SubrMapOffset;        /* CID-specific key */
        unsigned short SubrCount;  /* CID-specific key */
        unsigned short SDBytes;    /* CID-specific key */
        unsigned short BlueValues; /* Flags /BlueValues seen */
    } key;
    t1cDecryptFunc decrypt; /* Charstring decryption function */
} FDInfo;

typedef struct /* Length delimited string for search match */
{
    long length;
    char *value;
} MatchStr;

/* Module context */
struct t1rCtx_ {
    long flags;                    /* Control flags */
#define MM_FONT (1UL << 31)        /* MM font */
#define CID_FONT (1UL << 30)       /* CID-keyed font */
#define SYN_FONT (1UL << 29)       /* Synthetic font */
#define SEEN_END (1UL << 28)       /* Seen CharStrings dict end operator */
#define STD_ENC (1UL << 27)        /* Standard encoded font */
#define FREE_ENCODINGS (1UL << 26) /* Return encoding nodes to free list */
#define PRINT_STREAM (1UL << 25)   /* CoolType print steam font */
#define CRLF_NEWLINES (1UL << 24)  /* Font uses CR/LF newlines */
#define SEEN_NOTDEF (1UL << 23)    /* Seen .notdef glyph */
    abfTopDict top;                /* Top dict */
    FDInfo *fd;                    /* Active FDArray element */
    dnaDCL(FDInfo, FDArray);       /* FDArray */
    dnaDCL(abfFontDict, fdicts);   /* Font Dicts */
    struct                         /* Encoding */
    {
        STI custom[256];              /* Custom encoding */
        unsigned short standard[256]; /* Map standard encoding to tag */
    } encoding;
    struct /* Chars */
    {
        dnaDCL(Char, index);  /* In parse order */
        dnaDCL(long, byName); /* In glyph name order */
    } chars;
    struct /* String pool */
    {
        dnaDCL(long, index); /* In index order; [SRI]->iBuf */
        dnaDCL(char, buf);   /* String buffer */
    } strings;
    struct /* Dict key info */
    {
        char seen[kKeyCount]; /* Flags keys seen */
        long CIDMapOffset;    /* CID-specific key */
        unsigned FDBytes;     /* CID-specific key */
        unsigned GDBytes;     /* CID-specific key */
    } key;
    dnaDCL(char, tmp); /* Temporary char buffer */
    pstToken token;    /* Last PostScript token read from stream */
    struct             /* Synthetic font values */
    {
        abfString FontName;
        abfString FullName;
        float ItalicAngle;
        long UniqueID;
        abfFontMatrix FontMatrix;
    } synthetic;
    struct /* MM font values */
    {
        struct
        {
            long cnt;
            float array[T1_MAX_MASTERS];
        } WV;
        struct
        {
            long cnt;
            float array[T1_MAX_MASTERS * T1_MAX_AXES];
        } BDP;
        struct
        {
            long cnt;
            float array[T1_MAX_AXES * T1_MAX_AXIS_MAPS];
        } BDM;
        float *UDV; /* From client */
        float ForceBoldThreshold;
    } mm;
    pstCtx pst; /* pstoken lib context */
    dnaCtx dna; /* dynarr lib context */
    struct      /* Client callbacks */
    {
        ctlMemoryCallbacks mem;
        ctlStreamCallbacks stm;
    } cb;
    struct /* Streams */
    {
        void *tmp; /* Temporary */
        void *dbg; /* Debug */
        void *src; /* Source (CID-keyed only) */
    } stm;
    long tmpoff; /* Temporary stream offset */
    struct       /* Source stream data (CID-keyed only) */
    {
        long offset;   /* Buffer offset */
        char *buf;     /* Buffer data */
        size_t length; /* Buffer length */
        char *end;     /* Buffer end */
        char *next;    /* Next byte available */
    } src;
    abfEncoding *encfree; /* Encoding node free list */
    struct                /* Error handling */
    {
        _Exc_Buf env;
    } err;
};

static void parseGlyphDirectory(t1rCtx h);

/* ----------------------------- Error Handling ---------------------------- */

/* Write message to debug stream from va_list. */
static void vmessage(t1rCtx h, char *fmt, va_list ap) {
    char text[500];

    if (h->stm.dbg == NULL)
        return; /* Debug stream not available */

    vsprintf(text, fmt, ap);
    (void)h->cb.stm.write(&h->cb.stm, h->stm.dbg, strlen(text), text);
}

/* Write message to debug stream from varargs. */
static void CTL_CDECL message(t1rCtx h, char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vmessage(h, fmt, ap);
    va_end(ap);
}

/* Handle fatal error. */
static void CTL_CDECL fatal(t1rCtx h, int err_code, char *fmt, ...) {
    if (fmt == NULL)
        /* Write standard error message */
        message(h, "%s", t1rErrStr(err_code));
    else {
        /* Write font-specific error message */
        va_list ap;
        va_start(ap, fmt);
        vmessage(h, fmt, ap);
        va_end(ap);
    }
    RAISE(&h->err.env, err_code, NULL);
}

/* --------------------------- Memory Management --------------------------- */

/* Allocate memory. */
static void *memNew(t1rCtx h, size_t size) {
    void *ptr = h->cb.mem.manage(&h->cb.mem, NULL, size);
    if (ptr == NULL)
        fatal(h, t1rErrNoMemory, NULL);
    return ptr;
}

/* Free memory. */
static void memFree(t1rCtx h, void *ptr) {
    (void)h->cb.mem.manage(&h->cb.mem, ptr, 0);
}

/* -------------------------- Safe dynarr Callbacks ------------------------ */

/* Manage memory. */
static void *dna_manage(ctlMemoryCallbacks *cb, void *old, size_t size) {
    t1rCtx h = cb->ctx;
    void *ptr = h->cb.mem.manage(&h->cb.mem, old, size);
    if (size > 0 && ptr == NULL)
        fatal(h, t1rErrNoMemory, NULL);
    return ptr;
}

/* Initialize error handling dynarr context. */
static void dna_init(t1rCtx h) {
    ctlMemoryCallbacks cb;
    cb.ctx = h;
    cb.manage = dna_manage;
    h->dna = dnaNew(&cb, DNA_CHECK_ARGS);
}

/* ----------------------- Temporary File Management ----------------------- */

/* Reset tmp stream to beginning. */
static void tmpSeek(t1rCtx h, long offset) {
    if (h->cb.stm.seek(&h->cb.stm, h->stm.tmp, offset))
        fatal(h, t1rErrTmpStream, NULL);
    h->tmpoff = offset;
}

/* Write buffer to tmp steam and update offset. */
static void tmpWrite(t1rCtx h, long length, char *buf) {
    if (h->cb.stm.write(&h->cb.stm, h->stm.tmp, length, buf) != (size_t)length)
        fatal(h, t1rErrTmpStream, NULL);
    h->tmpoff += length;
}

/* Initialize tmp stream with invalid cstr that is used to initialize bad or
   missing subrs. Charstring parse fails if subr is referenced. The bad
   charstring is located at offset 1 so that offset 0 can never become a
   charstring offset. This restriction maintains compatibility with the buildch
   library where a 0 charstring offset means uninitialized. */
static void tmpInit(t1rCtx h) {
    char badcstr[] = {0}; /* Invalid charstring containing invalid op */
    tmpSeek(h, 1);
    tmpWrite(h, sizeof(badcstr), badcstr);
}

/* --------------------------- String Management --------------------------- */

/* Initialize strings. */
static void newStrings(t1rCtx h) {
    dnaINIT(h->dna, h->strings.index, 50, 200);
    dnaINIT(h->dna, h->strings.buf, 1500, 6000);
}

/* Reinitilize strings for new font. */
static void initStrings(t1rCtx h) {
    h->strings.index.cnt = 0;
    h->strings.buf.cnt = 0;
}

/* Free strings. */
static void freeStrings(t1rCtx h) {
    dnaFREE(h->strings.index);
    dnaFREE(h->strings.buf);
}

/* Add string. */
/* 64-bit warning fixed by type change here HO */
/* static STI addString(t1rCtx h, unsigned length, const char *value) */
static STI addString(t1rCtx h, size_t length, const char *value) {
    STI sti = (STI)h->strings.index.cnt;

    if (length == 0) {
        /* A null name (/) is legal in PostScript but could lead to unexpected
           behaviour elsewhere in the coretype libraries so it is substituted
           for a name that is very likely to be unique in the font */
        const char subs_name[] = "_null_name_substitute_";
        value = subs_name;
        length = sizeof(subs_name) - 1;
        message(h, "null charstring name");
    }

    /* Add new string index */
    *dnaNEXT(h->strings.index) = h->strings.buf.cnt;

    /* Add null-terminated string to buffer */
    /* 64-bit warning fixed by cast here HO */
    memcpy(dnaEXTEND(h->strings.buf, (long)(length + 1)), value, length);
    h->strings.buf.array[h->strings.buf.cnt - 1] = '\0';

    return sti;
}

/* Get string from STI. */
static char *getString(t1rCtx h, STI sti) {
    return &h->strings.buf.array[h->strings.index.array[sti]];
}

/* ---------------------------- Chars Management --------------------------- */

/* Initialize chars. */
static void newChars(t1rCtx h) {
    dnaINIT(h->dna, h->chars.index, 256, 1000);
    dnaINIT(h->dna, h->chars.byName, 256, 1000);
}

/* Reinitilize chars for new font. */
static void initChars(t1rCtx h) {
    h->chars.index.cnt = 0;
    h->chars.byName.cnt = 0;
}

/* Free chars. */
static void freeChars(t1rCtx h) {
    dnaFREE(h->chars.index);
    dnaFREE(h->chars.byName);
}

/* Match glyph name. */
static int CTL_CDECL matchChar(const void *key, const void *value, void *ctx) {
    t1rCtx h = ctx;
    return strcmp((char *)key, getString(h, (STI)h->chars.index.array
                                                [*(long *)value]
                                                    .gname.impl));
}

/* Add char record. Return 1 if record exists else 0. Char record returned by
   "chr" parameter. */
static int addChar(t1rCtx h, STI sti, Char **chr) {
    size_t index;
    int found =
        ctuLookup(getString(h, sti),
                  h->chars.byName.array, h->chars.byName.cnt,
                  sizeof(h->chars.byName.array[0]), matchChar, &index, h);

    if (found)
        /* Match found; return existing record */
        *chr = &h->chars.index.array[h->chars.byName.array[index]];
    else {
        /* Not found; add to table and return new record */
        long *new = &dnaGROW(h->chars.byName, h->chars.byName.cnt)[index];

        /* Make and fill hole */
        memmove(new + 1, new, (h->chars.byName.cnt++ - index) * sizeof(h->chars.byName.array[0]));
        *new = h->chars.index.cnt;

        *chr = dnaNEXT(h->chars.index);
    }

    return found;
}

/* Find char by name. NULL if not found else char record. */
static Char *findChar(t1rCtx h, STI sti) {
    size_t index;
    if (ctuLookup(getString(h, sti),
                  h->chars.byName.array, h->chars.byName.cnt,
                  sizeof(h->chars.byName.array[0]), matchChar, &index, h))
        return &h->chars.index.array[h->chars.byName.array[index]];
    else
        return NULL;
}

/* ---------------------------- Encoding Support --------------------------- */

/* Add encoding to char. */
static void encAdd(t1rCtx h, Char *chr, unsigned long code) {
    abfEncoding *enc = &chr->encoding;
    if (strcmp(chr->gname.ptr, ".notdef") == 0)
        return; /* Don't encode .notdef */
    else if (enc->code == ABF_GLYPH_UNENC) {
        /* First encoding; initialize */
        enc->code = code;
        enc->next = NULL;
    } else {
        /* Second or subsequent encoding; link new encoding */
        abfEncoding *new = h->encfree;
        if (new == NULL)
            /* Get node from heap */
            new = memNew(h, sizeof(abfEncoding));
        else
            /* Remove head node from free list */
            h->encfree = new->next;

        new->code = code;
        new->next = enc->next;
        enc->next = new;
        h->flags |= FREE_ENCODINGS;
    }
}

/* Put encoding node list on free list. */
static void encReuse(t1rCtx h, abfEncoding *node) {
    if (node == NULL)
        return;
    encReuse(h, node->next);
    node->next = h->encfree;
    h->encfree = node;
}

/* Reuse encoding node list. */
static void encListReuse(t1rCtx h) {
    long i;

    if (!(h->flags & FREE_ENCODINGS))
        return; /* Font has no multiple encodings */

    /* Put multiple encodings back on free list */
    for (i = 0; i < h->chars.index.cnt; i++) {
        abfEncoding *enc = &h->chars.index.array[i].encoding;
        if (enc->code != ABF_GLYPH_UNENC)
            encReuse(h, enc->next);
    }
    h->flags &= ~FREE_ENCODINGS;
}

/* Free encoding node list. */
static void encListFree(t1rCtx h, abfEncoding *node) {
    if (node == NULL)
        return;
    encListFree(h, node->next);
    memFree(h, node);
}

/* --------------------------- Context Management -------------------------- */

/* Initialize FDArray element when allocated. */
static void allocFDInfo(void *ctx, long cnt, FDInfo *fd) {
    t1rCtx h = ctx;
    while (cnt--) {
        dnaINIT(h->dna, fd->subrs.offset, 300, 300);
        fd++;
    }
}

/* Validate client and create context. */
t1rCtx t1rNew(ctlMemoryCallbacks *mem_cb, ctlStreamCallbacks *stm_cb,
              CTL_CHECK_ARGS_DCL) {
    t1rCtx h;

    /* Check client/library compatibility */
    if (CTL_CHECK_ARGS_TEST(T1R_VERSION))
        return NULL;

    /* Allocate context */
    h = mem_cb->manage(mem_cb, NULL, sizeof(struct t1rCtx_));
    if (h == NULL)
        return NULL;

    /* Safety initialization */
    h->FDArray.size = 0;
    h->fdicts.size = 0;
    h->chars.index.size = 0;
    h->chars.byName.size = 0;
    h->strings.index.size = 0;
    h->strings.buf.size = 0;
    h->tmp.size = 0;
    h->pst = NULL;
    h->dna = NULL;
    h->stm.tmp = NULL;
    h->stm.dbg = NULL;
    h->stm.src = NULL;
    h->encfree = NULL;

    /* Copy callbacks */
    h->cb.mem = *mem_cb;
    h->cb.stm = *stm_cb;

    /* Set error handler */
    DURING_EX(h->err.env)

    /* Initialize service library */
    dna_init(h);

    dnaINIT(h->dna, h->FDArray, 1, 20);
    h->FDArray.func = allocFDInfo;
    dnaINIT(h->dna, h->fdicts, 1, 20);
    newChars(h);
    newStrings(h);
    dnaINIT(h->dna, h->tmp, 250, 750);

    /* Initialize pstoken library */
    h->pst = pstNew(mem_cb, stm_cb, T1R_SRC_STREAM_ID, PST_CHECK_ARGS);
    if (h->pst == NULL)
        RAISE(&h->err.env, t1rErrSrcStream, NULL);

    /* Open tmp stream */
    h->stm.tmp = h->cb.stm.open(&h->cb.stm, T1R_TMP_STREAM_ID, 0);
    if (h->stm.tmp == NULL)
        RAISE(&h->err.env, t1rErrTmpStream, NULL);

    /* Open debug stream */
    h->stm.dbg = h->cb.stm.open(&h->cb.stm, T1R_DBG_STREAM_ID, 0);

    HANDLER

    /* Initialization failed */
    t1rFree(h);
    h = NULL;

    END_HANDLER

    return h;
}

/* Free context. */
void t1rFree(t1rCtx h) {
    int i;

    if (h == NULL)
        return;

    for (i = 0; i < h->FDArray.size; i++)
        dnaFREE(h->FDArray.array[i].subrs.offset);

    dnaFREE(h->FDArray);
    dnaFREE(h->fdicts);
    freeChars(h);
    freeStrings(h);
    dnaFREE(h->tmp);

    dnaFree(h->dna);
    pstFree(h->pst);

    /* Close tmp stream */
    if (h->stm.tmp != NULL)
        (void)h->cb.stm.close(&h->cb.stm, h->stm.tmp);

    /* Close debug stream */
    if (h->stm.dbg != NULL)
        (void)h->cb.stm.close(&h->cb.stm, h->stm.dbg);

    encListFree(h, h->encfree);

    /* Free library context */
    memFree(h, h);
}

/* --------------------------- Dictionary Parsing -------------------------- */

/* Handle fatal parse error. */
static void pstFatal(t1rCtx h, int err_code) {
    message(h, "(pst) %s", pstErrStr(err_code));
    fatal(h, t1rErrPostScript, NULL);
}

/* Get next PostScript token and store in context. Return token pointer. */
static pstToken *getToken(t1rCtx h) {
    int result = pstGetToken(h->pst, &h->token);
    if (result)
        pstFatal(h, result);
    return &h->token;
}

/* Find PostScript token. */
static void findToken(t1rCtx h, char *value) {
    int result = pstFindToken(h->pst, &h->token, value);
    if (result)
        pstFatal(h, result);
}

/* Read encoding array. Just save the reference to encoded glyph names */
static void readEncoding(t1rCtx h) {
    int i;
    pstToken *token = getToken(h);

    if (pstMatch(h->pst, token, "StandardEncoding")) {
        /* Standard encoded font */
        h->flags |= STD_ENC;
        return;
    }

    /* Initialize encoding array */
    for (i = 0; i < 256; i++)
        h->encoding.custom[i] = STI_UNDEF;

    /* Search for first entry */
    for (;;) {
        if (pstMatch(h->pst, token, "dup")) {
            int code;
            STI gname;

            /* Parse encoding value */
            token = getToken(h);
            if (token->type != pstInteger) {
                message(h, "Invalid token in encoding vector (integer expected)");
                continue;
            }

            code = pstConvInteger(h->pst, token);
            if (code < 0 || code > 255) {
                message(h, "Invalid codepoint in encoding vector %d", code);
                continue;
            }

            /* Parse glyph name */
            token = getToken(h);
            if (token->type != pstLiteral) {
                message(h, "Invalid token in encoding vector (literal expected)");
                continue;
            }

            gname = addString(h, token->length - 1, token->value + 1);

            /* Check for put operator */
            token = getToken(h);
            if (!pstMatch(h->pst, token, "put")) {
                message(h, "put operator expected in encoding vector");
                continue;
            }

            /* Save encoding */
            h->encoding.custom[code] = gname;

        } else if (pstMatch(h->pst, token, "def") ||
                   pstMatch(h->pst, token, "readonly"))
            return; /* Success */

        token = getToken(h);
    }

    fatal(h, t1rErrEncoding, NULL);
}

/* Compute hex string length taking newlines into account. */
static long calcHexLen(t1rCtx h, long len) {
    long nl_cnt;
    long nl_len = (h->flags & CRLF_NEWLINES) ? 2 : 1;

    /* Compute number of newline chars in string */
    if (h->fd->key.lenIV == -1)
        nl_cnt = (len - 1) / 32;
    else
        nl_cnt = (len - h->fd->key.lenIV - 1) / 32;

    return len * 2 + nl_cnt * nl_len;
}

/* Convert incremental download ASCIIHex charstring to binary. Returns number
   of converted binary bytes or 0 on error. */
static long convASCIIHexCstr(t1rCtx h, long length, char *value) {
    long i;
    char *p = value;
    char *end = value + length;
    for (i = 0;; i++) {
        int hi = -1; /* Hi-order nibble */
        for (;;) {
            int nib;
            int c;
        retry:
            if (p == end) {
                if (hi != -1)
                    value[i++] = hi; /* Partial byte, low nibble 0 */
                return i;
            }
            c = *p++;
            switch (c) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    nib = c - '0';
                    break;
                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                    nib = c - 'A' + 10;
                    break;
                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                    nib = c - 'a' + 10;
                    break;
                case '\0':
                case ' ':
                case '\t':
                case '\r':
                case '\n':
                    goto retry; /* Skip white space */
                default:
                    return 1;
            }
            if (hi == -1)
                hi = nib << 4;
            else {
                value[i] = hi | nib;
                break;
            }
        }
    }
}

/* Convert incremental download ASCII85 charstring to binary. Normally 5 bytes
   of ASCII-85 are decoded into 4 binary bytes. Thus, the result can be written
   directly to the charstring. However, the character 'z' is special and
   EXPANDS to 4 zero bytes which could overflow the charstring. Therefore, I
   consider character 'z' to be invalid because the only thing in a Type 1
   charstring it could represent would be a 5-byte representation of zero which
   seems very unlikely. Also, the t1write library never generates uses 'z' in
   an ASCII-85 charstring. 

   Returns number of converted binary bytes or 0 on error. */
static long convASCII85Cstr(t1rCtx h, char *value) {
    int i;
    char *p = value;
    char *q = value;
    int n = 0;
    unsigned long tuple = 0;

    for (p = value;; p++)
        switch (*p) {
            case '~':
                /* End of string */
                goto finish;
            case '\0':
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                break; /* Skip whitespace */
            case 'z':
                return 0; /* Impossible charstring */
            default:
                tuple = tuple * 85 + *p - '!';
                if (++n == 5) {
                    /* Write 4-tuple */
                    for (i = 3; i >= 0; i--) {
                        q[i] = (char)tuple;
                        tuple >>= 8;
                    }
                    q += 4;
                    n = 0;
                }
                break;
        }

finish:
    if (n == 1)
        return 0; /* Invalid ASCII85 */
    else if (n > 1) {
        /* Partial 5-tuple; pad missing zeros */
        for (i = n; i < 5; i++)
            tuple = tuple * 85 + 84;

        /* Discard unused bytes */
        for (i = n; i < 5; i++)
            tuple >>= 8;

        /* Write partial 4-tuple */
        for (i = n - 2; i >= 0; i--) {
            q[i] = (char)tuple;
            tuple >>= 8;
        }
        q += n - 1;
    }

    /* 64-bit warning fixed by cast here HO */
    return (long)(q - value);
}

/* Save charstring to tmp file and return tmp file offset. */
static long saveSubr(t1rCtx h, long length, char *value, int iFD, long num) {
    long offset = h->tmpoff;
    FDInfo *fd = &h->FDArray.array[iFD];

    if (fd->key.lenIV != -1 &&
        t1cDecrypt(fd->key.lenIV, &length, value, value)) {
        if (iFD != 0)
            fatal(h, t1rErrCstrDecrypt,
                  "can't decrypt FD[%d].subr[%ld]", iFD, num);
        else
            fatal(h, t1rErrCstrDecrypt, "can't decrypt subr[%ld]", num);
    }

    /* Validate charstring */
    switch ((length >= 1) ? value[length - 1] : 0) {
        case tx_endchar:
        case tx_callsubr:
        case tx_return:
            break;
        case 6:
            /* Second byte of possible seac operator */
            if (length >= 2 && value[length - 2] == tx_escape)
                break;
        default:
            if (iFD != 0)
                message(h,
                        "unterminated charstring FD[%d].subr[%ld] (invalidating)",
                        iFD, num);
            else
                message(h,
                        "unterminated charstring subr[%ld] (invalidating)", num);

            /* The subr is invalid but if it's never referenced by a charstring
               we can still successfully parse the font. So, we set the subr to
               reference the invalid charstring we inserted at the beginning of
               the tmp file. If this is ever interpreted later the charstring
               parser will fail with "invalid operator". */
            return 1;
    }

    /* Save charstring to tmp file */
    tmpWrite(h, length, value);

    return offset;
}

/* Read charstring token sequence. Return 0 on error else charstring length. */
static long readCstrTokenSeq(t1rCtx h, char **cstr) {
    int result;
    long length;
    pstToken *token;

    /* Read initial token */
    token = getToken(h);
    switch (token->type) {
        case pstHexString:
            /* ASCIIHex string; one of the print stream formats */
            *cstr = token->value;
            return convASCIIHexCstr(h, token->length - 2, *cstr + 1);
        case pstASCII85:
            /* ASCII85 string; one of the print stream formats */
            *cstr = token->value + 1;
            return convASCII85Cstr(h, *cstr + 1);
        case pstInteger:
            break;
        default:
            return 0; /* Unexpected token type */
    }

    /* Convert charstring length */
    length = pstConvInteger(h->pst, token);
    if (length < 1 || length > 65535)
        return 0;

    /* Parse RD operator */
    token = getToken(h);
    if (token->type != pstOperator)
        return 0;

    if (h->flags & PRINT_STREAM) {
        long hexlen = calcHexLen(h, length);
        result = pstRead(h->pst, hexlen + 1, cstr);
        if (result)
            pstFatal(h, result);
        if (convASCIIHexCstr(h, hexlen, *cstr + 1) != length)
            return 0;
    } else {
        /* Read separator and charstring bytes */
        result = pstRead(h->pst, length + 1, cstr);
        if (result)
            pstFatal(h, result);
    }

    return length;
}

/* Read /Subrs array. */
static void readSubrs(t1rCtx h) {
    int i;
    pstToken *token;
    long num = 0; /* Suppress optimizer warning */
    long cnt = 0; /* Suppress optimizer warning */

    /* Parse subrs count */
    token = getToken(h);
    if (token->type != pstInteger ||
        (cnt = pstConvInteger(h->pst, token), cnt < 0 || cnt > 65535))
        fatal(h, t1rErrSubrsCount, NULL);

    /* Allocate and initialize elements */
    dnaSET_CNT(h->fd->subrs.offset, cnt);
    for (i = 0; i < cnt; i++)
        /* Set tmp file offset of invalid charstring */
        h->fd->subrs.offset.array[i] = 0;

    /* Save start of region */
    h->fd->subrs.region.begin = h->tmpoff;

    findToken(h, "array");
    for (i = 0;; i++) {
        long length;
        char *cstr;

        /* Read dup token */
        token = getToken(h);
        if (!pstMatch(h->pst, token, "dup")) {
            /* Save end of region */
            h->fd->subrs.region.end = h->tmpoff;

            if (i > cnt)
                message(h, "duplicate subrs");
            else if (i < cnt)
                message(h, "sparse /Subr array (invalidating unset entries)");

            return;
        }

        /* Parse subr number */
        token = getToken(h);
        if (token->type != pstInteger ||
            (num = pstConvInteger(h->pst, token),
             num < 0 || num >= h->fd->subrs.offset.cnt))
            break;

        /* Read cstr bytes */
        length = readCstrTokenSeq(h, &cstr);
        if (length == 0)
            break;

        /* Save charstring data omitting separator byte */
        h->fd->subrs.offset.array[num] = saveSubr(h, length, cstr + 1, 0, num);

        /* Parse NP operator */
        token = getToken(h);
        if (token->type != pstOperator)
            break;

        /* Check for "noaccess put" sequence */
        if (pstMatch(h->pst, token, "noaccess")) {
            token = getToken(h);
            if (token->type != pstOperator)
                break;
        }
    }

    fatal(h, t1rErrSubrEntry, "invalid subr entry [%ld]", num);
}

/* Match std name. */
static int CTL_CDECL matchStd(const void *key, const void *value) {
    return strcmp((char *)key, ((StdMap *)value)->gname);
}

/* Get standard encoding from glyph name. Return -1 if no standard encoding
   else code. */
static int getStdEnc(t1rCtx h, STI gname) {
    static const StdMap stdenc[] =
        {
#include "stdenc3.h"
        };
    StdMap *map = (StdMap *)
        bsearch(getString(h, gname), stdenc, ARRAY_LEN(stdenc),
                sizeof(stdenc[0]), matchStd);
    return (map == NULL) ? -1 : map->code;
}

/* Save glyph charstring to tmp file. */
static void saveCstr(t1rCtx h, long length, char *value, Char *chr, int update) {
    FDInfo *fd = &h->FDArray.array[chr->iFD];

    if (fd->key.lenIV != -1 &&
        fd->decrypt(fd->key.lenIV, &length, value, value)) {
        if (h->flags & CID_FONT)
            fatal(h, t1rErrCstrDecrypt, "can't decrypt CID-%d", chr->cid);
        else
            fatal(h, t1rErrCstrDecrypt, "can't decrypt <%s>",
                  getString(h, (STI)chr->gname.impl));
    }

    /* Validate charstring */
    switch ((length >= 1) ? value[length - 1] : 0) {
        case tx_endchar:
        case tx_callsubr:
            break;
        case 6:
            /* Second byte of possible seac operator */
            if (length >= 2 && value[length - 2] == tx_escape)
                break;
        default:
            if (h->flags & CID_FONT)
                fatal(h, t1rErrUntermCstr, "unterminated charstring CID-%d",
                      chr->cid);
            else
                fatal(h, t1rErrUntermCstr, "unterminated charstring <%s>",
                      getString(h, (STI)chr->gname.impl));
    }

    /* Save charstring to tmp file */
    if (update) {
        chr->sup.begin = h->tmpoff;
        chr->sup.end = chr->sup.begin + length;
    }
    tmpWrite(h, length, value);
}

/* Add missing .notdef character to font. The inserted charstring has a width
   of 250 units and no path. It could be argued that the charstring should be a
   copy of the space character's charstring or that the width be scaled
   appropriately for fonts with a non-standard design space but this makes the
   code unnecessarily complicated for handling what is, after all, a broken
   font. */
static void addNotdef(t1rCtx h, unsigned short tag) {
    static const unsigned char cstr[] =
        {0x8b, 0xf7, 0x8e, 0xd, 0xe}; /* 0 250 hsbw endchar */
    Char *chr;
    STI gname;
    long save;
    FDInfo *fd = &h->FDArray.array[0];

    /* Add new char */
    gname = addString(h, 7, ".notdef");
    (void)addChar(h, gname, &chr);

    /* Initialize char */
    abfInitGlyphInfo(chr);
    chr->tag = tag;
    chr->gname.impl = gname;

    /* Save plaintext charstring data  */
    save = fd->key.lenIV;
    fd->key.lenIV = -1;
    saveCstr(h, sizeof(cstr), (char *)cstr, chr, 1);
    fd->key.lenIV = save;

    message(h, "missing .notdef glyph (inserted)");
}

/* Read CharStrings dictionary */
static void readChars(t1rCtx h) {
    STI gname;          /* Glyph name */
    unsigned short tag; /* Charstring tag */
    int LanguageGroup1 = h->fdicts.array[0].Private.LanguageGroup == 1;

    findToken(h, "begin");
    tag = 0;
    for (;;) {
        Char *chr;
        pstToken *token;
        char *cstr;
        long length;

        /* Parse glyph name */
        token = getToken(h);
        if (pstMatch(h->pst, token, "end")) {
            /* Reached end of CharStrings dict */
            if (!(h->flags & SEEN_NOTDEF))
                addNotdef(h, tag);

            h->flags |= SEEN_END;
            return;
        } else if (token->type != pstLiteral)
            fatal(h, t1rErrCharName, NULL);

        /* Trim leading / from glyph name literal */
        token->length--;
        token->value++;

        if (token->length == 7 && strncmp(token->value, ".notdef", 7) == 0)
            h->flags |= SEEN_NOTDEF; /* Remember .notdef glyph */

        /* Save glyph name string */
        gname = addString(h, token->length, token->value);
        if (addChar(h, gname, &chr)) {
            message(h, "duplicate charstring <%s> (discarded)",
                    getString(h, gname));
            gname = STI_UNDEF;
        }

        /* Read cstr bytes */
        length = readCstrTokenSeq(h, &cstr);
        if (length == 0)
            break;

        if (gname != STI_UNDEF) {
            int code = getStdEnc(h, gname);
            if (code != -1)
                h->encoding.standard[code] = tag;

            /* Initialize char */
            abfInitGlyphInfo(chr);
            chr->tag = tag++;
            chr->gname.impl = gname;
            if (LanguageGroup1)
                chr->flags |= ABF_GLYPH_LANG_1;

            /* Save charstring */
            saveCstr(h, length, cstr + 1, chr, 1);
        }

        /* Parse NP operator */
        token = getToken(h);
        if (token->type != pstOperator)
            break;
    }

    fatal(h, t1rErrCharEntry,
          "invalid character entry <%s>", getString(h, gname));
}

/* Init FD element. */
static void initFDInfo(t1rCtx h, int iFD) {
    FDInfo *fd = &h->FDArray.array[iFD];
    abfFontDict *fdict = &h->fdicts.array[iFD];
    abfInitFontDict(fdict);
    fd->fdict = fdict;
    fd->key.lenIV = 4;
    fd->key.SubrMapOffset = -1;
    fd->key.SubrCount = 0;
    fd->key.SDBytes = 0;
    fd->key.BlueValues = 0;
    fd->decrypt = t1cDecrypt;
}

/* ---------------------------- Multiple Master ---------------------------- */

/* Validate BlendDesignMap array. Return 1 if valid else 0. */
static int validBDM(t1rCtx h, int nAxes, int nMasters) {
    int i;
    int j;
    float *BDM = h->mm.BDM.array;

    i = 0;
    for (j = 0; j < nAxes; j++) {
        float U0;
        float N0;

        if (i >= h->mm.BDM.cnt - 1)
            return 0;
        U0 = BDM[i++];
        N0 = BDM[i++];

        if (N0 != 0.0)
            return 0;

        for (;;) {
            float U1;
            float N1;

            if (i >= h->mm.BDM.cnt - 1)
                return 0;
            U1 = BDM[i++];
            N1 = BDM[i++];

            if (U0 >= U1 || N0 >= N1)
                return 0;
            else if (N1 == 1.0)
                break;

            U0 = U1;
            N0 = N1;
        }
    }

    return i == h->mm.BDM.cnt;
}

/* Normalize user coordinate within range. */
static int normRange(float *u, float *n, int cnt, const float *ranges) {
    int i;

    /* Clamp to range */
    if (*u < ranges[0])
        *u = ranges[0];
    else if (*u > ranges[cnt - 1])
        *u = ranges[cnt - 1];

    /* Find range */
    for (i = 0; i < cnt - 1; i++)
        if (*u < ranges[i + 1])
            break;

    /* Interpolate range */
    *n = (*u - ranges[i]) / (ranges[i + 1] - ranges[i]);

    return i;
}

/* Perform bilinear interpolation of coordinate. */
static float interpolate(float n, float op, float a, float b, float c, float d) {
    return a + n * (b - a) + op * (d - a) + n * op * (a - b + c - d);
}

/* Piecewise normalize user coordinate. */
static float normalize(float *u, const float *pieces) {
    float u0 = pieces[0];
    float n0 = pieces[1];
    int i = 2;
    if (*u < u0) {
        *u = u0;
        return 0.0; /* Clamp low value */
    }
    for (;;) {
        float u1 = pieces[i++];
        float n1 = pieces[i++];
        if (*u <= u1)
            return n0 + (n1 - n0) * (*u - u0) / (u1 - u0);
        else if (n1 == 1.0) {
            *u = u1;
            return 1.0; /* Clamp high value */
        }
        u0 = u1;
        n0 = n1;
    }
}

/* Custom convert UDV to NDV for Kepler. */
static void keplerNDV(t1rCtx h) {
    static const float wt_ranges[] = {275, 385, 775};
    static const float wd_ranges[] = {350, 450, 575, 850};
    static const float op_ranges[] = {5, 7, 10, 18, 72};
    static const float wt_tab[][ARRAY_LEN(wt_ranges)] =
        {
            {0.00f, 0.18f, 1.00f}, /* 5 point */
            {0.00f, 0.18f, 1.00f}, /* 7 point */
            {0.00f, 0.22f, 1.00f}, /* 10 point */
            {0.00f, 0.22f, 1.00f}, /* 18 point */
            {0.00f, 0.22f, 1.00f}, /* 72 point */
        };
    static const float wd_tab[][ARRAY_LEN(wd_ranges)] =
        {
            {0.00f, 0.20f, 0.41f, 1.00f}, /* 5 point */
            {0.00f, 0.20f, 0.41f, 1.00f}, /* 7 point */
            {0.00f, 0.20f, 0.45f, 1.00f}, /* 10 point */
            {0.00f, 0.17f, 0.50f, 1.00f}, /* 18 point */
            {0.00f, 0.00f, 0.58f, 1.00f}, /* 72 point */
        };
    static const float op_pieces[] =
        {
            5, 0.00f,
            6, 0.15f,
            7, 0.24f,
            8, 0.30f,
            10, 0.44f,
            13, 0.50f,
            18, 0.80f,
            72, 1.00f,
        };
    float wt;
    float wd;
    float op;
    float *UDV = h->fd->aux.UDV;
    float *NDV = h->fd->aux.NDV;
    int iwt = normRange(&UDV[0], &wt, ARRAY_LEN(wt_ranges), wt_ranges);
    int iwd = normRange(&UDV[1], &wd, ARRAY_LEN(wd_ranges), wd_ranges);
    int iop = normRange(&UDV[2], &op, ARRAY_LEN(op_ranges), op_ranges);

    NDV[0] = interpolate(wt, op,
                         wt_tab[iop + 0][iwt + 0],
                         wt_tab[iop + 0][iwt + 1],
                         wt_tab[iop + 1][iwt + 1],
                         wt_tab[iop + 1][iwt + 0]);
    NDV[1] = interpolate(wd, op,
                         wd_tab[iop + 0][iwd + 0],
                         wd_tab[iop + 0][iwd + 1],
                         wd_tab[iop + 1][iwd + 1],
                         wd_tab[iop + 1][iwd + 0]);
    NDV[2] = normalize(&UDV[2], op_pieces);
}

/* Custom convert NDV to WV for Jenson. */
static void jensonCDV(t1rCtx h) {
    float wt = h->fd->aux.NDV[0];
    float op = h->fd->aux.NDV[1] * 2;
    float *WV = h->fd->aux.WV;
    if (op <= 1.0) {
        WV[0] = (1 - wt) * (1 - op);
        WV[1] = wt * (1 - op);
        WV[2] = (1 - wt) * op;
        WV[3] = wt * op;
        WV[4] = 0;
        WV[5] = 0;
    } else {
        WV[0] = 0;
        WV[1] = 0;
        WV[2] = (1 - wt) * (2 - op);
        WV[3] = wt * (2 - op);
        WV[4] = (1 - wt) * (op - 1);
        WV[5] = wt * (op - 1);
    }
}

/* Match FontName. */
static int CTL_CDECL matchFontName(const void *key, const void *value) {
    return strcmp((char *)key, *(char **)value);
}

/* Convert UDV to NDV for standard MM font. */
static void stdNDV(t1rCtx h, int nAxes) {
    int i;
    int j;

    i = 0;
    for (j = 0;; j += 2) {
        h->fd->aux.NDV[i] = normalize(&h->fd->aux.UDV[i], &h->mm.BDM.array[j]);

        if (++i == nAxes)
            break;

        /* Find end of axis */
        for (;;) {
            j += 2;
            if (h->mm.BDM.array[j + 1] == 1.0)
                break;
        }
    }
}

/* Convert NDV to WV for standard MM font. */
static void stdCDV(t1rCtx h, int nAxes, int nMasters) {
    int i;
    int j;
    int k;
    char order[T1_MAX_MASTERS];
    char check[T1_MAX_MASTERS];
    float *NDV = h->fd->aux.NDV;
    float *WV = h->fd->aux.WV;

    /* Create master design ordering */
    k = 0;
    for (i = 0; i < nMasters; i++) {
        order[i] = 0;
        for (j = 0; j < nAxes; j++) {
            double d = h->mm.BDP.array[k++];
            long n = (long)d;

            if (n != d || (n != 0 && n != 1))
                /* Non-integer master coordinates must have CDV support */
                fatal(h, t1rErrGeometry, NULL);

            /* Each master's position is computed by considering its axis
               coordinates as a binary number with first axis as lsb. */
            order[i] |= n << j;
        }
    }

    /* Check every master represented */
    memset(check, 0, nMasters);
    for (i = 0; i < nMasters; i++)
        check[(int)order[i]] = 1;
    for (i = 0; i < nMasters; i++)
        if (!check[i])
            fatal(h, t1rErrGeometry, NULL);

    /* Check nMasters for power of 2 */
    if (nMasters != "\002\004\010\020"[nAxes - 1])
        fatal(h, t1rErrGeometry, NULL);

    /* Compute Weight Vector */
    for (i = 0; i < nMasters; i++) {
        WV[i] = 1;
        for (j = 0; j < nAxes; j++)
            WV[i] *= (order[i] & 1 << j) ? NDV[j] : 1 - NDV[j];
    }
}

/* Calculate UDV for default instance (specified by WeightVector). */
static void calcUDVFromWV(t1rCtx h, int nAxes, int nMasters) {
    int i;
    int j;
    float *BDM = h->mm.BDM.array;

    i = 0;
    for (j = 0; j < nAxes; j++) {
        int k;
        int l = j;
        float U0;
        float N0;
        float norm = 0; /* Per-axis normalized coord accumulator */

        /* Add normalized coord contribution for each master */
        for (k = 0; k < nMasters; k++) {
            norm += h->mm.BDP.array[l] * h->fd->aux.WV[k];
            l += nAxes;
        }

        /* Find matching axis map and interpolate */
        U0 = BDM[i++];
        N0 = BDM[i++];
        for (;;) {
            float U1 = BDM[i++];
            float N1 = BDM[i++];

            if (N0 <= norm && norm <= N1)
                h->fd->aux.UDV[j] =
                    RND(((U1 - U0) * (norm - N0) / (N1 - N0) + U0));

            if (N1 == 1.0)
                break; /* End of axis */

            U0 = U1;
            N0 = N1;
        }
    }
}

/* Initailize MM font. */
static void mmInit(t1rCtx h) {
    static char *jenson[] =
        {
            "AJensonMM",
            "AJensonMM-Alt",
            "AJensonMM-Ep",
            "AJensonMM-It",
            "AJensonMM-ItAlt",
            "AJensonMM-ItEp",
            "AJensonMM-ItSC",
            "AJensonMM-SC",
            "AJensonMM-Sw",
        };
    static char *kepler[] =
        {
            "KeplMM",
            "KeplMM-Ep",
            "KeplMM-It",
            "KeplMM-ItEp",
            "KeplMM-ItSC",
            "KeplMM-SC",
            "KeplMM-Sw",
        };
    int nMasters;
    int nAxes;
    char *FontName = getString(h, (STI)h->fd->fdict->FontName.impl);
    int isJenson = bsearch(FontName, jenson, ARRAY_LEN(jenson),
                           sizeof(jenson[0]), matchFontName) != NULL;
    int isKepler = bsearch(FontName, kepler, ARRAY_LEN(kepler),
                           sizeof(kepler[0]), matchFontName) != NULL;

    if (h->flags & MM_FONT)
        return; /* Already done */

    h->flags |= MM_FONT;

    /* Check for required keys */
    if (!h->key.seen[kBlendDesignPositions])
        fatal(h, t1rErrBDP, NULL);
    if (!h->key.seen[kBlendDesignMap])
        fatal(h, t1rErrBDM, NULL);
    if (!h->key.seen[kWeightVector])
        fatal(h, t1rErrWV, NULL);

    /* Compute and check design geometry */
    nMasters = h->mm.WV.cnt;
    nAxes = h->mm.BDP.cnt / nMasters;
    if (nAxes > T1_MAX_AXES || nAxes * nMasters != h->mm.BDP.cnt ||
        !validBDM(h, nAxes, nMasters))
        fatal(h, t1rErrGeometry, NULL);

    /* Copy WV */
    h->fd->aux.nMasters = nMasters;
    memcpy(h->fd->aux.WV, h->mm.WV.array, nMasters * sizeof(h->mm.WV.array[0]));

    if (h->mm.UDV == NULL) {
        /* Not specified by client; use default */
        if (isKepler) {
            h->fd->aux.UDV[0] = 385;
            h->fd->aux.UDV[1] = 575;
            h->fd->aux.UDV[2] = 10;
        } else
            calcUDVFromWV(h, nAxes, nMasters);
        return;
    }

    /* Copy UDV */
    memcpy(h->fd->aux.UDV, h->mm.UDV, nAxes * sizeof(h->mm.UDV[0]));

    /* Compute NDV */
    if (isKepler)
        keplerNDV(h);
    else
        stdNDV(h, nAxes);

    /* Compute WV */
    if (isJenson)
        jensonCDV(h);
    else
        stdCDV(h, nAxes, nMasters);
}

/* Fix up multiple master snapshot. */
static void prepMMData(t1rCtx h) {
    unsigned int i;
    size_t lenCoords;
    size_t lenFontName;
    char buf[64];
    char coords[64];
    char *p;
    char *FontName = getString(h, (STI)h->fd->fdict->FontName.impl);
    unsigned int nAxes = h->mm.BDP.cnt / h->fd->aux.nMasters;
    char *Weight = "SnapShotMM";

    if (h->top.ItalicAngle == 0) {
        /* BuildFont incorrectly specifies the ItalicAngle for MM oblique
           fonts as 0. This code checks for this condition and fixes the
           fonts known to have this problem. */
        static const struct
        {
            char *FontName;
            float ItalicAngle;
        } bad_fonts[] =
            {
                {"ITCAvantGardeMM-Oblique", -10.5},
                {"TektonMM-Oblique", -10.0},
            };
        for (i = 0; i < ARRAY_LEN(bad_fonts); i++)
            if (strcmp(FontName, bad_fonts[i].FontName) == 0) {
                h->top.ItalicAngle = bad_fonts[i].ItalicAngle;
                break;
            }
    }

    /* Set new Weight string */
    h->top.Weight.impl = addString(h, strlen(Weight), Weight);

    /* Delete FullName, FamilyName, UniqueID */
    h->top.FullName.impl = ABF_UNSET_INT;
    h->top.FamilyName.impl = ABF_UNSET_INT;
    h->top.UniqueID = ABF_UNSET_INT;

    /* Construct instance FontName rounding coords to nearest int */
    coords[0] = '_';
    p = &coords[1];
    for (i = 0; i < nAxes; i++) {
        sprintf(p, "%d_", (int)RND(h->fd->aux.UDV[i]));
        p += strlen(p);
    }
    lenFontName = strlen(FontName);
    lenCoords = strlen(coords);
    if (lenCoords + lenFontName + 1 > sizeof(buf))
        sprintf(buf, "%.*s%s",
                (int)(sizeof(buf) - (lenCoords + 1)), FontName, coords);
    else
        sprintf(buf, "%s%s", FontName, coords);
    h->fd->fdict->FontName.impl = addString(h, strlen(buf), buf);

    if (h->top.XUID.cnt == ABF_EMPTY_ARRAY)
        message(h, "no XUID in MM font");
    else {
        /* Append instance UDV to XUID */
        int iBase = h->top.XUID.cnt;

        if (iBase + nAxes > ARRAY_LEN(h->top.XUID.array))
            iBase = ARRAY_LEN(h->top.XUID.array) - nAxes;

        for (i = 0; i < nAxes; i++)
            h->top.XUID.array[iBase + i] = (long)h->fd->aux.UDV[i];
        h->top.XUID.cnt += nAxes;
    }
}

/* ---------------------------- Literal Parsing ---------------------------- */

/* Report bad dict value */
static void badKeyValue(t1rCtx h, int iKey) {
    if (h->FDArray.cnt > 1)
        fatal(h, t1rErrKeyValue, "/%s bad value: FD[%d]",
              keys[iKey], h->fd - h->FDArray.array);
    else
        fatal(h, t1rErrKeyValue, "/%s bad value", keys[iKey]);
}

/* Parse string value, add to string pool, and return its index. */
static STI parseString(t1rCtx h, int kKey) {
    pstToken *token = getToken(h);
    if (token->type != pstString)
        badKeyValue(h, kKey);
    return addString(h, token->length - 2, token->value + 1);
}

/* Parse name value, add to string pool, and return its index. */
static STI parseName(t1rCtx h, int kKey) {
    pstToken *token = getToken(h);
    switch (token->type) {
        case pstString:
            return addString(h, token->length - 2, token->value + 1);
        case pstLiteral:
            return addString(h, token->length - 1, token->value + 1);
        default:
            badKeyValue(h, kKey);
    }
    return 0; /* Suppress compiler warning */
}

/* Parse blend array and blend elements. */
static float parseBlend(t1rCtx h, int kKey, char **str) {
    float value;
    int i;
    char *p = *str;
    char delim = (*p++ == '[') ? ']' : '}';

    if (!(h->flags & MM_FONT))
        badKeyValue(h, kKey);

    /* Skip initial whitespace */
    while (isspace(*p))
        p++;

    /* Parse blend array */
    value = 0;
    for (i = 0; i < h->fd->aux.nMasters; i++) {
        char *q;

        /* Parse and blend number */
        value += h->fd->aux.WV[i] * (float)ctuStrtod(p, &q);
        if (p == q)
            badKeyValue(h, kKey); /* Invalid number */
        p = q;

        /* Skip trailing whitespace */
        while (isspace(*p))
            p++;
    }

    if (*p != delim)
        badKeyValue(h, kKey);

    *str = p;
    return value;
}

/* Make temporary null-terminated copy to ensure strtod/l termination. */
static char *copyArrayToken(t1rCtx h, pstToken *token) {
    dnaSET_CNT(h->tmp, token->length + 1);
    memcpy(h->tmp.array, token->value, token->length);
    h->tmp.array[token->length] = '\0';
    return h->tmp.array;
}

/* Parse blendable integer value and return it. */
static long parseInt(t1rCtx h, int kKey) {
    pstToken *token = getToken(h);
    char *p = token->value;
    switch (token->type) {
        case pstInteger:
            return pstConvInteger(h->pst, token);
        case pstArray:
        case pstProcedure:
            p = copyArrayToken(h, token);
            return (long)RND(parseBlend(h, kKey, &p));
        default:
            badKeyValue(h, kKey);
    }
    return 0; /* Suppress compiler warning */
}

/* Parse blendable integer or real value and return it. */
static float parseNum(t1rCtx h, int kKey, int round) {
    pstToken *token = getToken(h);
    switch (token->type) {
        case pstInteger:
            return (float)pstConvInteger(h->pst, token);
        case pstReal:
            return (float)pstConvReal(h->pst, token);
        case pstArray:
        case pstProcedure: {
            char *p = copyArrayToken(h, token);
            float value = parseBlend(h, kKey, &p);
            return round ? RND(value) : value;
        }
        default:
            badKeyValue(h, kKey);
    }
    return 0; /* Suppress compiler warning */
}

/* Parse and save number array. */
static int parseNumArray(t1rCtx h, int kKey,
                         int min, int max, float *array,
                         int blend, int report_empty) {
    int i;
    char *p;
    pstToken *token = getToken(h);

    if (token->type != pstArray && token->type != pstProcedure)
        badKeyValue(h, kKey); /* Expecting array object */

    i = 0;
    p = copyArrayToken(h, token) + 1;
    for (;;)
        switch (*p) {
            case '\0':
                goto finish;
            case '\t':
            case '\n':
            case '\f':
            case '\r':
            case ' ':
            case ']':
            case '}':
                p++;
                break;
            case '[':
            case '{':
                if (blend) {
                    if (i < max) {
                        float value = parseBlend(h, kKey, &p);
                        if (kKey == kFontBBox) {
                            array[i] =
                                (float)((i < 2) ? floor(value) : ceil(value));
                            i++;
                        } else
                            array[i++] = value;
                    } else {
                        message(h, "/%s array too big (truncated)", keys[kKey]);
                        return max;
                    }
                }
                p++;
                break;
            case 'd':
                if (p[1] == 'i' && p[2] == 'v' && i > 1 && array[i - 1] != 0) {
                    /* Try to support div operator; unwisely used by drivers */
                    array[i - 2] /= array[i - 1];
                    i--;
                    p += 3;
                } else
                    badKeyValue(h, kKey);
                break;
            default:
                if (i < max) {
                    char *q;
                    array[i++] = (float)ctuStrtod(p, &q);
                    if (p == q)
                        badKeyValue(h, kKey); /* Invalid number */
                    p = q;
                } else {
                    message(h, "/%s array too big (truncated)", keys[kKey]);
                    return max;
                }
                break;
        }

finish:
    if (i < min)
        badKeyValue(h, kKey);
    if (report_empty && i == 0)
        message(h, "/%s array empty (discarded)", keys[kKey]);
    return i;
}

/* Parse and save integer array. */
static int parseIntArray(t1rCtx h, int kKey, int min, int max, long *array) {
    int i;
    char *p;
    pstToken *token = getToken(h);

    if (token->type != pstArray && token->type != pstProcedure)
        badKeyValue(h, kKey); /* Expecting array object */

    i = 0;
    p = copyArrayToken(h, token) + 1;
    for (;;)
        switch (*p) {
            case '\0':
                goto finish;
            case '\t':
            case '\n':
            case '\f':
            case 'r':
            case ' ':
            case ']':
                p++;
                break;
            default:
                if (i < max) {
                    char *q;
                    array[i++] = strtol(p, &q, 0);
                    if (p == q)
                        badKeyValue(h, kKey); /* Invalid number */
                    p = q;
                } else {
                    message(h, "/%s array too big (truncated)", keys[kKey]);
                    return max;
                }
                break;
        }

finish:
    if (i < min)
        badKeyValue(h, kKey);
    return i;
}

/* Parse boolean value and return 0 if false and 1 if true. */
static long parseBool(t1rCtx h, int kKey) {
    long bool = 0; /* Suppress optimizer warning */
    pstToken *token = getToken(h);
    switch (token->type) {
        case pstOperator:
            if (pstMatch(h->pst, token, "false"))
                bool = 0;
            else if (pstMatch(h->pst, token, "true"))
                bool = 1;
            else
                badKeyValue(h, kKey);
            break;
        case pstArray: {
            int i = 0;
            char *p = token->value + 1;
            float value = 0;

            /* Skip initial whitespace */
            while (isspace(*p))
                p++;

            /* Parse blend array */
            for (i = 0; i < h->fd->aux.nMasters; i++) {
                if (strncmp(p, "false", 5) == 0)
                    p += 5;
                else if (strncmp(p, "true", 4) == 0) {
                    value += h->fd->aux.WV[i];
                    p += 4;
                } else
                    badKeyValue(h, kKey); /* Invalid boolean */

                /* Skip trailing whitespace */
                while (isspace(*p))
                    p++;
            }

            if (*p != ']')
                badKeyValue(h, kKey);

            if (kKey == kForceBold)
                bool = value >= h->mm.ForceBoldThreshold;
            else
                bool = value >= 0.5;
        } break;
        default:
            badKeyValue(h, kKey);
    }
    return bool;
}

/* Parse font matrix. Return 1 if Top dict FontMatrix else 0. */
static int parseFontMatrix(t1rCtx h, abfTopDict *top, abfFontDict *font) {
    float array[6];

    /* Parse matrix */
    (void)parseNumArray(h, kFontMatrix, 6, 6, array, 0, 0);

    if (h->flags & CID_FONT && !h->key.seen[kFDArray]) {
        /* Top-level CIDFont matrix */
        if (array[0] != 1.0 ||
            array[1] != 0.0 ||
            array[2] != 0.0 ||
            array[3] != 1.0 ||
            array[4] != 0.0 ||
            array[5] != 0.0) {
            /* Save matrix if not identity */
            memcpy(top->cid.FontMatrix.array, array, sizeof(array));
            top->cid.FontMatrix.cnt = 6;
        }
        return 1;
    } else {
        /* Font dict matrix */
        float *result = font->FontMatrix.array;

        if (top->cid.FontMatrix.cnt != ABF_EMPTY_ARRAY) {
            /* Form product of top and FDArray matrices */
            float *a = top->cid.FontMatrix.array;
            float *b = array;

            result[0] = a[0] * b[0] + a[1] * b[2];
            result[1] = a[0] * b[1] + a[1] * b[3];
            result[2] = a[2] * b[0] + a[3] * b[2];
            result[3] = a[2] * b[1] + a[3] * b[3];
            result[4] = a[4] * b[0] + a[5] * b[2] + b[4];
            result[5] = a[4] * b[1] + a[5] * b[3] + b[5];
        } else
            /* Copy matrix */
            memcpy(result, array, sizeof(array));

        if (result[0] != 0.001f ||
            result[1] != 0.0 ||
            result[2] != 0.0 ||
            result[3] != 0.001f ||
            result[4] != 0.0 ||
            result[5] != 0.0) {
            /* Non-default matrix */
            float max;
            int i;

            /* Make matrix available */
            font->FontMatrix.cnt = 6;

            /* Find the largest of a, b, c, and d */
            max = 0.0;
            for (i = 0; i < 4; i++) {
                float value = result[i];
                if (value < 0.0)
                    value = -value;
                if (value > max)
                    max = value;
            }
            if (max == 0.0)
                fatal(h, t1rErrFontMatrix, NULL);

            /* Calculate units-per-em */
            top->sup.UnitsPerEm = (unsigned short)(1.0 / max + 0.5);
        }
    }
    return 0;
}

/* Check RD type to see if this is an incremental download font. */
static void checkRDType(t1rCtx h) {
    pstToken *token = getToken(h);
    if (token->type == pstProcedure) {
        dnaSET_CNT(h->tmp, token->length + 1);
        memcpy(h->tmp.array, token->value, token->length);
        h->tmp.array[token->length] = '\0';
        if (strstr(h->tmp.array, "readhexstring") != NULL)
            h->flags |= PRINT_STREAM;
    }
}

/* Parse Erode proc. If we are parsing a print stream font we look for a
   carrige return in the proc. definition as a clue to the line ending type
   being used in the stream. If we haven't already seen the /StdVW key we
   derive its value from the 16th token in the proc. */
static void parseErodeProc(t1rCtx h, abfPrivateDict *private) {
    int i;
    pstToken *token = getToken(h);

    if (h->flags & PRINT_STREAM)
        for (i = 0; i < token->length; i++)
            if (token->value[i] == '\r') {
                h->flags |= CRLF_NEWLINES;
                break;
            }

    if (h->key.seen[kStdVW])
        return;

    if (token->type == pstProcedure) {
        char *p = copyArrayToken(h, token) + 1;
        char *endp = p + strlen(p);
        i = 0;
        while (*p != '\0') {
            /* Skip whitespace */
            while (isspace(*p) && (p < endp))
                p++;

            if (*p == '}')
                break; /* Badly formed erode proc */

            if (++i == 16) {
                /* Convert 16th token */
                char *q;
                long value = strtol(p, &q, 0);
                if (p != q && value >= 0)
                   private->StdVW = (float)value;
                break;
            }

            /* Skip token */
            while (!isspace(*p) && (p < endp))
                p++;
        }
    }
}

/* Parse OrigFontType. */
static void parseOrigFontType(t1rCtx h, abfTopDict *top) {
    pstToken *token = getToken(h);
    if (token->type == pstLiteral) {
        static struct
        {
            const char *name;
            int id;
        } map[] =
            {
                {"Type1", abfOrigFontTypeType1},
                {"CID", abfOrigFontTypeCID},
                {"TrueType", abfOrigFontTypeTrueType},
                {"OCF", abfOrigFontTypeOCF},
            };
        unsigned int i;
        long length;
        char *value = pstConvLiteral(h->pst, token, &length);

        for (i = 0; i < ARRAY_LEN(map); i++)
            if (strlen(map[i].name) == (size_t)length &&
                strncmp(value, map[i].name, length) == 0) {
                top->OrigFontType = map[i].id;
                return;
            }
    }

    badKeyValue(h, kOrigFontType);
}

/* Initialize FDArray. */
static void initFDArray(t1rCtx h, long cnt) {
    int i;
    if (cnt < 0 || cnt > 256)
        badKeyValue(h, kFDArray);
    dnaSET_CNT(h->FDArray, cnt);
    dnaSET_CNT(h->fdicts, cnt);
    for (i = 0; i < h->FDArray.cnt; i++)
        initFDInfo(h, i);
    h->fd = &h->FDArray.array[0];
}

/* Match key name. */
static int CTL_CDECL matchKey(const void *key, const void *value) {
    const char *a = ((MatchStr *)key)->value;
    const char *end = a + ((MatchStr *)key)->length;
    const char *b = *(char **)value;
    while (a != end)
        if (*b == '\0')
            return 1;
        else if (*a < *b)
            return -1;
        else if (*a++ > *b++)
            return 1;
    return (*b != '\0') ? -1 : 0;
}

/* Initialize key seen array. */
static void initKeySeen(t1rCtx h) {
    memset(h->key.seen, 0, sizeof(h->key.seen));
}

/* Process literal. */
static void doLiteral(t1rCtx h, pstToken *literal) {
    abfTopDict *top;
    abfFontDict *font;
    abfPrivateDict *private;
    MatchStr key;
    int keyId;
    const char **p;

    /* Lookup literal in key list */
    key.value = pstConvLiteral(h->pst, literal, &key.length);
    p = (const char **)bsearch(&key, keys, ARRAY_LEN(keys),
                               sizeof(keys[0]), matchKey);
    if (p == NULL)
        return; /* Ignore unknown key */

    /* Convert to index */
    /* 64-bit warning fixed by cast here HO */
    keyId = (int)(p - keys);

    /* For CIDFonts check for key redefinition which signals new FD */
    if (h->flags & CID_FONT && h->key.seen[keyId]) {
        if (++h->fd - h->FDArray.array >= h->FDArray.cnt)
            fatal(h, t1rErrFDArray, NULL);
        initKeySeen(h);
    }

    top = &h->top;
    font = h->fd->fdict;
    private = &font->Private;

    /* Process key */
    switch (keyId) {
            /* -------------------------- Top Dict ------------------------- */
        case kversion:
            top->version.impl = parseString(h, kversion);
            break;
        case kNotice:
            top->Notice.impl = parseString(h, kNotice);
            break;
        case kCopyright:
            top->Copyright.impl = parseString(h, kCopyright);
            break;
        case kFullName:
            top->FullName.impl = parseString(h, kFullName);
            break;
        case kFamilyName:
            top->FamilyName.impl = parseString(h, kFamilyName);
            break;
        case kWeight:
            top->Weight.impl = parseString(h, kWeight);
            break;
        case kisFixedPitch:
            top->isFixedPitch = parseBool(h, kisFixedPitch);
            break;
        case kItalicAngle:
            top->ItalicAngle = parseNum(h, kItalicAngle, 0);
            break;
        case kUnderlinePosition:
            top->UnderlinePosition = parseNum(h, kUnderlinePosition, 1);
            break;
        case kUnderlineThickness:
            top->UnderlineThickness = parseNum(h, kUnderlineThickness, 1);
            break;
        case kUniqueID:
            top->UniqueID = parseInt(h, kUniqueID);
            break;
        case kFontBBox:
            (void)parseNumArray(h, kFontBBox, 4, 4, top->FontBBox, 1, 0);
            break;
        case kStrokeWidth:
            top->StrokeWidth = parseNum(h, kStrokeWidth, 1);
            break;
        case kXUID:
            top->XUID.cnt = parseIntArray(h, kXUID, 1, 16, top->XUID.array);
            break;
        case kEncoding:
            readEncoding(h);
            break;
        case kPaintType:
            font->PaintType = parseInt(h, kPaintType);
            break;
        case kFontType:
            if (parseInt(h, kFontType) != 1)
                fatal(h, t1rErrFontType, NULL);
            break;
        case klenIV:
            h->fd->key.lenIV = parseInt(h, klenIV);
            if (h->fd->key.lenIV < -1)
                badKeyValue(h, klenIV);
            break;
        case kFontMatrix:
            if (parseFontMatrix(h, top, font))
                return; /* Avoid setting the key.seen[kFontMatrix] for top dict */
            break;
        case kFontName:
            font->FontName.impl = parseName(h, kFontName);
            break;
        case kPrivate:
            break;
        case kCharStrings:
            readChars(h);
            break;
            /* ------------------------ Private Dict ----------------------- */
        case kBlueValues:
           private->BlueValues.cnt =
                parseNumArray(h, kBlueValues, 0, 14,
                              private->BlueValues.array, 1, 0);
            h->fd->key.BlueValues = 1;
            break;
        case kOtherBlues:
           private->OtherBlues.cnt =
                parseNumArray(h, kOtherBlues, 0, 10,
                              private->OtherBlues.array, 1, 1);
            break;
        case kFamilyBlues:
            if (h->key.seen[kFamilyBlues] && !h->key.seen[kWeightVector])
                /* Ignore second FamilyBlues in some bad single master fonts */
                message(h, "duplicate /FamilyBlues (ignored)");
            else
               private->FamilyBlues.cnt =
                parseNumArray(h, kFamilyBlues, 0, 14,
                              private->FamilyBlues.array, 1, 1);
            break;
        case kFamilyOtherBlues:
           private->FamilyOtherBlues.cnt =
                parseNumArray(h, kFamilyOtherBlues, 0, 10,
                              private->FamilyOtherBlues.array, 1, 1);
            break;
        case kBlueScale:
           private->BlueScale = parseNum(h, kBlueScale, 0);
            break;
        case kBlueShift:
           private->BlueShift = parseNum(h, kBlueShift, 0);
            break;
        case kBlueFuzz:
           private->BlueFuzz = parseNum(h, kBlueFuzz, 1);
            break;
        case kStdHW:
            (void)parseNumArray(h, kStdHW, 0, 1, &private->StdHW, 1, 1);
            break;
        case kStdVW:
            (void)parseNumArray(h, kStdVW, 0, 1, &private->StdVW, 1, 1);
            break;
        case kStemSnapH:
           private->StemSnapH.cnt =
                parseNumArray(h, kStemSnapH, 0, 12,
                              private->StemSnapH.array, 1, 1);
            break;
        case kStemSnapV:
           private->StemSnapV.cnt =
                parseNumArray(h, kStemSnapV, 0, 12,
                              private->StemSnapV.array, 1, 1);
            break;
        case kForceBold:
           private->ForceBold = parseBool(h, kForceBold);
            break;
        case kForceBoldThreshold:
            h->mm.ForceBoldThreshold = parseNum(h, kForceBoldThreshold, 1);
            break;
        case kRndStemUp:
            /* RndStemUp with any value saved as LanguageGroup=1 
               (which enables global coloring) */
           private->LanguageGroup = 1;
            break;
        case kLanguageGroup:
           private->LanguageGroup = parseInt(h, kLanguageGroup);
            break;
        case kExpansionFactor:
           private->ExpansionFactor = parseNum(h, kExpansionFactor, 1);
            break;
        case kinitialRandomSeed:
           private->initialRandomSeed = parseNum(h, kinitialRandomSeed, 0);
            break;
        case kSubrs:
            readSubrs(h);
            break;
        case kErode:
            parseErodeProc(h, private);
            break;
        case kRD:
            checkRDType(h);
            break;
            /* ------------------ Multiple Master Top Dict ----------------- */
        case kWeightVector:
            h->mm.WV.cnt =
                parseNumArray(h, kWeightVector, 2, ARRAY_LEN(h->mm.WV.array),
                              h->mm.WV.array, 0, 0);
            break;
        case kBlendDesignPositions:
            h->mm.BDP.cnt =
                parseNumArray(h, kBlendDesignPositions, 2,
                              ARRAY_LEN(h->mm.BDP.array), h->mm.BDP.array, 0, 0);
            break;
        case kBlendDesignMap:
            h->mm.BDM.cnt =
                parseNumArray(h, kBlendDesignMap, 4, ARRAY_LEN(h->mm.BDM.array),
                              h->mm.BDM.array, 0, 0);
            break;
        case kBlend:
            mmInit(h);
            break;
            /* ------------------------ CID Top Dict ----------------------- */
        case kCIDFontName:
            top->cid.CIDFontName.impl = parseName(h, kCIDFontName);
            break;
        case kRegistry:
            top->cid.Registry.impl = parseString(h, kRegistry);
            break;
        case kOrdering:
            top->cid.Ordering.impl = parseString(h, kOrdering);
            break;
        case kSupplement:
            top->cid.Supplement = parseInt(h, kSupplement);
            break;
        case kCIDFontVersion:
            top->cid.CIDFontVersion = parseNum(h, kCIDFontVersion, 0);
            break;
        case kCIDFontRevision:
            top->cid.CIDFontRevision = parseInt(h, kCIDFontRevision);
            break;
        case kUIDBase:
            top->cid.UIDBase = parseInt(h, kUIDBase);
            break;
        case kCIDCount:
            top->cid.CIDCount = parseInt(h, kCIDCount);
            if (top->cid.CIDCount < 0 || top->cid.CIDCount > 65535)
                badKeyValue(h, kCIDCount);
            break;
        case kCIDInit:
            h->flags |= CID_FONT;
            break;
        case kCIDFontType:
            if (parseInt(h, kCIDFontType) != 0)
                fatal(h, t1rErrCIDFontType, NULL);
            break;
        case kCIDMapOffset:
            h->key.CIDMapOffset = parseInt(h, kCIDMapOffset);
            break;
        case kFDBytes:
            h->key.FDBytes = parseInt(h, kFDBytes);
            break;
        case kGDBytes:
            h->key.GDBytes = parseInt(h, kGDBytes);
            break;
        case kFDArray:
            initFDArray(h, parseInt(h, kFDArray));
            break;
        case kRunInt: {
            /* Check protection from the value of the /RunInt key. */
            pstToken *token = getToken(h);
            if (pstMatch(h->pst, token, "/CCRun"))
                /* Unprotected font */;
            else if (pstMatch(h->pst, token, "/eCCRun"))
                /* Protected font */
                h->fd->decrypt = t1cUnprotect;
            else
                badKeyValue(h, kRunInt);
        } break;
        case kSubrMapOffset:
            h->fd->key.SubrMapOffset = parseInt(h, kSubrMapOffset);
            break;
        case kSDBytes:
            h->fd->key.SDBytes = (unsigned short)parseInt(h, kSDBytes);
            break;
        case kSubrCount:
            h->fd->key.SubrCount = (unsigned short)parseInt(h, kSubrCount);
            break;
        case kGlyphDirectory:
            parseGlyphDirectory(h);
            h->flags |= (PRINT_STREAM | SEEN_END);
            break;
            /* ----------------------- Miscellaneous ----------------------- */
        case kPostScript:
            h->top.PostScript.impl = parseString(h, kPostScript);
            break;
        case kBaseFontName:
            h->top.BaseFontName.impl = parseString(h, kBaseFontName);
            break;
        case kBaseFontBlend:
            top->BaseFontBlend.cnt = parseIntArray(h, kBaseFontBlend, 1, 15,
                                                   h->top.BaseFontBlend.array);
            break;
        case kFSType:
            top->FSType = parseInt(h, kFSType);
            if (top->FSType < 0 || top->FSType > 65535)
                badKeyValue(h, kFSType);
            break;
        case kOrigFontType:
            parseOrigFontType(h, top);
            break;
        case kWasEmbedded:
            top->WasEmbedded = parseBool(h, kWasEmbedded);
            break;
        case kChameleon:
            fatal(h, t1rErrChameleon, NULL);
            break;
        case khires:
            fatal(h, t1rErrHybrid, NULL);
            break;
    }
    h->key.seen[keyId] = 1; /* Note that key was seen */
}

/* ---------------------------- Operator Parsing --------------------------- */

/* Process operator. */
static void doOperator(t1rCtx h, pstToken *operator) {
    pstToken *token;
    if (pstMatch(h->pst, operator, "currentfile")) {
        int result = 0;
        token = getToken(h);
        if (pstMatch(h->pst, token, "eexec"))
            result = pstSetDecrypt(h->pst); /* Prepare for eexec */
        else if (pstMatch(h->pst, token, "closefile"))
            result = pstSetPlain(h->pst); /* Prepare for plaintext */
        if (result)
            pstFatal(h, result);
    } else if (h->key.seen[kPrivate] &&
               pstMatch(h->pst, operator, "FontDirectory")) {
        /* Synthetic font; temporarily save synthetic font values */
        h->synthetic.FontName = h->fd->fdict->FontName;
        h->synthetic.FullName = h->top.FullName;
        h->synthetic.ItalicAngle = h->top.ItalicAngle;
        h->synthetic.UniqueID = h->top.UniqueID;
        h->synthetic.FontMatrix = h->fd->fdict->FontMatrix;

        /* Reinitialize */
        abfInitTopDict(&h->top);
        abfInitFontDict(h->fd->fdict);

        initKeySeen(h);
        tmpInit(h);
        h->fd->subrs.offset.cnt = 0;

        h->flags |= SYN_FONT;
    }
}

/* ------------------------------ CID Parsing ------------------------------ */

/* Save 5 std subrs to tmp file and initialize each FD to use them. */
static void saveStdSubrs(t1rCtx h) {
    static const unsigned char subr0[] =
        {0x8e, 0x8b, 0xc, 0x10, 0xc, 0x11, 0xc, 0x11, 0xc, 0x21, 0xb};
    static const unsigned char subr1[] =
        {0x8b, 0x8c, 0xc, 0x10, 0xb};
    static const unsigned char subr2[] =
        {0x8b, 0x8d, 0xc, 0x10, 0xb};
    static const unsigned char subr3[] =
        {0xb};
    static const unsigned char subr4[] =
        {0x8e, 0x8c, 0x8e, 0xc, 0x10, 0xc, 0x11, 0xa, 0xb};
    enum { subrcnt = 5 };
    long tmpoffs[subrcnt];
    long lenIV;
    ctlRegion region;
    int i;

    /* Make temporary copy of lenIV */
    lenIV = h->FDArray.array[0].key.lenIV;

    /* Set lenIV -1 to match pre-defined subrs */
    h->FDArray.array[0].key.lenIV = -1;

    /* Write subrs to temp file */
    region.begin = h->tmpoff;
    tmpoffs[0] = saveSubr(h, sizeof(subr0), (char *)subr0, 0, 0);
    tmpoffs[1] = saveSubr(h, sizeof(subr1), (char *)subr1, 0, 1);
    tmpoffs[2] = saveSubr(h, sizeof(subr2), (char *)subr2, 0, 2);
    tmpoffs[3] = saveSubr(h, sizeof(subr3), (char *)subr3, 0, 3);
    tmpoffs[4] = saveSubr(h, sizeof(subr4), (char *)subr4, 0, 4);
    region.end = h->tmpoff;

    /* Restore original lenIV */
    h->FDArray.array[0].key.lenIV = lenIV;

    for (i = 0; i < h->FDArray.cnt; i++) {
        FDInfo *fd = &h->FDArray.array[i];
        fd->subrs.region = region;
        dnaSET_CNT(fd->subrs.offset, subrcnt);
        memcpy(fd->subrs.offset.array, tmpoffs, sizeof(tmpoffs));
    }
}

/* Set LanguageGroup for glyph. */
static void setLanguageGroup(t1rCtx h,
                             abfGlyphInfo *info, unsigned char iFD, long cid) {
    if (iFD >= h->fdicts.cnt)
        fatal(h, t1rErrFDRange, "invalid FD index CID-%ld", cid);
    else if (h->fdicts.array[iFD].Private.LanguageGroup == 1)
        info->flags |= ABF_GLYPH_LANG_1;
}

/* Save CID download format char. */
static void cidSaveChar(t1rCtx h, long len, char *cstr, long cid) {
    unsigned char iFD;
    unsigned short tag = (unsigned short)h->chars.index.cnt;
    Char *chr = dnaNEXT(h->chars.index);

    /* Handle fd index byte */
    if (h->key.FDBytes == 0)
        iFD = 0;
    else {
        iFD = (unsigned char)cstr[0];
        cstr++;
        len--;
    }

    if (cid < 0 || cid >= h->top.cid.CIDCount)
        fatal(h, t1rErrCIDRange, NULL);

    /* Initialize char */
    abfInitGlyphInfo(chr);
    chr->flags = ABF_GLYPH_CID;
    chr->tag = tag;
    chr->cid = (unsigned short)cid;
    chr->iFD = iFD;
    setLanguageGroup(h, chr, iFD, cid);

    /* Save charstring */
    saveCstr(h, len, cstr, chr, 1);
}

/* Parse GlyphDirectory in download CID format (unpublished). */
static void parseGlyphDirectory(t1rCtx h) {
    char *cstr;
    pstToken *token;
    int state;
    int tokenType = pstHexString;
    long cid = 0; /* Suppress optimizer warning */
    long len = 0; /* Suppress optimizer warning */

    h->chars.index.cnt = 0;
    saveStdSubrs(h);

    /* Search for glyph data start */
    state = 0;
    for (;;) {
        token = getToken(h);
        switch (state) {
            case 0:
                if (token->type == pstInteger) {
                    cid = pstConvInteger(h->pst, token);
                    state = 1;
                }
                break;
            case 1:
                switch (token->type) {
                    case pstInteger:
                        len = pstConvInteger(h->pst, token);
                        state = 2;
                        break;
                    case pstASCII85:
                        tokenType = pstASCII85;
                        /* Fall through */
                    case pstHexString:
                        goto parse_hex_fmt;
                    default:
                        state = 0;
                        break;
                }
                break;
            case 2:
                if (pstMatch(h->pst, token, ":"))
                    goto parse_bin_fmt;
                state = 0;
        }
    }

    /* Hexadecimal charstring parse loop */
parse_hex_fmt:
    for (;;) {
        /* Convert hex string to binary */
        if (tokenType == pstASCII85) {
            cstr = token->value + 2;
            len = convASCII85Cstr(h, cstr);
        } else {
            cstr = token->value + 1;
            len = convASCIIHexCstr(h, token->length - 2, cstr);
        }
        if (len == 0)
            goto bad_char;

        cidSaveChar(h, len, cstr, cid);

        /* Parse terminating operator */
        if (!pstMatch(h->pst, getToken(h), "|"))
            goto bad_char;

        /* Parse CID token */
        token = getToken(h);
        if (token->type != pstInteger)
            return;
        cid = pstConvInteger(h->pst, token);

        /* Parse cstr */
        token = getToken(h);
        if (token->type != tokenType)
            goto bad_char;
    }

    /* Binary charstring parse loop */
parse_bin_fmt:
    for (;;) {
        int result = pstRead(h->pst, len + 1, &cstr);
        if (result)
            pstFatal(h, result);

        cidSaveChar(h, len, cstr + 1, cid);

        /* Parse NP operator */
        if (!pstMatch(h->pst, getToken(h), "|"))
            goto bad_char;

        /* Parse CID token */
        token = getToken(h);
        if (token->type != pstInteger)
            return;
        cid = pstConvInteger(h->pst, token);

        /* Parse cstr length */
        token = getToken(h);
        if (token->type != pstInteger ||
            (len = pstConvInteger(h->pst, token), len < 1 || len > 65535))
            goto bad_char;

        /* Parse RD operator */
        if (!pstMatch(h->pst, getToken(h), ":"))
            goto bad_char;
    }

bad_char:
    fatal(h, t1rErrCharEntry, "invalid char entry CID-%ld", cid);
}

/* Fill source buffer. */
static void fillbuf(t1rCtx h, long offset) {
    h->src.length = h->cb.stm.read(&h->cb.stm, h->stm.src, &h->src.buf);
    if (h->src.length == 0)
        fatal(h, t1rErrSrcStream, NULL);
    h->src.offset = offset;
    h->src.next = h->src.buf;
    h->src.end = h->src.buf + h->src.length;
}

/* Refill source buffer. */
static char nextbuf(t1rCtx h) {
    /* 64-bit warning fixed by cast here HO */
    fillbuf(h, (long)(h->src.offset + h->src.length));
    return *h->src.next++;
}

/* Seek to specified offset. */
static void srcSeek(t1rCtx h, long offset) {
    long delta = offset - h->src.offset;
    if (delta >= 0 && (size_t)delta < h->src.length)
        /* Offset within current buffer; reposition next byte */
        h->src.next = h->src.buf + delta;
    else {
        /* Offset outside current buffer; seek to offset and fill buffer */
        if (h->cb.stm.seek(&h->cb.stm, h->stm.src, offset))
            fatal(h, t1rErrSrcStream, NULL);
        fillbuf(h, offset);
    }
}

/* Read charstring from source stream. */
static char *readCstr(t1rCtx h, long begin, long end) {
    char *p;
    long left;
    long length = end - begin;

    srcSeek(h, begin);

    /* 64-bit warning fixed by cast here HO */
    left = (long)(h->src.end - h->src.next);
    if (left >= length) {
        /* Source buffer contains charstring; return it */
        p = h->src.next;
        h->src.next += length;
        return p;
    } else {
        /* Charstring spans source buffers; make copy */
        dnaSET_CNT(h->tmp, length);

        p = h->tmp.array;
        do {
            /* Copy buffer */
            memcpy(p, h->src.next, left);
            p += left;
            length -= left;

            /* Refill buffer */
            /* 64-bit warning fixed by cast here HO */
            fillbuf(h, (long)(h->src.offset + h->src.length));
            /* 64-bit warning fixed by cast here HO */
            left = (long)h->src.length;
        } while (left < length);

        memcpy(p, h->src.next, length);
        h->src.next += length;

        return h->tmp.array;
    }
}

/* Read 1-byte unsigned number. */
#define read1(h) \
    ((unsigned char)((h->src.next == h->src.end) ? nextbuf(h) : *h->src.next++))

/* Read CIDMap or SubrMap offset. */
static long readN(t1rCtx h, int N) {
    unsigned long value = 0;
    switch (N) {
        case 4:
            value = read1(h);
        case 3:
            value = value << 8 | read1(h);
        case 2:
            value = value << 8 | read1(h);
        case 1:
            value = value << 8 | read1(h);
    }
    return value;
}

/* Read CIDMap. */
static void readCIDMap(t1rCtx h,
                       long StartDataOffset, long length, long *maxoff) {
    long cid;
    long tag;
    unsigned char fd;
    long offset;

    /* Set chars array size */
    dnaSET_CNT(h->chars.index, h->top.cid.CIDCount);

    /* Validate */
    if (h->key.CIDMapOffset == -1)
        fatal(h, t1rErrCIDMap, NULL);

    if (h->key.GDBytes < 1 || h->key.GDBytes > 4)
        badKeyValue(h, kGDBytes);

    /* Read to CIDMap */
    srcSeek(h, StartDataOffset + h->key.CIDMapOffset);
    tag = 0;
    fd = (h->key.FDBytes == 0) ? 0 : read1(h);
    offset = readN(h, h->key.GDBytes);
    for (cid = 0; cid < h->chars.index.cnt; cid++) {
        long length;
        unsigned char nextfd = (h->key.FDBytes == 0) ? 0 : read1(h);
        long nextoff = readN(h, h->key.GDBytes);
        if (offset != nextoff) {
            Char *chr = &h->chars.index.array[tag];

            /* Initialize char */
            abfInitGlyphInfo(chr);
            chr->flags = ABF_GLYPH_CID;
            chr->tag = (unsigned short)tag++;
            chr->cid = (unsigned short)cid;
            chr->iFD = fd;
            chr->sup.begin = StartDataOffset + offset;
            chr->sup.end = StartDataOffset + nextoff;

            setLanguageGroup(h, chr, fd, cid);

            length = chr->sup.end - chr->sup.begin;
            if (length < 0 || length > 65535)
                fatal(h, t1rErrCharLength,
                      "bad charstring length <cid-%ld>", cid);
        }
        fd = nextfd;
        offset = nextoff;
    }
    h->chars.index.cnt = tag;

    if (h->chars.index.array[h->chars.index.cnt - 1].cid + 1 !=
        h->top.cid.CIDCount)
        message(h, "/CIDCount too big (ignored)");

    if (h->chars.index.array[0].cid != 0)
        /* CID 0 (notdef) missing. I would have liked to insert the missing
           character but this is too complicated cid-keyed fonts to be
           worthwhile for such a rare case so I just report a fatal error */
        fatal(h, t1rErrNoCID0, NULL);

    if (offset > *maxoff)
        *maxoff = offset;
}

/* Read SubrMaps and subr charstrings. */
static void readSubrMaps(t1rCtx h, long StartDataOffset, long *maxoff) {
    long i;
    long j;

    for (i = 0; i < h->FDArray.cnt; i++) {
        FDInfo *fd = &h->FDArray.array[i];

        /* Grow buffer to accomodate SubrCount+1 subr offset */
        (void)dnaGROW(fd->subrs.offset, fd->key.SubrCount);
        fd->subrs.offset.cnt = fd->key.SubrCount;
        if (fd->subrs.offset.cnt == 0)
            continue; /* No subrs */

        /* Validate */
        if (fd->key.SubrMapOffset == -1)
            fatal(h, t1rErrSubrMap, "/SubrMapOffset missing: FD[%d]", i);

        if (fd->key.SDBytes < 1 || fd->key.SDBytes > 4)
            fatal(h, t1rErrKeyValue, "/SDBytes: bad value: FD[%d]", i);

        /* Read SubrMap */
        srcSeek(h, StartDataOffset + fd->key.SubrMapOffset);
        for (j = 0; j <= fd->subrs.offset.cnt; j++)
            fd->subrs.offset.array[j] =
                StartDataOffset + readN(h, fd->key.SDBytes);
    }

    /* Read, decrypt, and copy charstrings to tmp file. */
    for (i = 0; i < h->FDArray.cnt; i++) {
        FDInfo *fd = &h->FDArray.array[i];
        long offset =
            fd->subrs.offset.array[fd->subrs.offset.cnt] - StartDataOffset;
        if (offset > *maxoff)
            *maxoff = offset;
        fd->subrs.region.begin = h->tmpoff;
        for (j = 0; j < fd->subrs.offset.cnt; j++) {
            char *cstr;
            long begin = fd->subrs.offset.array[j];
            long end = fd->subrs.offset.array[j + 1];
            long length = end - begin;

            if (length < 1 || length > 65535)
                fatal(h, t1rErrKeyValue,
                      "bad subr length FD[%d].subr[%ld]", i, j);

            cstr = readCstr(h, begin, end);

            if (fd->key.lenIV != -1 && cstr != h->tmp.array) {
                /* Make copy of charstring. A copy is need here because
                   saveSubr() will perform in-place decryption of the
                   charstring and it is possible the the same charstring is
                   referenced from other FDs and would therefore be decrypted a
                   second time causing failure. */
                dnaSET_CNT(h->tmp, length);
                memcpy(h->tmp.array, cstr, length);
                cstr = h->tmp.array;
            }

            fd->subrs.offset.array[j] = saveSubr(h, length, cstr, i, j);
        }

        fd->subrs.region.end = h->tmpoff;
    }
}

/* Seen first token of possible StartData sequence. Try to match the rest.
   Return NULL on match else non-matching token. */
static pstToken *cidRead(t1rCtx h, int binary) {
    pstToken *token;
    long maxoff;
    long length;
    long StartDataOffset;

    /* Match length */
    token = getToken(h);
    if (token->type != pstInteger)
        return token;
    length = pstConvInteger(h->pst, token);

    /* Match StartData token */
    token = getToken(h);
    if (!pstMatch(h->pst, token, "StartData"))
        return token;

    if (!binary)
        fatal(h, t1rErrStartData, NULL);

    /* Matched: (Binary) <length> StartData; validate keys */
    if (h->key.FDBytes != 1 && h->key.FDBytes != 0)
        badKeyValue(h, kFDBytes);

    /* Hijack source stream from pstoken lib */
    h->stm.src = pstHijackStream(h->pst, &StartDataOffset);
    if (h->stm.src == NULL)
        fatal(h, t1rErrSrcStream, NULL);

    /* Initialize source read */
    h->src.offset = 0;
    h->src.length = 0;

    /* Skip separator byte */
    StartDataOffset++;

    maxoff = 0;
    readCIDMap(h, StartDataOffset, length, &maxoff);
    readSubrMaps(h, StartDataOffset, &maxoff);

    if (maxoff != length) {
        long diff = length - maxoff;
        if (diff > 0)
            message(h,
                    "StartData length is %ld bytes too long (ignored)", diff);
        else
            message(h,
                    "StartData length is %ld bytes too short (ignored)", -diff);
    }

    h->flags |= SEEN_END;

    return NULL;
}

/* Add string pointer from string index. */
static void addStrPtr(t1rCtx h, abfString *str) {
    if (str->impl != ABF_UNSET_INT)
        str->ptr = getString(h, (STI)str->impl);
}

/* Return the offset of a standard encoded glyph of -1 if none. */
static long getStdEncGlyphOffset(void *ctx, int stdcode) {
    t1rCtx h = ctx;
    unsigned short tag = h->encoding.standard[stdcode];
    return (tag == 0xffff) ? -1 : h->chars.index.array[tag].sup.begin;
}

/* Report absfont error message to debug stream. */
static void report_error(abfErrCallbacks *cb, int err_code, int iFD) {
    t1rCtx h = cb->ctx;
    if (iFD == -1)
        message(h, "%s (ignored)", abfErrStr(err_code));
    else
        message(h, "%s FD[%d] (ignored)", abfErrStr(err_code), iFD);
}

/* Prepare font data for client. */
static void prepClientData(t1rCtx h) {
    long i;

    if (h->flags & SYN_FONT) {
        /* Synthetic font; save base FontName */
        h->top.SynBaseFontName = h->fd->fdict->FontName;

        /* Restore synthetic font values */
        h->fd->fdict->FontName = h->synthetic.FontName;
        h->top.FullName = h->synthetic.FullName;
        h->top.ItalicAngle = h->synthetic.ItalicAngle;
        h->top.UniqueID = h->synthetic.UniqueID;
        h->fd->fdict->FontMatrix = h->synthetic.FontMatrix;
        h->top.sup.flags |= ABF_SYN_FONT;
    }
    if (h->flags & MM_FONT)
        prepMMData(h);

    /* Add strings to top dict */
    addStrPtr(h, &h->top.version);
    addStrPtr(h, &h->top.Notice);
    addStrPtr(h, &h->top.Copyright);
    addStrPtr(h, &h->top.FullName);
    addStrPtr(h, &h->top.FamilyName);
    addStrPtr(h, &h->top.Weight);
    addStrPtr(h, &h->top.PostScript);
    addStrPtr(h, &h->top.BaseFontName);
    addStrPtr(h, &h->top.SynBaseFontName);
    addStrPtr(h, &h->top.cid.CIDFontName);
    addStrPtr(h, &h->top.cid.Registry);
    addStrPtr(h, &h->top.cid.Ordering);

    /* Add strings to font dicts */
    for (i = 0; i < h->fdicts.cnt; i++) {
        abfFontDict *font = &h->fdicts.array[i];
        addStrPtr(h, &font->FontName);
    }

    /* Prepare auxiliary data */
    for (i = 0; i < h->FDArray.cnt; i++) {
        FDInfo *fd = &h->FDArray.array[i];
        fd->aux.flags = 0;
        if (h->flags & T1R_UPDATE_OPS)
            fd->aux.flags |= T1C_UPDATE_OPS;
        fd->aux.src = h->stm.tmp;
        fd->aux.subrs.cnt = fd->subrs.offset.cnt;
        fd->aux.subrs.offset = fd->subrs.offset.array;
        fd->aux.subrsEnd = fd->subrs.region.end;
        fd->aux.stm = &h->cb.stm;
        fd->aux.ctx = h;
        fd->aux.getStdEncGlyphOffset = getStdEncGlyphOffset;
        if (h->flags & T1R_USE_MATRIX) {
            abfFontMatrix *FontMatrix = &h->fdicts.array[i].FontMatrix;

            if (FontMatrix->cnt != ABF_EMPTY_ARRAY) {
                /* Prepare transformation matrix */
                int j;
                for (j = 0; j < 6; j++)
                    fd->aux.matrix[j] =
                        FontMatrix->array[j] * h->top.sup.UnitsPerEm;
                fd->aux.flags |= T1C_USE_MATRIX;
            }
        }
        if (!(h->flags & MM_FONT))
            fd->aux.nMasters = 1;
        if (!(fd->key.BlueValues))
            message(h, "/BlueValues missing: FD[%ld]", i);
    }

    h->top.sup.nGlyphs = h->chars.index.cnt;
    h->top.FDArray.cnt = h->fdicts.cnt;
    h->top.FDArray.array = h->fdicts.array;

    /* Validate dictionaries */
    if (h->stm.dbg == NULL)
        abfCheckAllDicts(NULL, &h->top);
    else {
        abfErrCallbacks cb;
        cb.ctx = h;
        cb.report_error = report_error;
        abfCheckAllDicts(&cb, &h->top);
    }

    if (h->flags & CID_FONT) {
        /* CID-keyed font */
        h->top.sup.flags |= ABF_CID_FONT;
        h->top.sup.srcFontType = abfSrcFontTypeType1CID;

        /* We pre-multiplied the FontMatrix's in the FDArray with the one in
           the top dict FontMatrix so we can discard it */
        h->top.cid.FontMatrix.cnt = ABF_EMPTY_ARRAY;
        return;
    }

    /* Name-keyed font; set source font type */
    h->top.sup.srcFontType = abfSrcFontTypeType1Name;

    /* Add strings to glyphs */
    for (i = 0; i < h->chars.index.cnt; i++)
        addStrPtr(h, &h->chars.index.array[i].gname);

    /* Add glyph encodings */
    if (h->flags & STD_ENC) {
        /* Standard encoding */
        for (i = 0; i < h->chars.index.cnt; i++) {
            Char *chr = &h->chars.index.array[i];
            int code = getStdEnc(h, (STI)chr->gname.impl);
            if (code != -1)
                encAdd(h, chr, code);
        }
    } else {
        /* Custom encoding */
        for (i = 0; i < 256; i++) {
            STI sti = h->encoding.custom[i];
            if (sti != STI_UNDEF) {
                Char *chr = findChar(h, sti);
                if (chr != NULL)
                    /* Char exists; add encoding */
                    encAdd(h, chr, i);
            }
        }
    }
}

/* Parse PostScript font. */
int t1rBegFont(t1rCtx h, long flags, long origin, abfTopDict **top, float *UDV) {
    int result;

    /* Set error handler */
    DURING_EX(h->err.env)

    /* Initialize */
    h->flags = flags & 0xffff;
    abfInitTopDict(&h->top);
    dnaSET_CNT(h->FDArray, 1);
    dnaSET_CNT(h->fdicts, 1);
    initFDInfo(h, 0);
    h->fd = &h->FDArray.array[0];
    initChars(h);
    initStrings(h);
    initKeySeen(h);
    h->key.CIDMapOffset = -1;
    h->key.FDBytes = (unsigned)-1;
    h->key.GDBytes = (unsigned)-1;
    memset(h->encoding.standard, 0xff, sizeof(h->encoding.standard));
    h->mm.UDV = UDV;
    h->mm.WV.cnt = 0;
    h->mm.BDP.cnt = 0;
    h->mm.BDM.cnt = 0;
    h->mm.ForceBoldThreshold = 0.5;
    tmpInit(h);

    /* Begin new parse */
    result = pstBegParse(h->pst, origin);
    if (result)
        pstFatal(h, result);
    if (flags & T1R_DUMP_TOKENS)
        pstSetDumpLevel(h->pst, 1);

    /* Parse font */
    do {
        pstToken *token = getToken(h);
    retry:
        switch (token->type) {
            case pstLiteral:
                doLiteral(h, token);
                break;
            case pstOperator:
                doOperator(h, token);
                break;
            case pstString: {
                int binary;

                /* Try to match possible StartData sequence */
                if (pstMatch(h->pst, token, "(Hex)"))
                    binary = 0;
                else if (pstMatch(h->pst, token, "(Binary)"))
                    binary = 1;
                else
                    break; /* No match */

                token = cidRead(h, binary);
                if (token != NULL)
                    goto retry; /* No match */
            } break;
            default:
                break;
        }
    } while (!(h->flags & SEEN_END));

    prepClientData(h);
    *top = &h->top;

    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* End PostScript font parse. */
int t1rEndFont(t1rCtx h) {
    int result = pstEndParse(h->pst);
    if (result) {
        message(h, "(pst) %s", pstErrStr(result));
        message(h, "%s", t1rErrStr(t1rErrPostScript));
        return t1rErrPostScript;
    }
    encListReuse(h);
    return t1rSuccess;
}

/* Read charstring. */
static void readSubr(t1rCtx h, abfGlyphCallbacks *glyph_cb, Offset subrStartOffset, Offset subrEndOffset, abfGlyphInfo *glyph_info, t1cAuxData *glyph_aux) {
    int result;
    long offset;
    Char chr = *glyph_info;
    t1cAuxData aux = *glyph_aux;
    offset = chr.sup.begin = subrStartOffset;
    chr.sup.end = subrEndOffset;
    chr.flags &= ~ABF_GLYPH_SEEN;
    chr.flags |= ABF_GLYPH_CUBE_GSUBR;
    aux.flags |= T1C_CUBE_GSUBR;

    result = glyph_cb->beg(glyph_cb, &chr);

    /* Check result */
    switch (result) {
        case ABF_CONT_RET:
            aux.flags &= ~T1C_WIDTH_ONLY;
            break;
        case ABF_WIDTH_RET:
            aux.flags |= T1C_WIDTH_ONLY;
            break;
        case ABF_SKIP_RET:
            return;
        case ABF_QUIT_RET:
            fatal(h, t1rErrCstrQuit, NULL);
        case ABF_FAIL_RET:
            fatal(h, t1rErrCstrFail, NULL);
    }

    if (h->flags & T1R_IS_CUBE)
        aux.flags |= T1C_IS_CUBE;

    /* Parse charstring */
    result = t1cParse(offset, &aux, glyph_cb);
    if (result) {
        if (chr.flags & ABF_GLYPH_CID)
            message(h, "(t1c) %s <cid-%hu>", t1cErrStr(result), chr.cid);
        else
            message(h, "(t1c) %s <%s>", t1cErrStr(result), chr.gname.ptr);
        fatal(h, t1rErrCstrParse, NULL);
    }

    /* End glyph */
    glyph_cb->end(glyph_cb);
}

/* Read charstring. */
static void readGlyph(t1rCtx h,
                      unsigned short tag, abfGlyphCallbacks *glyph_cb) {
    int result;
    long offset;
    long flags = h->flags;
    Char *chr = &h->chars.index.array[tag];
    t1cAuxData *aux = &h->FDArray.array[chr->iFD].aux;

    if ((flags & CID_FONT) && !(flags & PRINT_STREAM)) {
        /* Unlike a name-keyed font the charstrings in a cid-keyed font aren't
           processed until the client requests a particular glyph via this
           function. In order to conserve space charstrings are normally
           overwritten because they are decrypted and saved to the same
           position in the tmp stream. However, the client can preserve them
           by setting the T1R_KEEP_CID_CSTRS flag. The following code handles
           this functionality. */
        char *cstr = readCstr(h, chr->sup.begin, chr->sup.end);
        long length = chr->sup.end - chr->sup.begin;

        offset = h->tmpoff;
        if (flags & T1R_KEEP_CID_CSTRS) {
            /* Preserve charstrings */
            saveCstr(h, length, cstr, chr, 1);
            result = glyph_cb->beg(glyph_cb, chr);
        } else {
            /* Overwrite charstrings */
            long saveoff = chr->sup.begin;

            tmpSeek(h, offset);
            saveCstr(h, length, cstr, chr, 0);
            h->tmpoff = offset;

            /* Call client with temporary offset */
            chr->sup.begin = offset;
            chr->sup.end = chr->sup.begin + length;
            result = glyph_cb->beg(glyph_cb, chr);
            chr->sup.begin = saveoff;
            chr->sup.end = chr->sup.begin + length;
        }
    } else {
        offset = chr->sup.begin;
        result = glyph_cb->beg(glyph_cb, chr);
    }

    /* Mark glyph as seen */
    chr->flags |= ABF_GLYPH_SEEN;

    /* Check result */
    switch (result) {
        case ABF_CONT_RET:
            aux->flags &= ~T1C_WIDTH_ONLY;
            break;
        case ABF_WIDTH_RET:
            aux->flags |= T1C_WIDTH_ONLY;
            break;
        case ABF_SKIP_RET:
            return;
        case ABF_QUIT_RET:
            fatal(h, t1rErrCstrQuit, NULL);
        case ABF_FAIL_RET:
            fatal(h, t1rErrCstrFail, NULL);
    }

    if (flags & T1R_IS_CUBE)
        aux->flags |= T1C_IS_CUBE;
    if (flags & T1R_FLATTEN_CUBE)
        aux->flags |= T1C_FLATTEN_CUBE;

    /* Parse charstring */
    result = t1cParse(offset, aux, glyph_cb);
    if (result) {
        if (chr->flags & ABF_GLYPH_CID)
            message(h, "(t1c) %s <cid-%hu>", t1cErrStr(result), chr->cid);
        else
            message(h, "(t1c) %s <%s>", t1cErrStr(result), chr->gname.ptr);
        fatal(h, t1rErrCstrParse, NULL);
    }

    /* End glyph */
    glyph_cb->end(glyph_cb);

    /* if it is a CUBE font, and we have the call backs to do so, play the SUBR's through to the client */
    if ((flags & T1R_IS_CUBE) && !(glyph_cb->cubeSetwv == NULL) && !(flags & T1R_FLATTEN_CUBE) && !(flags & T1R_SEEN_GLYPH) && (aux->subrs.cnt > 10)) {
        int i;
        Offset subrStartOffset = 0;
        Offset subrEndOffset;
        h->flags |= T1R_SEEN_GLYPH;
        for (i = 0; i < aux->subrs.cnt; i++) {
            subrStartOffset = aux->subrs.offset[i];
            if ((i + 1) < aux->subrs.cnt)
                subrEndOffset = aux->subrs.offset[i + 1];
            else
                subrEndOffset = aux->subrsEnd;
            readSubr(h, glyph_cb, subrStartOffset, subrEndOffset, chr, aux);
        }
    }
}

/* Iterate through all glyphs in font. */
int t1rIterateGlyphs(t1rCtx h, abfGlyphCallbacks *glyph_cb) {
    long i;

    /* Set error handler */
    DURING_EX(h->err.env)

    for (i = 0; i < h->chars.index.cnt; i++)
        readGlyph(h, (unsigned short)i, glyph_cb);

    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* Get glyph from font by its tag. */
int t1rGetGlyphByTag(t1rCtx h,
                     unsigned short tag, abfGlyphCallbacks *glyph_cb) {
    if (tag >= h->chars.index.cnt)
        return t1rErrNoGlyph;

    /* Set error handler */
    DURING_EX(h->err.env)
    readGlyph(h, tag, glyph_cb);
    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* Match glyph name after font fully parsed. */
static int CTL_CDECL postMatchChar(const void *key, const void *value,
                                   void *ctx) {
    t1rCtx h = ctx;
    return strcmp((char *)key,
                  h->chars.index.array[*(long *)value].gname.ptr);
}

/* Get glyph from font by its name. */
int t1rGetGlyphByName(t1rCtx h, char *gname, abfGlyphCallbacks *glyph_cb) {
    size_t index;

    if (h->flags & CID_FONT)
        return t1rErrNoGlyph;

    if (!ctuLookup(gname, h->chars.byName.array, h->chars.byName.cnt,
                   sizeof(h->chars.byName.array[0]), postMatchChar, &index, h))
        return t1rErrNoGlyph;

    /* Set error handler */
    DURING_EX(h->err.env)

    readGlyph(h, (unsigned short)h->chars.byName.array[index], glyph_cb);

    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* Match CID value. */
static int CTL_CDECL matchCID(const void *key, const void *value) {
    unsigned short a = *(unsigned short *)key;
    unsigned short b = ((Char *)value)->cid;
    if (a < b)
        return -1;
    else if (a > b)
        return 1;
    else
        return 0;
}

/* Get glyph from font by its CID. */
int t1rGetGlyphByCID(t1rCtx h,
                     unsigned short cid, abfGlyphCallbacks *glyph_cb) {
    volatile unsigned short tag; /* volatile suppresses optimizer warning */

    if (!(h->flags & CID_FONT))
        return t1rErrNoGlyph;

    if (h->chars.index.array[h->chars.index.cnt - 1].cid ==
        h->chars.index.cnt - 1) {
        /* Non-subset font; index by CID */
        if (cid >= h->chars.index.cnt)
            return t1rErrNoGlyph;
        else
            tag = cid;
    } else {
        /* Subset font; search for matching CID */
        Char *chr =
            (Char *)bsearch(&cid, h->chars.index.array,
                            h->chars.index.cnt, sizeof(Char), matchCID);
        if (chr == NULL)
            return t1rErrNoGlyph;
        else
            /* 64-bit warning fixed by cast here HO */
            tag = (unsigned short)(chr - h->chars.index.array);
    }

    /* Set error handler */
    DURING_EX(h->err.env)

    readGlyph(h, tag, glyph_cb);

    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* Get standard-encoded glyph from font. */
int t1rGetGlyphByStdEnc(t1rCtx h, int stdcode, abfGlyphCallbacks *glyph_cb) {
    unsigned short tag = 0; /* Suppress optimizer warning */

    if (stdcode < 0 || stdcode >= 256 ||
        (tag = h->encoding.standard[stdcode]) == 0xffff)
        return t1rErrNoGlyph;

    /* Set error handler */
    DURING_EX(h->err.env)

    readGlyph(h, tag, glyph_cb);

    HANDLER
    return Exception.Code;
    END_HANDLER

    return t1rSuccess;
}

/* Clear record of glyphs seen by client. */
int t1rResetGlyphs(t1rCtx h) {
    long i;

    for (i = 0; i < h->chars.index.cnt; i++)
        h->chars.index.array[i].flags &= ~ABF_GLYPH_SEEN;

    if (h->top.sup.flags & ABF_CID_FONT)
    {
        /* If the font is a Type1 CID font, the charstrings must be read
         when building a subset, in order to get the FD index. When a CID
         charstring is read, it is decrypted in place in the src buffer.
         If the total charstrings are smaller than the src buffer, then
         when the glyph data is later read again when the subset is
         applied, the charstring data is just copied from the buffer.
         However, they are now decrypted, leading to parsing failure. Set
         the buffer length to 0 so that the src charstring data will get
         reloaded again. */
        h->src.offset = 0;
        h->src.length = 0;
    }

    return t1rSuccess;
}

/* Return Weight Vector. */
const float *t1rGetWV(t1rCtx h, long *nMasters) {
    if (h->fd->aux.nMasters == 1) {
        *nMasters = 0;
        return NULL;
    } else {
        *nMasters = h->fd->aux.nMasters;
        return h->fd->aux.WV;
    }
}

/* Get subrs info. */
const ctlSubrs *t1rGetSubrs(t1rCtx h, int iFD, const ctlRegion **region) {
    if (iFD < 0 || iFD >= h->FDArray.cnt) {
        *region = NULL;
        return NULL;
    } else {
        FDInfo *fd = &h->FDArray.array[iFD];
        *region = &fd->subrs.region;
        return &fd->aux.subrs;
    }
}

/* Get version numbers of libraries. */
void t1rGetVersion(ctlVersionCallbacks *cb) {
    if (cb->called & 1 << T1R_LIB_ID)
        return; /* Already enumerated */

    /* Support libraries */
    abfGetVersion(cb);
    ctuGetVersion(cb);
    dnaGetVersion(cb);
    pstGetVersion(cb);
    t1cGetVersion(cb);

    /* This library */
    cb->getversion(cb, T1R_VERSION, "t1read");

    /* Record this call */
    cb->called |= 1 << T1R_LIB_ID;
}

/* ----------------------------- Error Support ----------------------------- */

/* Map error code to error string. */
char *t1rErrStr(int err_code) {
    static char *errstrs[] =
        {
#undef CTL_DCL_ERR
#define CTL_DCL_ERR(name, string) string,
#include "t1rerr.h"
        };
    return (err_code < 0 || err_code >= (int)ARRAY_LEN(errstrs)) ? "unknown error" : errstrs[err_code];
}

/* ----------------------------- Debug Support ----------------------------- */

#if T1R_DEBUG

/* Dump string table. */
static void dbstrs(t1rCtx h) {
    long i;
    printf("--- strings [index]=<string>\n");
    for (i = 0; i < h->strings.index.cnt; i++)
        printf("[%03ld]=<%s>\n", i, getString(h, (STI)i));
}

/* Dump char table. */
static void dbchars(t1rCtx h) {
    long i;
    if (h->flags & CID_FONT)
        printf("--- chars [tag]={offset,cid,iFD}\n");
    else
        printf("--- chars [tag]={offset,code[+code]+,gname}\n");
    for (i = 0; i < h->chars.index.cnt; i++) {
        Char *chr = &h->chars.index.array[i];

        printf("[%05hu]={%05ld,", chr->tag, chr->sup.begin);
        if (chr->flags & ABF_GLYPH_CID)
            ;
        else {
            /* Print encoding */
            abfEncoding *enc = &chr->encoding;
            if (enc->code == ABF_GLYPH_UNENC)
                printf("-");
            else {
                printf("0x%02lx", enc->code);
                for (;;) {
                    enc = enc->next;
                    if (enc == NULL)
                        break;
                    printf("+0x%02lx", enc->code);
                }
            }
        }

        /* Name-keyed glyph; print gname */
        if (chr->gname.impl == ABF_UNSET_INT)
            printf(",-}\n");
        else
            printf(",%s}\n", getString(h, (STI)chr->gname.impl));
    }
}

/* Dump encoding. */
static void dbencs(t1rCtx h) {
    printf("--- encoding\n");
    if (h->flags & STD_ENC)
        printf("StandardEncoding\n");
    else {
        int i;
        for (i = 0; i < 256; i++) {
            STI sti = h->encoding.custom[i];
            printf("[%d]=<%s> ", i,
                   (sti == STI_UNDEF) ? "-unenc-" : getString(h, sti));
        }
        printf("\n");
    }
}

/* This function just serves to suppress annoying "defined but not used"
   compiler messages when debugging. */
static void CTL_CDECL dbuse(int arg, ...) {
    dbuse(0, dbstrs, dbchars, dbencs);
}

#endif /* T1R_DEBUG */
