/* 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. */
/***********************************************************************/

/* Font conversion database manager */

#include "package.h"
#include DYNARR
#include HOTCONV

#include "lstdio.h"
#include "lstring.h"
#include "lctype.h"
#include "lstdlib.h"

#include "fcdb.h"
#include "file.h"
#include "ctutil.h"

/* Keyword ids */
enum {
    kFamily,         /* "f", "family" */
    kSubfamily,      /* "s", "subfamily" */
    kCompatible,     /* "l", "compatible" */
    kCompatibleFull, /* "m", "Mac compatible full " */
    kOldCompatible,  /* "c", old " compatible", FDK 2.0 and earlier */
    kBold,           /* "b", "bold" */
    kItalic,         /* "i", "italic" */
    kBoldItalic,     /* "t", "bolditalic" */
    kMacEnc,         /* "macenc" */
    kItalicangle,
    kWeight,
    kWidth,
    kWPFFamily,
    kWPFStyle,
    kUnknown
};

/* Font reference */
typedef struct {
    long iFontName; /* FontName string index */
    long location;  /* b31-24: file id, b23-0: file offset */
    long length;    /* Record length (excluding [FontName]) */
    long line;      /* Line number of record key */
} FontRef;

/* Module context */
struct fcdbCtx_ {
    dnaDCL(FontRef, refs);        /* Font record list */
    dnaDCL(char, FontNames);      /* Concatenated null-terminated FontNames */
    dnaDCL(char, record);         /* Record buffer */
    char *MatchName;              /* For use with bsearch lookup */
    fcdbCallbacks cb;             /* Client callbacks */
    unsigned fileid;              /* Current file being parsed */
    long line;                    /* Current line being parsed */
    unsigned short syntaxVersion; /* used by fcdb call backs. The cbCtx call backs need another copy of this */
};

/* Match FontName key */
static int CDECL matchFontName(const void *key, const void *value) {
    const struct fcdbCtx_ *h = key;
    return strcmp(h->MatchName,
                  &h->FontNames.array[((FontRef *)value)->iFontName]);
}

/* sort FontNamse key */
static int CDECL cmpFontName(const void *first, const void *second, void *ctx) {
    const struct fcdbCtx_ *h = ctx;
    return strcmp(&h->FontNames.array[((FontRef *)first)->iFontName],
                  &h->FontNames.array[((FontRef *)second)->iFontName]);
}

/* Report parsing error */
static void rptError(fcdbCtx h, int errid) {
    h->cb.error(h->cb.ctx, h->fileid, h->line, errid);
}

