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

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>

#include "hotconv.h"
#include "cmap.h"
#include "map.h"
#include "feat.h"
#include "dynarr.h"

/* ---------------------------- Table Definition --------------------------- */

#define cmap_VERSION 0

/* Common header for formats 0, 2, 4, 6. Access by the GET_* macros below */
typedef struct {
    uint16_t format;
    uint16_t length;
    uint16_t language;
} FormatHdr;

/* Common header for the extended formats: 8, 10, 12. Access by the GET_* macros below */
typedef struct {
    uint16_t format;
    uint16_t reserved;
    uint32_t length;
    uint32_t language;
} FormatHdrExt;

typedef struct {
    uint16_t format;
    uint32_t length;
    uint32_t numUVS;
} FormatHdr14;

#define IS_EXT_FORMAT(f) ((f) == 8 || (f) == 10 || (f) == 12) /* Extended formats */

#define GET_FORMAT(p) (*((unsigned short *)p))

typedef struct {
    uint16_t format;
    uint16_t length;
    uint16_t language;
    unsigned char glyphId[256];
} Format0;
#define FORMAT0_SIZE (uint16 * 3 + uint8 * 256)

typedef struct {
    unsigned short firstCode;
    unsigned short entryCount;
    short idDelta;
    unsigned short idRangeOffset;
} Segment2;
#define SEGMENT2_SIZE (uint16 * 3 + int16 * 1)

typedef struct {
    unsigned short format;
    unsigned short length;
    unsigned short language;
    unsigned short segmentKeys[256];
    dnaDCL(Segment2, segment);
    dnaDCL(GID, glyphId);
} Format2;
#define FORMAT2_SIZE(segs, glyphs) \
    (uint16 * 3 + uint16 * 256 + SEGMENT2_SIZE * (segs) + uint16 * (glyphs))

typedef struct {
    unsigned short format;
    unsigned short length;
    unsigned short language;
    unsigned short segCountX2;
    unsigned short searchRange;
    unsigned short entrySelector;
    unsigned short rangeShift;
    unsigned short *endCode;
    unsigned short password;
    unsigned short *startCode;
    short *idDelta;
    unsigned short *idRangeOffset;
    dnaDCL(GID, glyphId);
} Format4;
#define FORMAT4_SIZE(segs, glyphs) \
    (uint16 * 8 + (3 * uint16 + int16) * (segs) + uint16 * (glyphs))

typedef struct {
    unsigned short format;
    unsigned short length;
    unsigned short language;
    unsigned short firstCode;
    unsigned short entryCount;
    GID *glyphId;
} Format6;
#define FORMAT6_SIZE(glyphs) (uint16 * 5 + uint16 * (glyphs))

/* Segment for formats 8 and 12: */
typedef struct {
    uint32_t startCharCode;
    uint32_t endCharCode;
    uint32_t startGlyphID;
} Segment8_12;
#define SEGMENT8_12_SIZE (3 * uint32)

typedef struct {
    uint16_t format;
    uint16_t reserved;
    uint32_t length;
    uint32_t language;
    uint32_t nGroups;
    dnaDCL(Segment8_12, segment);
} Format12;

#define FORMAT12_SIZE(nSegs) (2 * uint16 + 3 * uint32 + SEGMENT8_12_SIZE * (nSegs))

typedef struct {
    uint32_t uv;
    uint32_t addtlCnt;
} DefaultUVSEntryRange;

typedef struct {
    uint32_t uv;
    uint16_t glyphID;
} ExtUVSEntry;

typedef struct {
    unsigned long uvs;
    dnaDCL(DefaultUVSEntryRange, dfltUVS);
    dnaDCL(ExtUVSEntry, extUVS);
} UVSRecord;

typedef struct {
    uint16_t format;
    uint32_t length;
    uint32_t numUVS;
} Format14;

#define FORMAT14_SIZE(nUVSRecs) (uint16 + 2 * uint32 + nUVSRecs * (uint24 + 2 * uint32))

typedef struct {
    int16_t id; /* Internal use. All encodings that have the same id will end */
                /* up pointing to the same subtable in the OTF. Of all the    */
                /* encodings with the same id, exactly one will have a        */
                /* non-NULL format field.                                     */
    uint16_t platformId;
    uint16_t scriptId;
    uint32_t offset;
    void *format;
} Encoding;
#define ENCODING_SIZE (uint16 * 2 + uint32 * 1)

typedef struct {
    uint16_t version;
    uint16_t nEncodings;
    dnaDCL(Encoding, encoding);
} cmapTbl;
#define TBL_HDR_SIZE (uint16 * 2)

/* --------------------------- Context Definition ---------------------------*/

/* Format 2 code space ranges */
typedef struct {
    unsigned short lo;
    unsigned short hi;
} Range;

/* New encoding information */
typedef struct {
    unsigned long code;
    GID glyphId;
    unsigned short span; /* number of mappings in this range */
    unsigned short ordered;
    unsigned short flags;
#define CODE_BREAK (1 << 0)   /* Broke range because of code discontinuity */
#define CODE_1BYTE (1 << 1)   /* Single byte range (for mixed-byte) */
#define CODE_MACCNTL (1 << 2) /* An added Mac control code */
#define CODE_DELETE (1 << 3)  /* Mark for deletion */
} Mapping;

typedef struct {
    unsigned long uv;
    unsigned long uvs;
} DefaultUVEntry;

typedef struct {
    unsigned long uv;
    unsigned long uvs;
    unsigned short gid;
} NonDefaultUVEntry;

struct cmapCtx_ {
    /* Stores the current encoding (cmap subtable) */
    unsigned platformId;
    unsigned scriptId;
    unsigned language;
    unsigned long maxCode; /* Max code in encoding */
    unsigned maxGlyphId;   /* Max glyphId in the encoding */
    short flags;
#define SEEN_1BYTE (1 << 0) /* Single-byte code point seen */
#define SEEN_2BYTE (1 << 1) /* Double-byte code point seen */
#define SEEN_4BYTE (1 << 2) /* Four-byte code point seen */
#define SEEN_UVS (1 << 2)   /*Seen Unicode Variation Selector map */
    unsigned long lastUVS;
    dnaDCL(UVSRecord, uvsList);
    dnaDCL(Mapping, mapping);
    dnaDCL(Range, codespace); /* for mixed-byte encodings' dbl-byte ranges */

    cmapTbl tbl; /* Stores the entire cmap */
    hotCtx g;
};

/* --------------------------- Standard Functions -------------------------- */

/* Return a cmap context */
void cmapNew(hotCtx g) {
    cmapCtx h = MEM_NEW(g, sizeof(struct cmapCtx_)); /* Allocate context */

    h->tbl.nEncodings = 0;
    h->lastUVS = 0;
    dnaINIT(g->dnaCtx, h->tbl.encoding, 5, 2);
    dnaINIT(g->dnaCtx, h->uvsList, 40, 40);
    dnaINIT(g->dnaCtx, h->mapping, 9000, 2000);
    dnaINIT(g->dnaCtx, h->codespace, 5, 10);

    /* Link contexts */
    h->g = g;
    g->ctx.cmap = h;
}