/* Add new database file */
void fcdbAddFile(fcdbCtx h, unsigned fileid, void *callBackCtx) {
    unsigned short syntaxVersion = 0; /* 0 means not yet set. Inidcates which version is being used. */

    /* Next state table */
    static unsigned char next[10][9] = {
        /*  blank     \n      \r       [       ]       *       c       =     EOF    index */
        /* -------- ------- ------- ------- ------- ------- -------- -----  -----   ----- */
        {     0,       0,      2,      3,      1,      1,      9,      1,     0},  /* [0] */
        {     1,       0,      2,      1,      1,      1,      1,      1,     0},  /* [1] */
        {     0,       0,      2,      3,      1,      1,      1,      1,     0},  /* [2] */
        {     4,       0,      2,      1,      1,      5,      5,      5,     0},  /* [3] */
        {     4,       0,      2,      1,      1,      5,      5,      5,     0},  /* [4] */
        {     6,       0,      2,      1,      7,      5,      5,      5,     0},  /* [5] */
        {     6,       0,      2,      1,      7,      1,      1,      1,     0},  /* [6] */
        {     8,       0,      2,      1,      1,      1,      1,      1,     0},  /* [7] */
        {     8,       0,      2,      1,      1,      1,      1,      1,     0},  /* [8] */
        {     1,       0,      2,      1,      1,      1,      1,      1,     0},  /* [9] */
    };

    /* Action table */

#define I_ (1 << 0) /* Increment line counter */
#define E_ (1 << 1) /* Report sytax error */
#define A_ (1 << 2) /* Accumulate key */
#define S_ (1 << 3) /* Save key */
#define O_ (1 << 4) /* Open square bracket; save length & reset key index */
#define C_ (1 << 5) /* Close square bracket; record offset */
#define Q_ (1 << 6) /* Quit on EOF */
#define M_ (1 << 7) /* Mark as having seen an old "c=" sequence, so that we will build the name table as FDK's did up through 2.0. */

    static unsigned char action[10][9] = {
        /*  blank     \n      \r       [       ]       *       c       =     EOF       index */
        /* -------- ------- ------- ------- ------- ------- -------- -----  -----      ----- */
        {     0,       I_,     I_,     O_,     0,      0,      0,      0,     Q_},    /* [0] */
        {     0,       I_,     I_,     0,      0,      0,      0,      0,     Q_},    /* [1] */
        {     0,       0,      I_,     O_,     0,      0,      0,      0,     Q_},    /* [2] */
        {     0,       I_|E_,  I_|E_,  E_,     E_,     A_,     A_,     A_,    E_|Q_}, /* [3] */
        {     0,       I_|E_,  I_|E_,  E_,     E_,     A_,     A_,     A_,    E_|Q_}, /* [4] */
        {     0,       I_|E_,  I_|E_,  E_,     C_,     A_,     A_,     A_,    E_|Q_}, /* [5] */
        {     0,       I_|E_,  I_|E_,  E_,     C_,     E_,     E_,     E_,    E_|Q_}, /* [6] */
        {     0,       I_|S_,  I_|S_,  E_,     E_,     E_,     E_,     E_,    E_|Q_}, /* [7] */
        {     0,       I_|S_,  I_|S_,  E_,     E_,     E_,     E_,     E_,    E_|Q_}, /* [8] */
        {     0,       I_,     I_,     0,      0,      0,      0,      M_,    Q_},    /* [9] */
    };

    char key[HOT_MAX_FONT_NAME];
    int iKey = 0; /* Suppress optimizer warning */
    int state = 0;
    long offset = 0;
    long recoff = 0; /* Suppress optimizer warning */
    FontRef *ref = NULL;

    /* Initialize match name to key for lookups */
    h->MatchName = key;

    h->fileid = fileid;
    h->line = 1;

    for (;;) {
        size_t count;
        char *pbeg = h->cb.refill(h->cb.ctx, h->fileid, &count);
        char *pend = pbeg + count;
        char *p = pbeg;

        while (p != pend) {
            int class; /* Character class */
            int actn;  /* Action flags */
            int c = *p++;

            switch (c) {
                case '\t':
                case '\v':
                case '\f':
                case ' ': /* Blank */
                    class = 0;
                    break;
                case '\n':
                    class = 1;
                    break;
                case '\r':
                    class = 2;
                    break;
                case '[':
                    class = 3;
                    break;
                case ']':
                    class = 4;
                    break;
                case 'c':
                    class = 6;
                    break;
                case '=':
                    class = 7;
                    break;
                default:
                    class = 5;
                    break;
                case '\0': /* EOF */
                    class = 8;
                    break;
            }

            actn = action[state][class];
            state = next[state][class];

            /* Perform actions */
            if (actn == 0) {
                continue;
            }
            if (actn & E_) {
                /* Syntax error */
                rptError(h, fcdbSyntaxErr);
            }
            if (actn & S_) {
                /* Terminate section key */
                key[iKey] = '\0';

                ref = (FontRef *)bsearch(h, h->refs.array, h->refs.cnt,
                                         sizeof(FontRef), matchFontName);
                if (ref) {
                    /* Already in list */
                    rptError(h, fcdbDuplicateErr);
                    state = 1;
                } else {
                    size_t index;
                    /* Not found in list */
                    index = h->refs.cnt;
                    ref = &dnaGROW(h->refs, index)[index];

                    /* Make hole */
                    memmove(ref + 1, ref,
                            sizeof(FontRef) * (h->refs.cnt++ - index));

                    /* Fill record */
                    ref->iFontName = h->FontNames.cnt;
                    ref->location = h->fileid << 24 | recoff;
                    ref->line = h->line;

                    strcpy(dnaEXTEND(h->FontNames, iKey + 1), key);
                }
            }
            if (actn & A_) {
                if (iKey == HOT_MAX_FONT_NAME - 1) {
                    /* Key length exceeded */
                    rptError(h, fcdbKeyLengthErr);
                    state = 1;
                } else {
                    key[iKey++] = c;
                }
            }
            if (actn & O_) {
                iKey = 0;
                if (ref != NULL) {
                    /* Set record length */
                    ref->length = p - pbeg + offset - 1 - recoff;
                }
            }
            if (actn & C_) {
                recoff = p - pbeg + offset;
            }
            if (actn & Q_) {
                if (ref != NULL) {
                    /* Set record length */
                    ref->length = p - pbeg + offset - 1 - recoff;
                }
                ctuQSort(h->refs.array, h->refs.cnt, sizeof(FontRef), cmpFontName, h);
                if (syntaxVersion == 0) {
                    syntaxVersion = 2;
                }
                h->syntaxVersion = syntaxVersion;
                h->cb.setMenuVersion(callBackCtx, fileid, syntaxVersion);
                return;
            }
            if (actn & I_) {
                h->line++;
            }

            if (actn & M_) {
                if (syntaxVersion == 0) {
                    syntaxVersion = 1;
                }
            }
        }

        /* Update buffer offset */
        offset += count;
    }
#undef I_
#undef E_
#undef A_
#undef S_
#undef O_
#undef C_
#undef Q_
#undef M_

    ctuQSort(h->refs.array, h->refs.cnt, sizeof(FontRef), cmpFontName, h);
    if (syntaxVersion == 0) {
        syntaxVersion = 2;
    }
    h->syntaxVersion = syntaxVersion;
    h->cb.setMenuVersion(callBackCtx, fileid, syntaxVersion);
}

/* Get key identifer for keyword */
static int getKeywordId(char *keyword) {
    static struct {
        char *name;
        short id;
    } keywords[] = {
        {"f", kFamily},
        {"s", kSubfamily},
        {"l", kCompatible},
        {"m", kCompatibleFull},
        {"c", kOldCompatible},
        {"b", kBold},
        {"i", kItalic},
        {"t", kBoldItalic},
        {"wpff", kWPFFamily},
        {"wpfs", kWPFStyle},
        {"macenc", kMacEnc},
        {"family", kFamily},
        {"subfamily", kSubfamily},
        {"style family", kCompatible},
        {"mac full", kCompatibleFull},
        {"compatible", kOldCompatible},
        {"bold", kBold},
        {"italic", kItalic},
        {"bolditalic", kBoldItalic},
        {"italicangle", kItalicangle},
        {"width", kWidth},
        {"weight", kWeight},
    };
    unsigned int i;
    for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
        if (strcmp(keyword, keywords[i].name) == 0) {
            return keywords[i].id;
        }
    }
    return kUnknown;
}

/* Parse comma-terminated number from head of string. If no number or number
   not comma-terminated return -1. If number converted but out of range return
   -2. If conversion suceeded a pointer following the terminating comma is
   return by the "q" argument. */
static long parseNumber(fcdbCtx h, char **p, int isNameId) {
    char *q;
    long value = strtol(*p, &q, 0);
    if (*p != q) {
        /* Number converted */
        while (isspace(*q)) {
            q++;
        }

        if (*q == ',') {
            if (isNameId) {
                if (value < 0 || value > 65535) {
                    /* Name id range error */
                    rptError(h, fcdbIdRangeErr);
                    return -2;
                }
            } else if (value < 0 || value > 255) {
                /* Name id range error */
                rptError(h, fcdbCodeRangeErr);
                return -2;
            }

            /* Conversion suceeded */
            *p = q + 1;
            return value;
        } else {
            /* Unterminated number; no conversion */
            return -1;
        }
    }

    /* No conversion */
    return -1;
}