/* Add standard Mac C0 control chars; any client-specified mappings will take
   priority; they'll be detected later in checkDuplicates(). */
static void addMacControlChars(hotCtx g, cmapCtx h) {
    unsigned int i;
    GID spaceGID = mapUV2GID(g, UV_SPACE);
    static unsigned short addCodes[] = {
        /* ---------- Apple's rationale: ---------- */
        9, /* HORIZONTAL TAB    SimpleText displays this for tab - and we    */
        /*                   don't want marking notdefs to be displayed.  */
        13, /* CARRIAGE RETURN   SimpleText displays this for carriage return */
            /*                   - and we don't want marking notdefs to be    */
            /*                   displayed.                                   */
#if 0
        /* Removed these since the space at NULL was tickling an InDesign/Mac */
        /* 1.0 bug whereby it was identifying the space's GID as the notdef   */
        /* GID.                                                               */
        0, /* NULL              TTY streams: you don't want the NULLs to be  */
        /*                   displayed.                                   */
        /*                   "Faux" Unicode: Unicode text in the ASCII    */
        /*                   range can be displayed as if it were ASCII   */
        8,  /* BACKSPACE         <No rationale>                               */
        29, /* GROUP SEPARATOR   <No rationale>                               */
#endif
    };

    if (spaceGID != GID_UNDEF) {
        for (i = 0; i < ARRAY_LEN(addCodes); i++) {
            cmapAddMapping(g, addCodes[i], spaceGID, 1);
            h->mapping.array[h->mapping.cnt - 1].flags |= CODE_MACCNTL;
        }
    }
}

/* Start a new encoding (cmap subtable) */
void cmapBeginEncoding(hotCtx g, unsigned platformId, unsigned scriptId,
                       unsigned language) {
    cmapCtx h = g->ctx.cmap;

    h->platformId = platformId;
    h->scriptId = scriptId;
    h->language = language;
    h->maxCode = 0;
    h->maxGlyphId = 0;
    h->flags = 0;
    h->mapping.cnt = 0;
    h->codespace.cnt = 0;

    if (h->platformId == cmap_MAC && !IS_CID(g)) {
        addMacControlChars(g, h);
    }
}

/* Prints message. Message should not have newline */
static void CDECL cmapMsg(hotCtx g, int msgType, char *fmt, ...) {
    cmapCtx h = g->ctx.cmap;
    va_list ap;
    char msgVar[1024];
    char msg[1024];

    va_start(ap, fmt);
    VSPRINTF_S(msgVar, sizeof(msgVar), fmt, ap);
    va_end(ap);
    sprintf(msg, "cmap{plat=%u,script=%u,lang=%u}: %s", h->platformId,
            h->scriptId, h->language, msgVar);
    hotMsg(g, msgType, msg);
}

/* Add a mapping pair to current encoding. Assumes that any notdef/cid layering
   (for CMap encodings) has already been done. Doesn't weed out notdefs;
   faithfully records them if added by this API. codeSize must be 1, 2 or 4; it
   is needed to flag values like 0x0020 as double-byte. */
void cmapAddMapping(hotCtx g, unsigned long code, unsigned glyphId,
                    int codeSize) {
    cmapCtx h = g->ctx.cmap;
    Mapping *mapping = dnaNEXT(h->mapping);

    mapping->code = code;
    mapping->glyphId = glyphId;
    mapping->span = 0;
    mapping->ordered = 0;
    mapping->flags = codeSize == 1 ? CODE_1BYTE : 0;

    if (codeSize == 1) {
        h->flags |= SEEN_1BYTE;
        if (code > 0xFF) {
            cmapMsg(g, hotFATAL, "code <%lx> is not single-byte", code);
        }
    } else if (codeSize == 2) {
        h->flags |= SEEN_2BYTE;
        if (code > 0xFFFF) {
            cmapMsg(g, hotFATAL, "code <%lx> is not double-byte", code);
        }
    } else if (codeSize == 4) {
        h->flags |= SEEN_4BYTE;
    } else {
        hotMsg(g, hotFATAL, "[internal] invalid cmap codeSize");
    }

    if (h->maxCode < code) {
        h->maxCode = code;
    }
    if (h->maxGlyphId < glyphId) {
        h->maxGlyphId = glyphId;
    }
}

/* Add only 2-byte codespace ranges (needs to be called for mixed-byte
   encodings only) */
void cmapAddCodeSpaceRange(hotCtx g, unsigned lo, unsigned hi, int codeSize) {
    cmapCtx h = g->ctx.cmap;
    Range *csr;

    if (codeSize == 1 && (lo > 0xff || hi > 0xff)) {
        cmapMsg(g, hotFATAL, "codespace range <%hx> <%hx> is not single-byte",
                lo, hi);
    }

#if 0 /* xxx Remove after testing */
    printf("codespace: %hx-%hx (codeSize=%d)\n", lo, hi, codeSize);
#endif

    if (codeSize != 2) {
        return;
    }

    csr = dnaNEXT(h->codespace);
    csr->lo = lo;
    csr->hi = hi;
}

/* Add a UVS entry to current tables. Needs to add the entry to one of the four
   lists: default UVS with BMP UB, default UVS with supplement UV, non default UVS
   with BMP UVm non-default UBS with supplemental UV, Assumes that the
   entries are added in UVS order, so we don;t need to sort the four tables.. */
void cmapAddUVSEntry(hotCtx g, unsigned int uvsFlags, unsigned long uv, unsigned long uvs, GID gid) {
    cmapCtx h = g->ctx.cmap;
    UVSRecord *uvsRec = NULL;

    if (h->lastUVS != uvs) {
        uvsRec = dnaNEXT(h->uvsList);
        dnaINIT(g->dnaCtx, uvsRec->dfltUVS, 1000, 1000);
        dnaINIT(g->dnaCtx, uvsRec->extUVS, 1000, 1000);
        h->lastUVS = uvsRec->uvs = uvs;
    } else {
        uvsRec = dnaINDEX(h->uvsList, h->uvsList.cnt - 1);
    }

    if (uvsFlags & UVS_IS_DEFAULT) {
        if (uvsRec->dfltUVS.cnt == 0) {
            DefaultUVSEntryRange *uvd = dnaNEXT(uvsRec->dfltUVS);
            uvd->uv = uv;
            uvd->addtlCnt = 0;
        } else {
            DefaultUVSEntryRange *uvd = dnaINDEX(uvsRec->dfltUVS, uvsRec->dfltUVS.cnt - 1);
            if ((uvd->addtlCnt < 255) && ((uvd->uv + uvd->addtlCnt + 1) == uv)) {
                uvd->addtlCnt++;
            } else {
                DefaultUVSEntryRange *uvd = dnaNEXT(uvsRec->dfltUVS);
                uvd->uv = uv;
                uvd->addtlCnt = 0;
            }
        }
    } else {
        ExtUVSEntry *uvExt = dnaNEXT(uvsRec->extUVS);
        uvExt->uv = uv;
        uvExt->glyphID = gid;
    }
}

/* Make format 0 cmap */
static Format0 *makeFormat0(cmapCtx h) {
    int i;
    Mapping *mapping = h->mapping.array;
    Format0 *fmt = MEM_NEW(h->g, sizeof(Format0));

    for (i = 0; i < 256; i++) {
        fmt->glyphId[i] = 0;
    }
    for (i = 0; i < h->mapping.cnt; i++) {
        fmt->glyphId[mapping[i].code] = (unsigned char)mapping[i].glyphId;
    }

    fmt->format = 0;
    fmt->length = FORMAT0_SIZE;
    fmt->language = h->language;

    return fmt;
}

/* ------- begin format 2 */

/* GlyphId array element initializer */
static void glyphIdInit(void *ctx, long count, GID *element) {
    int i;
    for (i = 0; i < count; i++) {
        *element = 0;
        element++;
    }
    return;
}

/* Sort deleted last; single-byte before double-byte; then by code; then
   MAC_CNTL before client-added */
static int CDECL cmpMixedByteCodes(const void *first, const void *second) {
    const Mapping *a = (Mapping *)first;
    const Mapping *b = (Mapping *)second;

    if ((a->flags & CODE_DELETE) && (b->flags & CODE_DELETE)) {
        return 0;
    } else if (a->flags & CODE_DELETE) {
        return 1;
    } else if (b->flags & CODE_DELETE) {
        return -1;
    } else if ((a->flags & CODE_1BYTE) && !(b->flags & CODE_1BYTE)) {
        return -1;
    } else if (!(a->flags & CODE_1BYTE) && (b->flags & CODE_1BYTE)) {
        return 1;
    } else if (a->code < b->code) {
        return -1;
    } else if (a->code > b->code) {
        return 1;
    } else if ((a->flags & CODE_MACCNTL) && !(b->flags & CODE_MACCNTL)) {
        return -1;
    } else if (!(a->flags & CODE_MACCNTL) && (b->flags & CODE_MACCNTL)) {
        return 1;
    } else {
        return 0;
    }
}

/* Sort deleted last; compare character codes; then MAC_CNTL before client-
   added. */
static int CDECL cmpCodes(const void *first, const void *second) {
    const Mapping *a = first;
    const Mapping *b = second;

    if ((a->flags & CODE_DELETE) && (b->flags & CODE_DELETE)) {
        return 0;
    } else if (a->flags & CODE_DELETE) {
        return 1;
    } else if (b->flags & CODE_DELETE) {
        return -1;
    } else if (a->code < b->code) {
        return -1;
    } else if (a->code > b->code) {
        return 1;
    } else if ((a->flags & CODE_MACCNTL) && !(b->flags & CODE_MACCNTL)) {
        return -1;
    } else if (!(a->flags & CODE_MACCNTL) && (b->flags & CODE_MACCNTL)) {
        return 1;
    } else {
        return 0;
    }
}

/* Partition into segments of single byte or equal first byte ranges. Already
   sorted by cmpMixedByteCodes. */
static void partitionSegments(cmapCtx h) {
    long i;
    unsigned first;
    int span;
    Mapping *mapping = h->mapping.array;

    /* Partition single-byte codes */
    i = 0;
    for (;;) {
        if (i == h->mapping.cnt) {
            cmapMsg(h->g, hotFATAL, "no multi-byte codes in encoding");
        } else if (mapping[i].flags & CODE_1BYTE) {
            i++;
        } else {
            break;
        }
    }

    if (i == 0) {
        cmapMsg(h->g, hotFATAL, "no single-byte codes in encoding");
    }
    mapping[0].span = (unsigned short)i;

    /* Partition by hi-byte codes */
    span = 1;
    first = mapping[i].code & 0xff00;
    for (i++; i < h->mapping.cnt; i++) {
        unsigned curr = mapping[i].code & 0xff00;
        if (first != curr) {
            mapping[i - span].span = span;
            span = 1;
            first = curr;
        } else {
            span++;
        }
    }
    mapping[i - span].span = span;
}

/* Add format 2 segment */
static Segment2 *addSegment2(Format2 *fmt, unsigned short firstCode,
                             unsigned short entryCount, short idDelta,
                             unsigned short idRangeOffset) {
    Segment2 *segment = dnaNEXT(fmt->segment);

    segment->firstCode = firstCode;
    segment->entryCount = entryCount;
    segment->idDelta = idDelta;
    segment->idRangeOffset = idRangeOffset;

    return segment;
}

/* Make first format 2 segment. This handles the single byte encoding */
static void makeFirstSegment2(cmapCtx h, Format2 *fmt) {
    int i;
    Mapping *mapping = h->mapping.array;
    int span = mapping[0].span;
    GID *glyphId = dnaEXTEND(fmt->glyphId, 256);

    (void)addSegment2(fmt, 0, 256, 0, 0);

    /* Construct glyphId array */
    for (i = 0; i < 256; i++) {
        glyphId[i] = 0;
    }
    for (i = 0; i < span; i++) {
        glyphId[mapping[i].code] = mapping[i].glyphId;
    }
}

/* Make format 2 segment. Returns segment index */
static int makeSegment2(cmapCtx h, Format2 *fmt, int start) {
    int i;
    int iSegment;
    Segment2 *segment;
    int minGlyphId; /* Minimum glyphId in segment */
    GID glyphId[256];
    Mapping *mapping = h->mapping.array;
    int span = mapping[start].span;
    int firstCode = mapping[start].code & 0xff;
    int codeRange = (mapping[start + span - 1].code & 0xff) - firstCode + 1;

    /* Find minimum glyph id in segment */
    minGlyphId = mapping[start].glyphId;
    for (i = 1; i < span; i++) {
        if (mapping[start + i].glyphId < minGlyphId) {
            minGlyphId = mapping[start + i].glyphId;
        }
    }

    /* Add new segment */
    iSegment = fmt->segment.cnt; /* Save new segment index */
    segment =
        addSegment2(fmt, (unsigned short)firstCode, (unsigned short)codeRange,
                    (short)(minGlyphId - 1), 0);

    /* Construct glyphId array */
    for (i = 0; i < codeRange; i++) {
        glyphId[i] = 0;
    }
    for (i = 0; i < span; i++) {
        glyphId[(mapping[start + i].code & 0xff) - firstCode] =
            mapping[start + i].glyphId - segment->idDelta;
    }

    /* Look for a glyphId array match */
    for (i = 1; i < iSegment; i++) {
        int j;
        int iGlyphId = fmt->segment.array[i].idRangeOffset;
        GID *existing = &fmt->glyphId.array[iGlyphId];

        for (j = 0; j < codeRange; j++) {
            if (existing[j] != glyphId[j]) {
                goto failed; /* Match failed, try next segment */
            }
        }
        /* Match succeeded, reference matching range */
        segment->idRangeOffset = iGlyphId;
        return iSegment;

    failed:;
    }

    /* Match failed, add glyphIds */
    segment->idRangeOffset = (unsigned short)fmt->glyphId.cnt;
    COPY(dnaEXTEND(fmt->glyphId, codeRange), glyphId, codeRange);

    return iSegment;
}