/* Parse name key value */
static int parseName(fcdbCtx h, unsigned keywordId, char *p) {
    long id;
    int win;
    unsigned short platformId = HOT_NAME_MS_PLATFORM;
    unsigned short platspecId = HOT_NAME_MS_UGL;
    unsigned short languageId = HOT_NAME_MS_ENGLISH;
    unsigned short nameId = 0; /* Suppress optimizer warning */

    if (h->cb.addname == NULL) {
        return 0;
    }

    /* Try to parse platform id */
    id = parseNumber(h, &p, 1);
    if (id == -2) {
        return 1;
    } else if (id == -1) {
        ; /* Use MS defaults */
    } else {
        platformId = (unsigned short)id;
        if (platformId == HOT_NAME_MS_PLATFORM) {
            ; /* Use MS defaults */
        } else if (platformId == HOT_NAME_MAC_PLATFORM) {
            /* Set Macintosh defaults */
            platformId = HOT_NAME_MAC_PLATFORM;
            platspecId = HOT_NAME_MAC_ROMAN;
            languageId = HOT_NAME_MAC_ENGLISH;
        } else {
            rptError(h, fcdbSyntaxErr);
            return 1;
        }

        /* Try to parse platform-specific id */
        id = parseNumber(h, &p, 1);
        if (id == -2) {
            return 1;
        } else if (id == -1) {
            ; /* Use assigned defaults */
        } else {
            platspecId = (unsigned short)id;

            /* Try to parse language id */
            id = parseNumber(h, &p, 1);
            if (id == -2) {
                return 1;
            } else if (id == -1) {
                rptError(h, fcdbSyntaxErr);
                return 1;
            } else {
                languageId = (unsigned short)id;
            }
        }
    }
    if (*p == '\0') {
        rptError(h, fcdbEmptyNameErr);
        return 1;
    }

    /* Prepare name string */
    win = platformId == HOT_NAME_MS_PLATFORM;

    /* Convert keyword id to name id */
    switch (keywordId) {
        case kFamily:
            nameId = HOT_NAME_PREF_FAMILY;
            break;
        case kSubfamily:
            nameId = HOT_NAME_PREF_SUBFAMILY;
            break;
        case kCompatible:
            nameId = HOT_NAME_FAMILY;
            if (h->syntaxVersion != 2) {
                rptError(h, fcdbMixedSyntax);
            }
            break;
        case kCompatibleFull:
            if (h->syntaxVersion != 2) {
                rptError(h, fcdbMixedSyntax);
            }
            if (platformId != HOT_NAME_MAC_PLATFORM) {
                rptError(h, fcdbWinCompatibleFullError);
                return 1;
            }
            nameId = HOT_NAME_COMP_FULL;
            break;
        case kOldCompatible:
            if (h->syntaxVersion != 1) {
                rptError(h, fcdbMixedSyntax);
            }
            nameId = win ? HOT_NAME_FAMILY : HOT_NAME_COMP_FULL;
            break;
        case kWPFFamily:
            nameId = HOT_NAME_WPF_FAMILY;
            break;
        case kWPFStyle:
            nameId = HOT_NAME_WPF_STYLE;
            break;
    }

    /* Add name string */
    if (h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, p)) {
        rptError(h, fcdbSyntaxErr);
        return 1;
    }

    return 0;
}

/* Parse style-linked FontName */
static int parseLink(fcdbCtx h, unsigned keywordId, char *p) {
    if (h->cb.addlink == NULL) {
        return 0;
    }

    h->cb.addlink(h->cb.ctx, keywordId - kBold + 1, p);
    return 0;
}

/* Parse encoding key value */
static int parseEnc(fcdbCtx h, char *p) {
    char *q;
    long code = parseNumber(h, &p, 0);

    if (code == -2) {
        return 1;
    } else if (code == -1) {
        rptError(h, fcdbSyntaxErr);
        return 1;
    }

    /* Skip leading blanks */
    while (isspace(*p)) {
        p++;
    }

    /* Skip glyph name */
    for (q = p; *p != '\0'; p++) {
        if (isspace(*p)) {
            rptError(h, fcdbSyntaxErr);
            return 1;
        }
    }

    h->cb.addenc(h->cb.ctx, code, q);
    return 0;
}