/* Add double-byte codespace ranges */
static void addcodespaceRanges(cmapCtx h, Format2 *fmt) {
    int i;
    int addSegment = 0;

    for (i = 0; i < h->codespace.cnt; i++) {
        unsigned j;
        unsigned endHiByte;
        Range *range = &h->codespace.array[i];

        endHiByte = range->hi >> 8;
        for (j = range->lo >> 8; j <= endHiByte; j++) {
            if (fmt->segmentKeys[j] == 0) {
                fmt->segmentKeys[j] = (unsigned short)fmt->segment.cnt;
                addSegment = 1;
            }
        }
    }

    if (addSegment) {
        /* Add segment that always gives notdef */
        (void)addSegment2(fmt, 0, 0, 0, 0);
    }
}

/* Make format 2 cmap. Already sorted by cmpMixedByteCodes. */
static Format2 *makeFormat2(cmapCtx h) {
    long i;
    Mapping *mapping = h->mapping.array;
    Format2 *fmt = MEM_NEW(h->g, sizeof(Format2));

    dnaINIT(h->g->dnaCtx, fmt->segment, 128, 32);
    dnaINIT(h->g->dnaCtx, fmt->glyphId, 2000, 500);
    fmt->glyphId.func = glyphIdInit;

    partitionSegments(h);

    /* Initialize segment keys */
    for (i = 0; i < 256; i++) {
        fmt->segmentKeys[i] = 0;
    }

    makeFirstSegment2(h, fmt);

    /* Make all remaining the segments */
    for (i = mapping[0].span; i < h->mapping.cnt; i += mapping[i].span) {
        fmt->segmentKeys[mapping[i].code >> 8] = makeSegment2(h, fmt, i);
    }

    addcodespaceRanges(h, fmt);

    /* Adjust segmentKeys values */
    for (i = 0; i < 256; i++) {
        fmt->segmentKeys[i] *= 8;
    }

    /* Adjust idRangeOffset values */
    for (i = 0; i < fmt->segment.cnt; i++) {
        fmt->segment.array[i].idRangeOffset = (unsigned short)(((fmt->segment.cnt - i - 1) * SEGMENT2_SIZE +
                                                                uint16 +
                                                                fmt->segment.array[i].idRangeOffset * uint16));
    }

    fmt->format = 2;
    fmt->language = h->language;
    fmt->length = (unsigned short)FORMAT2_SIZE(fmt->segment.cnt,
                                               fmt->glyphId.cnt);
    return fmt;
}

/* ------- end format 2 */

static void setOrdered(Mapping *mapping, int codeBreak, int span, int index) {
    int threshold;

    /* If the ordered segment starts with a code break or ends with a code  */
    /* break, then the cost of keeping this as a separate segment is just   */
    /* one new segment break (the following segment break). Else, the cost  */
    /* is adding two new segment breaks - the one to start this segment,    */
    /* and to start the next segment. We mark as NOT ordered those segments */
    /* that are no larger than the threshold.                               */
    if ((mapping[index - span].flags & CODE_BREAK) || codeBreak)
        threshold = 4;
    else
        threshold = 8;
    if (span > threshold)
        mapping[index - span].ordered = 1;
}

/* Partition mappings optimally for format 4. Assumes mapping in code order */
static int partitionRanges(cmapCtx h, Mapping *mapping) {
    int span;
    int ordered = 0;
    int lastOrdered;
    int total;
    int i;
    int nSegments;

    /* mapping is sorted by charCode, then by glyphID. If there is a break   */
    /* on the character code sequence, we always make a new segment. If      */
    /* there is a break in the glyph ID sequence, then we may make a         */
    /* segment break, if doing so saves more bytes than not doing so.        */
    /* If a segment has more than one glyph and no breaks in its glyphID     */
    /* sequence, then it is set as  ordered. Ordered segments are dropped    */
    /* only if keeping them costs less than the new segment cost. All other  */
    /* segments are merged together. idRangeOffset for an ordered segment is */
    /* set to 0, and its glyphID's are not added to the glyphId array, as    */
    /* its glyphIDs can be calculated from startCode/endCode/idDelta alone.  */

    /* Create ordered partitioning. */
    span = 1;
    for (i = 1; i < h->mapping.cnt; i++) {
        int codeBreak = (mapping[i - 1].code + 1 != mapping[i].code);
        if (codeBreak || mapping[i - 1].glyphId + 1 != mapping[i].glyphId) {
            mapping[i - span].span = span;
            mapping[i - span].ordered = 0; /* default */
            if (span > 1)                  /* an ordered segment is a segment with more than one code point with consecutive glyph ID's */
            {
                setOrdered(mapping, codeBreak, span, i);
            }
            if (codeBreak) {
                mapping[i].flags |= CODE_BREAK;
            }
            span = 1;
        } else {
            span++;
        }
    }
    mapping[i - span].span = span;
    if (span > 1)
        setOrdered(mapping, 1, span, i);
    else
        mapping[i - span].ordered = 0;

    /* Coalesce ranges */
    nSegments = 0;
    total = span = mapping[0].span;
    lastOrdered = mapping[0].ordered;
    for (i = span; i < h->mapping.cnt; i += span) {
        int codeBreak = mapping[i].flags & CODE_BREAK;
        ordered = mapping[i].ordered;
        span = mapping[i].span;
        /* merge a segment with the previous segment if                   */
        /* 1) it does not start with a code break, and                    */
        /* 2) it is not ordered, and the previous segment is not ordered. */
        if ((!codeBreak) && (!ordered) && (!lastOrdered)) {
            mapping[i - total].span += span;
            total = mapping[i - total].span;
            lastOrdered = ordered;
        } else {
            lastOrdered = mapping[i].ordered;
            total = span;
            nSegments++;
        }
    }
    mapping[i - total].span = total;
    mapping[i - total].ordered = ordered;
    nSegments += 2; /* Add last segment and sentinel */

    /* Previously, 'ordered' has been used to mean "keep this as a segment, */
    /* and don't merge it with the prior segment or following segment". Now */
    /* we need to set it to really mean 'the glyphID's in this segment are  */
    /* sequential', as this field is used to decide how to use idDelta and  */
    /* idRange.                                                             */
    for (i = 0; i < h->mapping.cnt; i += span) {
        int j;
        int ordered = 1;
        span = mapping[i].span;
        for (j = i + 1; j < i + span; j++) {
            if (mapping[j].glyphId - 1 != mapping[j - 1].glyphId) {
                ordered = 0;
                break;
            }
        }
        mapping[i].ordered = ordered;
    }

    return nSegments;
}