/* Parse font record */
static int parseRecord(fcdbCtx h) {
    /* Next state table */
    static unsigned char next[8][7] = {
        /*  blank     \n       \r      ;       =       *      EOR    index */
        /* -------- ------- ------- ------- ------- ------- -------- ----- */
        {     0,       0,      2,      1,      1,      3,      0},  /* [0] */
        {     1,       0,      2,      1,      1,      1,      0},  /* [1] */
        {     0,       0,      2,      1,      1,      3,      0},  /* [2] */
        {     4,       0,      2,      1,      5,      3,      0},  /* [3] */
        {     4,       0,      2,      1,      5,      1,      0},  /* [4] */
        {     6,       0,      2,      7,      7,      7,      0},  /* [5] */
        {     6,       0,      2,      7,      7,      7,      0},  /* [6] */
        {     7,       0,      2,      7,      7,      7,      0},  /* [7] */
    };

    /* Action table */

#define I_ (1 << 0) /* Increment line counter */
#define E_ (1 << 1) /* Report sytax error */
#define P_ (1 << 2) /* Save string pointer */
#define K_ (1 << 3) /* Save keyword */
#define V_ (1 << 4) /* Save value */
#define Q_ (1 << 5) /* Quit on end-of-record */

    static unsigned char action[8][7] = {
        /*  blank     \n      \r       ;       =       *      EOR       index */
        /* -------- ------- ------- ------- ------- ------- --------    ----- */
        {     0,       I_,    I_,      0,      E_,     P_,     Q_},    /* [0] */
        {     0,       I_,    I_,      0,      0,      0,      Q_},    /* [1] */
        {     0,       0,     I_,      0,      E_,     P_,     Q_},    /* [2] */
        {     K_,      I_|E_, I_|E_,   E_,     K_,     0,      E_|Q_}, /* [3] */
        {     0,       I_|E_, I_|E_,   E_,     0,      E_,     E_|Q_}, /* [4] */
        {     0,       I_|E_, I_|E_,   P_,     P_,     P_,     E_|Q_}, /* [5] */
        {     0,       I_|E_, I_|E_,   P_,     P_,     P_,     E_|Q_}, /* [6] */
        {     0,       I_|V_, I_|V_,   0,      0,      0,      V_|Q_}, /* [7] */
    };

    char *pbeg = NULL; /* Suppress optimizer warning */
    int keywordId = 0; /* Suppress optimizer warning */
    int state = 0;
    char *p = h->record.array;

    for (;;) {
        int class; /* Character class */
        int actn;  /* Action flags */
        int c = *p++;

        switch (c) {
            case '\t':
            case '\v':
            case '\f':
            case ' ': /* Blank */
                class = 0;
                break;
            case '\n':
                class = 1;
                break;
            case '\r':
                class = 2;
                break;
            case ';':
                class = 3;
                break;
            case '=':
                class = 4;
                break;
            case '#':
                class = 6;
                break;
            default:
                class = 5;
                break;
            case '\0': /* End of record */
                class = 6;
                break;
        }

        actn = action[state][class];
        state = next[state][class];

        /* Perform actions */
        if (actn == 0) {
            continue;
        }
        if (actn & E_) {
            rptError(h, fcdbSyntaxErr);
            return 1;
        }
        if (actn & P_) {
            pbeg = p - 1;
        }
        if (actn & K_) {
            *(p - 1) = '\0'; /* Terminate keyword */
            keywordId = getKeywordId(pbeg);
            if (keywordId == kUnknown) {
                rptError(h, fcdbSyntaxErr);
                return 1;
            }
        }
        if (actn & V_) {
            char *q;

            /* Trim trailing blanks */
            for (q = p - 1; isspace(*q); q--) {
            }
            *(q + 1) = '\0';

            switch (keywordId) {
                case kFamily:
                case kSubfamily:
                case kCompatible:
                case kCompatibleFull:
                case kOldCompatible:
                case kWPFFamily:
                case kWPFStyle:
                    if (parseName(h, keywordId, pbeg)) {
                        return 1;
                    }
                    break;
                case kBold:
                case kItalic:
                case kBoldItalic:
                    if (parseLink(h, keywordId, pbeg)) {
                        return 1;
                    }
                    break;
                case kMacEnc:
                    if (parseEnc(h, pbeg)) {
                        return 1;
                    }
                    break;
            }
        }
        if (actn & Q_) {
            return 0;
        }
        if (actn & I_) {
            h->line++;
        }
    }
#undef I_
#undef E_
#undef P_
#undef K_
#undef V_
#undef Q_
}

static void buildDefaultRec(fcdbCtx h, char *FontName) {
    unsigned short platformId = HOT_NAME_MS_PLATFORM;
    unsigned short platspecId = HOT_NAME_MS_UGL;
    unsigned short languageId = HOT_NAME_MS_ENGLISH;
    char familyName[64];
    char *subFamily = NULL; /* This is set to point into the FontName*/
    int sepIndex = strcspn(FontName, "-");
    unsigned short nameId = 0; /* Suppress optimizer warning */

    if ((sepIndex > -1) && (FontName[sepIndex] == '-')) { /* On Windows, strcspn returns the string length when the char is not found! */
        subFamily = &FontName[sepIndex + 1];
        strncpy(familyName, FontName, sepIndex);
        familyName[sepIndex] = 0;
    } else {
        strcpy(familyName, FontName);
    }

    nameId = HOT_NAME_FAMILY; /* family */
    h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, familyName);

    nameId = HOT_NAME_SUBFAMILY; /* style */
    if (subFamily == NULL) {
        h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, "Regular");
    } else {
        h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, subFamily);
    }

    platformId = HOT_NAME_MAC_PLATFORM;
    platspecId = HOT_NAME_MAC_ROMAN;
    languageId = HOT_NAME_MAC_ENGLISH;
    nameId = HOT_NAME_PREF_FAMILY; /* family */
    h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, familyName);
    nameId = HOT_NAME_PREF_SUBFAMILY; /* style */
    if (subFamily == NULL) {
        h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, "Regular");
    } else {
        h->cb.addname(h->cb.ctx, platformId, platspecId, languageId, nameId, subFamily);
    }
}

/* Get font record from database. Return 0 if record found and parsed OK else
   return 1. */
int fcdbGetRec(fcdbCtx h, char *FontName) {
    FontRef *ref;

    if (h->refs.cnt == 0) {
        buildDefaultRec(h, FontName);
        return 1;
    }

    h->MatchName = FontName;
    ref = (FontRef *)bsearch(h, h->refs.array, h->refs.cnt, sizeof(FontRef), matchFontName);
    if (ref == NULL) {
        return 1;
    }

    /* Get record */
    h->fileid = ref->location >> 24 & 0xff;
    h->line = ref->line;
    h->cb.getbuf(h->cb.ctx, h->fileid, ref->location & 0xffffff,
                 ref->length, dnaGROW(h->record, ref->length));
    h->record.array[ref->length] = '\0';

    return parseRecord(h);
}

/* Create new context */
fcdbCtx fcdbNew(fcdbCallbacks *cb, void *dna_ctx) {
    fcdbCtx h = cbMemNew(cb->ctx, sizeof(struct fcdbCtx_));
    dnaCtx local_dna_ctx = (dnaCtx)dna_ctx;

    h->cb = *cb; /* Copy callbacks */

    dnaINIT(local_dna_ctx, h->refs, 2600, 500);
    dnaINIT(local_dna_ctx, h->FontNames, 50000, 20000);
    dnaINIT(local_dna_ctx, h->record, 1000, 5000);
    h->syntaxVersion = 0;
    return h;
}

/* Free callback context */
void fcdbFree(fcdbCtx h) {
    void *cxt = h->cb.ctx;
    dnaFREE(h->refs);
    dnaFREE(h->FontNames);
    dnaFREE(h->record);
    cbMemFree(cxt, h);
}

#if DEBUG

static void dbbyte(unsigned byte) {
    if (byte < 0x80) {
        printf("%c", byte);
    } else {
        static char hex[] = "0123456789abcdef";
        printf(":%c%c", hex[byte >> 4 & 0xf], hex[byte & 0xf]);
    }
}

static void dbbytes(int msb, int lsb) {
    static char hex[] = "0123456789abcdef";
    printf("!%c%c%c%c",
           hex[msb >> 4 & 0xf], hex[msb & 0xf],
           hex[lsb >> 4 & 0xf], hex[lsb & 0xf]);
}