/* Make format 4 segment */
static void makeSegment4(hotCtx g, Format4 *fmt, int nSegments, int segment,
                         Mapping *mapping, int start) {
    int i;
    int end = start + mapping[start].span - 1;

    fmt->endCode[segment] = (unsigned short)mapping[end].code;
    fmt->startCode[segment] = (unsigned short)mapping[start].code;

    if (mapping[start].ordered) {
        fmt->idDelta[segment] = mapping[start].glyphId - (unsigned short)mapping[start].code;
        fmt->idRangeOffset[segment] = 0;
    } else {
        fmt->idDelta[segment] = 0;
        fmt->idRangeOffset[segment] = (unsigned short)(uint16 * (nSegments - segment) + uint16 * fmt->glyphId.cnt);
        for (i = start; i <= end; i++) {
            *dnaNEXT(fmt->glyphId) = mapping[i].glyphId;
        }
        /* Future improvement: when you are using idRangeOffset, you do not */
        /* have to set idDelta to 0. This could be used to combine          */
        /* sequential segments that are not ordered.                        */
    }
}

/* Make format 4 cmap */
static void doRemovePUAs(hotCtx g, cmapCtx h, int isMixedByte) {
    long i;
    int nPUAs = 0;
    Mapping *mapping = h->mapping.array;

    for (i = 1; i < h->mapping.cnt; i++) {
        if ((mapping[i].code >= 0xE000L) &&
            (mapping[i].code <= 0xF8FFL)) {
            nPUAs++;
            mapping[i].flags |= CODE_DELETE;
        }
    }

    /* Remove PUA's */
    if (nPUAs > 0) {
        qsort(mapping, h->mapping.cnt, sizeof(Mapping),
              isMixedByte ? cmpMixedByteCodes : cmpCodes);
        h->mapping.cnt -= nPUAs;
    }
}

static Format4 *makeFormat4(cmapCtx h, unsigned long *length) {
    hotCtx g = h->g;
    int i;
    int nSegments = 0;
    int segment;
    int notOK = 1;
    int truncateCmap = 0;
    long tempLength;

    Mapping *mapping = h->mapping.array;
    Format4 *fmt = MEM_NEW(h->g, sizeof(Format4));
    dnaINIT(h->g->dnaCtx, fmt->glyphId, 256, 64);

    if (g->convertFlags & HOT_STUB_CMAP4)
        truncateCmap = 1;

    while (notOK) {
        fmt->glyphId.cnt = 0;
        if (h->mapping.cnt == 0) {
            Mapping *stub_mapping = dnaNEXT(h->mapping);

            stub_mapping->code = 0;
            stub_mapping->glyphId = 0;
            stub_mapping->span = 1;
            stub_mapping->ordered = 1;
            stub_mapping->flags = 0;
            nSegments = 1;
            mapping = h->mapping.array;
        }
        nSegments = partitionRanges(h, mapping);

        if (truncateCmap) {
            nSegments = 2;
        }
        fmt->endCode = MEM_NEW(h->g, sizeof(fmt->endCode[0]) * nSegments);
        fmt->startCode = MEM_NEW(h->g, sizeof(fmt->startCode[0]) * nSegments);
        fmt->idDelta = MEM_NEW(h->g, sizeof(fmt->idDelta[0]) * nSegments);
        fmt->idRangeOffset =
            MEM_NEW(h->g, sizeof(fmt->idRangeOffset[0]) * nSegments);

        segment = 0;
        for (i = 0; i < h->mapping.cnt; i += mapping[i].span) {
            if (truncateCmap && (segment >= (nSegments - 1))) {
                break;
            }
            makeSegment4(g, fmt, nSegments, segment++, mapping, i);
        }

        /* Add sentinel */
        fmt->endCode[segment] = 0xffff;
        fmt->startCode[segment] = 0xffff;
        fmt->idDelta[segment] = 1;
        fmt->idRangeOffset[segment] = 0;

        fmt->segCountX2 = nSegments * 2;
        fmt->password = 0;
        hotCalcSearchParams(uint16, nSegments, &fmt->searchRange,
                            &fmt->entrySelector, &fmt->rangeShift);
        /* hotCalcSearchParams(sizeof(fmt->endCode[0]), nSegments, &fmt->searchRange, &fmt->entrySelector, &fmt->rangeShift); */

        fmt->format = 4;
        fmt->language = h->language;
        tempLength = FORMAT4_SIZE(nSegments, fmt->glyphId.cnt);
        if (tempLength < 0x10000L) {
            notOK = 0;
            if (truncateCmap) {
                cmapMsg(g, hotWARNING, "cmap format 4 subtable was truncated to 2 segments, due to overflow with non-truncated version");
            }
        } else {
            if (truncateCmap) {
                cmapMsg(g, hotFATAL, "Length overflow in cmap format 4 subtable, despite truncating cmap to 2 segments.");
            } else {
                truncateCmap = 1;
            }
        }
    } /* end while */

    *length = fmt->length = (unsigned short)tempLength;

    return fmt;
}

/* Make format 6 cmap */
static Format6 *makeFormat6(cmapCtx h, unsigned long *length) {
    int i;
    Mapping *mapping = h->mapping.array;
    Format6 *fmt = MEM_NEW(h->g, sizeof(Format6));

    fmt->firstCode = (unsigned short)mapping[0].code;
    fmt->entryCount = (unsigned short)(mapping[h->mapping.cnt - 1].code - mapping[0].code + 1);
    fmt->glyphId = MEM_NEW(h->g, fmt->entryCount * sizeof(fmt->glyphId[0]));
    for (i = 0; i < fmt->entryCount; i++) {
        fmt->glyphId[i] = 0;
    }
    for (i = 0; i < h->mapping.cnt; i++) {
        fmt->glyphId[mapping[i].code - mapping[0].code] = mapping[i].glyphId;
    }

    fmt->format = 6;
    fmt->language = h->language;
    *length = fmt->length = FORMAT6_SIZE(fmt->entryCount);

    return fmt;
}

/* Make format 12 cmap */
static Format12 *makeFormat12(cmapCtx h, unsigned long *length) {
    long i;
    Mapping *mapping = h->mapping.array;
    long iSegBeg = 0; /* Index of beginning of segment */
    Format12 *fmt = MEM_NEW(h->g, sizeof(Format12));

    dnaINIT(h->g->dnaCtx, fmt->segment, 40, 80); /* xxx dynamic numbers? */

    for (i = 1; i <= h->mapping.cnt; i++) {
        if (i == h->mapping.cnt || mapping[i].code != mapping[i - 1].code + 1 || mapping[i].glyphId != mapping[i - 1].glyphId + 1) {
            /* Segment starts at [iSegBeg] and ends at [i-1]. Create it: */
            Segment8_12 *seg = dnaNEXT(fmt->segment);

            seg->startCharCode = mapping[iSegBeg].code;
            seg->endCharCode = mapping[i - 1].code;
            seg->startGlyphID = mapping[iSegBeg].glyphId;

            iSegBeg = i;
        }
    }

    fmt->format = 12;
    fmt->reserved = 0;
    fmt->length = FORMAT12_SIZE(fmt->segment.cnt);
    fmt->language = h->language;
    fmt->nGroups = fmt->segment.cnt;

    if (length) {
        *length = fmt->length;
    }

    return fmt;
}

/* Make format 14 cmap */
static Format14 *makeFormat14(cmapCtx h) {
    long i;
    Format14 *fmt = MEM_NEW(h->g, sizeof(Format14));

    fmt->format = 14;
    fmt->length = (uint16 + 2 * uint32);                   /* size of header*/
    fmt->length += h->uvsList.cnt * (uint24 + 2 * uint32); /* size of Variation Selector list */
    for (i = 0; i < h->uvsList.cnt; i++) {
        UVSRecord *uvsRec = &h->uvsList.array[i];
        if (uvsRec->dfltUVS.cnt > 0) {
            fmt->length += uint32 + uvsRec->dfltUVS.cnt * (uint24 + uint8); /* size of UV range list for this UVS record */
        }
        if (uvsRec->extUVS.cnt > 0) {
            fmt->length += uint32 + uvsRec->extUVS.cnt * (uint24 + uint16); /* size of UV range list for this UVS record */
        }
    }

    fmt->numUVS = h->uvsList.cnt;
    return fmt;
}

/* Free format 0 cmap */
static void freeFormat0(hotCtx g, Format0 *fmt) {
    MEM_FREE(g, fmt);
}

/* Free format 2 cmap */
static void freeFormat2(hotCtx g, Format2 *fmt) {
    dnaFREE(fmt->segment);
    dnaFREE(fmt->glyphId);
    MEM_FREE(g, fmt);
}

/* Free format 4 cmap */
static void freeFormat4(hotCtx g, Format4 *fmt) {
    MEM_FREE(g, fmt->endCode);
    MEM_FREE(g, fmt->startCode);
    MEM_FREE(g, fmt->idDelta);
    MEM_FREE(g, fmt->idRangeOffset);
    dnaFREE(fmt->glyphId);
    MEM_FREE(g, fmt);
}

/* Free format 6 cmap */
static void freeFormat6(hotCtx g, Format6 *fmt) {
    MEM_FREE(g, fmt->glyphId);
    MEM_FREE(g, fmt);
}

/* Free format 12 cmap */
static void freeFormat12(hotCtx g, Format12 *fmt) {
    dnaFREE(fmt->segment);
    MEM_FREE(g, fmt);
}

/* Free format 14 cmap */
static void freeFormat14(hotCtx g, Format14 *fmt) {
    cmapCtx h = g->ctx.cmap;
    long i = 0;
    while (i < h->uvsList.cnt) {
        UVSRecord *uvsRec = &h->uvsList.array[i];
        dnaFREE(uvsRec->dfltUVS);
        dnaFREE(uvsRec->extUVS);
        i++;
    }
    MEM_FREE(g, fmt);
}

/* Check for duplicate codes. Client-specified Mac C0 control mappings take
   priority to the ones added by this module. */
static void checkDuplicates(hotCtx g, cmapCtx h, int isMixedByte) {
    long i;
    int nDupes = 0;
    Mapping *mapping = h->mapping.array;

    for (i = 1; i < h->mapping.cnt; i++) {
        if (mapping[i - 1].code == mapping[i].code &&
            (mapping[i - 1].flags & CODE_1BYTE) ==
                (mapping[i].flags & CODE_1BYTE)) {
            if (mapping[i - 1].flags & CODE_MACCNTL) {
                nDupes++;
                mapping[i - 1].flags |= CODE_DELETE;
            } else {
                featGlyphDump(g, mapping[i - 1].glyphId, ',', 0);
                featGlyphDump(g, mapping[i].glyphId, 0, 0);
                cmapMsg(g, hotFATAL, "multiple glyphs (%s) mapped to code <%lX>",
                        g->note.array, mapping[i].code);
            }
        }
    }

    /* Remove duplicates */
    if (nDupes > 0) {
        qsort(mapping, h->mapping.cnt, sizeof(Mapping),
              isMixedByte ? cmpMixedByteCodes : cmpCodes);
        h->mapping.cnt -= nDupes;
    }
}

/* Finish an encoding (adding special Mac C0 controls, if necessary), select
   its format, make the subtable. If no mappings were added, it returns 0 (no
   Encoding will be created); otherwise, 1. */
int cmapEndEncoding(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    unsigned long format4Size;
    Encoding *encoding;
    int isMixedByte;

    if (h->mapping.cnt == 0) {
        cmapMsg(g, hotWARNING, "empty encoding");

        if (h->platformId == cmap_MAC) {
            /* Mac cmap is required; the space wasn't present, so: */
            cmapAddMapping(g, 0, GID_NOTDEF, 1);
        } else if (!((IS_CID(g)) && (h->platformId == cmap_MS) && (h->scriptId == cmap_MS_UGL))) {
            /* Ms cmap is required; the space wasn't present, so: */
            return 0;
        }
    }

    encoding = dnaNEXT(h->tbl.encoding);

    /* Assign id's incrementally */
    encoding->id = (h->tbl.encoding.cnt == 1) ? 0 : (encoding - 1)->id + 1;

    encoding->platformId = h->platformId;
    encoding->scriptId = h->scriptId;

    isMixedByte = h->flags & SEEN_1BYTE && h->flags & SEEN_2BYTE;
    qsort(h->mapping.array, h->mapping.cnt, sizeof(Mapping),
          isMixedByte ? cmpMixedByteCodes : cmpCodes);
    checkDuplicates(g, h, isMixedByte);

#if 0 /* xxx Remove after testing */
    if (encoding->platformId == 1) {
        long i;
        for (i = 0; i < h->mapping.cnt; i++) {
            Mapping *map = &h->mapping.array[i];
            if (map->flags & CODE_1BYTE) {
                printf("[%02hx]=%hu\n", map->code, map->glyphId);
            } else {
                printf("[%04hx]=%hu\n", map->code, map->glyphId);
            }
        }
    }
#endif

    /* Choose most suitable format from 0, 2, 4, 6, or 12 */
    if (isMixedByte) {
        if (h->platformId != cmap_MAC) {
            cmapMsg(g, hotWARNING, "platform %hd cmap is mixed-byte",
                    h->platformId);
        }
        encoding->format = makeFormat2(h);
    } else if (h->platformId == cmap_MS) {
        /* Microsoft platform always has format restrictions! */

        if (h->flags & SEEN_1BYTE) {
            cmapMsg(g, hotWARNING, "single-byte codes in MS platform");
        }

        if (h->flags & SEEN_4BYTE) {
            if (h->flags & SEEN_2BYTE) {
                cmapMsg(g, hotWARNING, "2-byte codes in 4-byte MS encoding");
            }

            encoding->format = makeFormat12(h, NULL);
        } else {
            encoding->format = makeFormat4(h, &format4Size);
        }
    } else if (h->maxCode > 0xFFFF) {
        cmapMsg(g, hotFATAL, "[internal] maxCode > 0xFFFF");
    } else {
        /* Choose between formats 0, 4 and 6, if possible and allowed.         */
        /* o ATM/NT 4.1 and NT 5 restrictions: custom cmap must be format 0 or */
        /*   6, but due to a bug in the NT 5 driver's format 6-reading code,   */
        /*   choose 0 whenever possible, even if 6 would be smaller in size.   */
        /* o InDesign 1.0 CoolType restrictions: supports only format 0 or 6   */
        /*   Mac cmap.                                                         */

        int format4Selected = 0;
        unsigned long format4Size = ULONG_MAX; /* Denotes fmt 4 not allowed */
        Format4 *format4 =
            (h->platformId == cmap_MAC || h->platformId == cmap_CUSTOM)
                ? NULL
                : makeFormat4(h, &format4Size);
        unsigned long format6Size =
            FORMAT6_SIZE(h->mapping.array[h->mapping.cnt - 1].code -
                         h->mapping.array[0].code + 1);

        if (h->maxCode < 256 && h->maxGlyphId < 256 /* i.e. if fmt 0 possible */
            && (h->platformId == cmap_CUSTOM ||
                (FORMAT0_SIZE <= format4Size && FORMAT0_SIZE <= format6Size))) {
            encoding->format = makeFormat0(h);
        } else if (format4Size < format6Size) {
#if HOT_DEBUG
            cmapMsg(g, hotNOTE, "format 4 cmap created");
#endif
            format4Selected = 1;
            encoding->format = format4;
        } else {
            encoding->format = makeFormat6(h, &format6Size);

            /* A client may want to intercept this note if it wants to warn */
            /* customers that the font won't work in NT 5.                  */
            if (h->platformId == cmap_CUSTOM) {
                cmapMsg(g, hotNOTE, "format 6 custom cmap created");
            }
        }
    }

    return 1;
}