static void dbwinname(char *str) {
    /* Convert UTF-8 to 16-bit */
    while (*str != '\0') {
        unsigned s0 = *str++;
        if (s0 < 0xc0) {
            /* 1-byte */
            if (s0 == 0x21) {
                printf("!0021"); /* Handle exclam */
            } else {
                printf("%c", (char)s0);
            }
        } else {
            unsigned s1 = *str++;
            if (s0 < 0xe0) {
                /* 2-byte */
                dbbytes(s0 >> 2 & 0x07,
                        s0 << 6 | (s1 & 0x3f));
            } else {
                /* 3-byte */
                unsigned s2 = *str++;
                dbbytes(s0 << 4 | (s1 >> 2 & 0x0f),
                        s1 << 6 | (s2 & 0x3f));
            }
        }
    }
}

static void dbmacname(char *str) {
    while (*str != '\0') {
        unsigned s0 = *str++;
        if (s0 == 0x3a) {
            printf(":3a"); /* Handle colon */
        } else {
            dbbyte(s0);
        }
    }
}

static int dbAddName(void *ctx,
                     unsigned short platformId, unsigned short platspecId,
                     unsigned short languageId, unsigned short nameId,
                     char *str) {
    int win = platformId == HOT_NAME_MS_PLATFORM;

    /* Print keyword */
    switch (nameId) {
        case HOT_NAME_FAMILY:
            printf("\t%s=", win ? "c" : "f");
            break;
        case HOT_NAME_SUBFAMILY:
        case HOT_NAME_PREF_SUBFAMILY:
            printf("\ts=");
            break;
        case HOT_NAME_PREF_FAMILY:
            printf("\tf=");
            break;
        case HOT_NAME_COMP_FULL:
            printf("\tc=");
            break;
    }

    if (win) {
        if (platspecId != HOT_NAME_MS_UGL ||
            languageId != HOT_NAME_MS_ENGLISH) {
            printf("%hu,%hu,0x%04hx,", platformId, platspecId, languageId);
        }
        dbwinname(str);
    } else {
        if (platspecId != HOT_NAME_MAC_ROMAN ||
            languageId != HOT_NAME_MAC_ENGLISH) {
            printf("%hu,%hu,%hu,", platformId, platspecId, languageId);
        } else {
            printf("%hu,", platformId);
        }
        dbmacname(str);
    }
    printf("\n");
    return 0;
}

void dbAddEnc(void *ctx, int code, char *gname) {
    printf("\tmacenc=%d,%s\n", code, gname);
}

static void dbrec(fcdbCtx h, char *FontName) {
    fcdbCallbacks tmp;

    /* Swap callbacks with debug versions */
    tmp = h->cb;
    h->cb.addname = dbAddName;
    h->cb.addenc = dbAddEnc;

    printf("[%s]\n", FontName);
    if (fcdbGetRec(h, FontName)) {
        printf("fcdbGetRec failed for font [%s]\n", FontName);
    } else {
        printf("\n");
    }
    /* Restore original callbacks */
    h->cb = tmp;
}

static void dbrecs(fcdbCtx h) {
    int i;
    fcdbCallbacks tmp;

    /* Swap callbacks with debug versions */
    tmp = h->cb;
    h->cb.addname = dbAddName;
    h->cb.addenc = dbAddEnc;

    for (i = 0; i < h->refs.cnt; i++) {
        char *FontName = &h->FontNames.array[h->refs.array[i].iFontName];
        printf("[%s]\n", FontName);
        if (fcdbGetRec(h, FontName)) {
            printf("fcdbGetRec failed for font [%s]\n", FontName);
        } else {
            printf("\n");
        }
    }

    /* Restore original callbacks */
    h->cb = tmp;
}

static void dbrefs(fcdbCtx h) {
    int i;

    printf("--- refs[index]={location,length,FontName}\n");
    for (i = 0; i < h->refs.cnt; i++) {
        FontRef *ref = &h->refs.array[i];
        printf("[%d]={%08lx,%3ld,%s}\n", i, ref->location, ref->length,
               &h->FontNames.array[ref->iFontName]);
    }
}

/* This function just serves to suppress annoying "defined but not used"
   compiler messages when debugging */
static void CDECL dbuse(int arg, ...) {
    dbuse(0, dbrec, dbrecs, dbrefs);
}
#endif