void cmapEndUVSEncoding(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    Encoding *encoding;

    encoding = dnaNEXT(h->tbl.encoding);

    /* Assign id's incrementally */
    encoding->id = (h->tbl.encoding.cnt == 1) ? 0 : (encoding - 1)->id + 1;

    encoding->platformId = h->platformId;
    encoding->scriptId = h->scriptId;
    encoding->format = makeFormat14(h);
}

/* Create an encoding which points to the same subtable as the last Encoding
   added by cmapBeginEncoding()..cmapEndEncoding(). Note: it returns silently
   if no Encoding was previously added. */
void cmapPointToPreviousEncoding(hotCtx g, unsigned platform,
                                 unsigned platformSpecific) {
    cmapCtx h = g->ctx.cmap;
    Encoding *newEncoding;

    if (h->tbl.encoding.cnt == 0) {
        return;
    }

    newEncoding = dnaNEXT(h->tbl.encoding);
    newEncoding->id = (newEncoding - 1)->id;
    newEncoding->platformId = platform;
    newEncoding->scriptId = platformSpecific;
    newEncoding->format = NULL; /* Indicates a "link" */
}

/* Compare encodings */
static int CDECL cmpEncodings(const void *first, const void *second) {
    const Encoding *a = first;
    const Encoding *b = second;
    if (a->platformId < b->platformId) {
        return -1;
    } else if (a->platformId > b->platformId) {
        return 1;
    } else if (a->scriptId < b->scriptId) {
        return -1;
    } else if (a->scriptId > b->scriptId) {
        return 1;
    } else {
        unsigned long lang1;
        unsigned long lang2;

        if (a->format == NULL || b->format == NULL) {
            return 0;
        }

        switch (GET_FORMAT((a->format))) {
            case 14: {
                lang1 = 0;
                break;
            }

            case 8:
            case 10:
            case 12: {
                lang1 = ((FormatHdrExt *)(a->format))->language;
                break;
            }

            default:
                lang1 = ((FormatHdr *)(a->format))->language;
        }
        switch (GET_FORMAT((b->format))) {
            case 14: {
                lang2 = 0;
                break;
            }

            case 8:
            case 10:
            case 12: {
                lang2 = ((FormatHdrExt *)(b->format))->language;
                break;
            }

            default:
                lang2 = ((FormatHdr *)(b->format))->language;
        }

        if (lang1 < lang2) {
            return -1;
        } else if (lang1 > lang2) {
            return 1;
        }
        return 0;
    }
}

/* Check for Macintosh platform */
static int checkMacPlatform(cmapCtx h) {
    int i;

    for (i = 0; i < h->tbl.encoding.cnt; i++) {
        if (h->tbl.encoding.array[i].platformId == cmap_MAC) {
            return 1;
        }
    }
    return 0;
}

/* Fill all data structures in preparation for writing */
int cmapFill(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    int i;
    Encoding *encoding = h->tbl.encoding.array;
    unsigned long offset;

    if (h->tbl.encoding.cnt == 0) {
        hotMsg(h->g, hotFATAL, "no cmap table specified");
    }

    if (!checkMacPlatform(h)) {
        hotMsg(h->g, hotWARNING, "no Mac cmap specified");
    }

    h->tbl.version = cmap_VERSION;

    h->tbl.nEncodings = (unsigned short)h->tbl.encoding.cnt;
    offset = TBL_HDR_SIZE + ENCODING_SIZE * h->tbl.nEncodings;

    /* Sort encodings and fill offsets */
    qsort(encoding, h->tbl.nEncodings, sizeof(Encoding), cmpEncodings);
    for (i = 0; i < h->tbl.nEncodings; i++) {
        if (encoding[i].format != NULL) {
            unsigned short formatID;
            formatID = GET_FORMAT(encoding[i].format);
            encoding[i].offset = offset;
            switch (formatID) {
                case 14: {
                    offset += ((FormatHdr14 *)(encoding[i].format))->length;
                    break;
                }

                case 8:
                case 10:
                case 12: {
                    offset += ((FormatHdrExt *)(encoding[i].format))->length;
                    break;
                }

                default:
                    offset += ((FormatHdr *)(encoding[i].format))->length;
            }
        }
    }

    /* Second pass to fill in offsets for links */
    for (i = 0; i < h->tbl.nEncodings; i++) {
        if (encoding[i].format == NULL) {
            int j;
            for (j = 0; j < h->tbl.nEncodings; j++) {
                if (encoding[j].id == encoding[i].id &&
                    encoding[j].format != NULL) {
                    encoding[i].offset = encoding[j].offset;
                    break;
                }
            }
        }
    }

    return 1;
}

/* Write format 0 cmap */
static void writeFormat0(cmapCtx h, Format0 *fmt) {
    int i;

    OUT2(fmt->format);
    OUT2(fmt->length);
    OUT2(fmt->language);

    for (i = 0; i < 256; i++) {
        OUT1(fmt->glyphId[i]);
    }
}

/* Write format 2 cmap */
static void writeFormat2(cmapCtx h, Format2 *fmt) {
    int i;

    OUT2(fmt->format);
    OUT2(fmt->length);
    OUT2(fmt->language);

    for (i = 0; i < 256; i++) {
        OUT2(fmt->segmentKeys[i]);
    }

    for (i = 0; i < fmt->segment.cnt; i++) {
        Segment2 *segment = &fmt->segment.array[i];

        OUT2(segment->firstCode);
        OUT2(segment->entryCount);
        OUT2(segment->idDelta);
        OUT2(segment->idRangeOffset);
    }

    for (i = 0; i < fmt->glyphId.cnt; i++) {
        OUT2(fmt->glyphId.array[i]);
    }
}

/* Write format 4 cmap */
static void writeFormat4(cmapCtx h, Format4 *fmt) {
    int i;
    int nSegments = fmt->segCountX2 / 2;
    GID *glyphId = fmt->glyphId.array;

    OUT2(fmt->format);
    OUT2(fmt->length);
    OUT2(fmt->language);
    OUT2(fmt->segCountX2);
    OUT2(fmt->searchRange);
    OUT2(fmt->entrySelector);
    OUT2(fmt->rangeShift);

    for (i = 0; i < nSegments; i++) {
        OUT2(fmt->endCode[i]);
    }

    OUT2(fmt->password);

    for (i = 0; i < nSegments; i++) {
        OUT2(fmt->startCode[i]);
    }

    for (i = 0; i < nSegments; i++) {
        OUT2(fmt->idDelta[i]);
    }

    for (i = 0; i < nSegments; i++) {
        OUT2(fmt->idRangeOffset[i]);
    }

    for (i = 0; i < fmt->glyphId.cnt; i++) {
        OUT2(glyphId[i]);
    }
}

/* Write format 6 cmap */
static void writeFormat6(cmapCtx h, Format6 *fmt) {
    int i;

    OUT2(fmt->format);
    OUT2(fmt->length);
    OUT2(fmt->language);
    OUT2(fmt->firstCode);
    OUT2(fmt->entryCount);

    for (i = 0; i < fmt->entryCount; i++) {
        OUT2(fmt->glyphId[i]);
    }
}

/* Write format 12 cmap */
static void writeFormat12(cmapCtx h, Format12 *fmt) {
    long i;

    OUT2(fmt->format);
    OUT2(fmt->reserved);
    OUT4(fmt->length);
    OUT4(fmt->language);
    OUT4(fmt->nGroups);

    for (i = 0; i < fmt->segment.cnt; i++) {
        Segment8_12 *seg = &fmt->segment.array[i];

        OUT4(seg->startCharCode);
        OUT4(seg->endCharCode);
        OUT4(seg->startGlyphID);
    }
}

/* Write format 12 cmap */
static void writeFormat14(cmapCtx h, Format14 *fmt) {
    long i;
    hotCtx g = h->g;
    unsigned long numDefaultUVSEntries = 0;
    unsigned long numExtUVSEntries = 0;
    unsigned long offset = uint16 + 2 * uint32 + h->uvsList.cnt * (uint24 + 2 * uint32); /* length of table to end of UVS records.*/

    OUT2(fmt->format);
    OUT4(fmt->length);
    OUT4(fmt->numUVS);
    for (i = 0; i < h->uvsList.cnt; i++) {
        UVSRecord *uvsRec = dnaINDEX(h->uvsList, i);

        OUT3(uvsRec->uvs);
        if (uvsRec->dfltUVS.cnt == 0) {
            OUT4(0);
        } else {
            OUT4(offset);
            numDefaultUVSEntries += uvsRec->dfltUVS.cnt;
            offset += uint32 + (uvsRec->dfltUVS.cnt * (uint24 + uint8));
        }

        if (uvsRec->extUVS.cnt == 0) {
            OUT4(0);
        } else {
            OUT4(offset);
            numExtUVSEntries += uvsRec->extUVS.cnt;
            offset += uint32 + (uvsRec->extUVS.cnt * (uint24 + uint16));
        }
    }

    /* Now write out the tables. */
    for (i = 0; i < h->uvsList.cnt; i++) {
        UVSRecord *uvsRec = dnaINDEX(h->uvsList, i);

        if (uvsRec->dfltUVS.cnt > 0) {
            int j;
            OUT4(uvsRec->dfltUVS.cnt);
            for (j = 0; j < uvsRec->dfltUVS.cnt; j++) {
                DefaultUVSEntryRange *dfltUVSER = dnaINDEX(uvsRec->dfltUVS, j);
                OUT3(dfltUVSER->uv);
                OUT1(dfltUVSER->addtlCnt);
                numDefaultUVSEntries += dfltUVSER->addtlCnt;
            }
        }

        if (uvsRec->extUVS.cnt > 0) {
            int j;
            OUT4(uvsRec->extUVS.cnt);
            for (j = 0; j < uvsRec->extUVS.cnt; j++) {
                ExtUVSEntry *exttUVSER = dnaINDEX(uvsRec->extUVS, j);
                OUT3(exttUVSER->uv);
                OUT2(exttUVSER->glyphID);
            }
        }
    }
    cmapMsg(g, hotNOTE, "Number of default Unicode Variation Sequence values %lu", numDefaultUVSEntries);
    cmapMsg(g, hotNOTE, "Number of non-default UVS values %lu", numExtUVSEntries);
}

void cmapWrite(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    int i;

    OUT2(h->tbl.version);
    OUT2(h->tbl.nEncodings);

    for (i = 0; i < h->tbl.nEncodings; i++) {
        Encoding *encoding = &h->tbl.encoding.array[i];

        OUT2(encoding->platformId);
        OUT2(encoding->scriptId);
        OUT4(encoding->offset);
    }

    for (i = 0; i < h->tbl.nEncodings; i++) {
        void *format = h->tbl.encoding.array[i].format;

        if (format == NULL) {
            continue;
        }

        switch (GET_FORMAT(format)) {
            case 0:
                writeFormat0(h, format);
                break;

            case 2:
                writeFormat2(h, format);
                break;

            case 4:
                writeFormat4(h, format);
                break;

            case 6:
                writeFormat6(h, format);
                break;

            case 12:
                writeFormat12(h, format);
                break;

            case 14:
                writeFormat14(h, format);
                break;
        }
    }
}

void cmapReuse(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    int i;

    for (i = 0; i < h->tbl.nEncodings; i++) {
        void *format = h->tbl.encoding.array[i].format;

        if (format == NULL) {
            continue;
        }

        switch (GET_FORMAT(format)) {
            case 0:
                freeFormat0(g, format);
                break;

            case 2:
                freeFormat2(g, format);
                break;

            case 4:
                freeFormat4(g, format);
                break;

            case 6:
                freeFormat6(g, format);
                break;

            case 12:
                freeFormat12(g, format);
                break;

            case 14:
                freeFormat14(g, format);
                break;
        }
    }
    h->tbl.nEncodings = 0;
    h->tbl.encoding.cnt = 0;
    h->lastUVS = 0;
    h->uvsList.cnt = 0;
    h->mapping.cnt = 0;
    h->codespace.cnt = 0;
}

/* Free memory for this context */
void cmapFree(hotCtx g) {
    cmapCtx h = g->ctx.cmap;
    cmapReuse(g); /* frees all the encodings in use */
    dnaFREE(h->tbl.encoding);
    dnaFREE(h->uvsList);
    dnaFREE(h->mapping);
    dnaFREE(h->codespace);
    MEM_FREE(g, h);
}
