makeCmapTable function

dynamic makeCmapTable(
  1. dynamic glyphs
)

Implementation

makeCmapTable(glyphs) {
    // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit)
    var isPlan0Only = true;
    var i;

    // Check if we need to add cmap format 12 or if format 4 only is fine
    for (i = glyphs.length - 1; i > 0; i -= 1) {
        var g = glyphs.get(i);
        if (g.unicode > 65535) {
            print('Adding CMAP format 12 (needed!)');
            isPlan0Only = false;
            break;
        }
    }

    var cmapTable = [
        {"name": 'version', "type": 'USHORT', "value": 0},
        {"name": 'numTables', "type": 'USHORT', "value": isPlan0Only ? 1 : 2},

        // CMAP 4 header
        {"name": 'platformID', "type": 'USHORT', "value": 3},
        {"name": 'encodingID', "type": 'USHORT', "value": 1},
        {"name": 'offset', "type": 'ULONG', "value": isPlan0Only ? 12 : (12 + 8)}
    ];

    if (!isPlan0Only)
        cmapTable.addAll([
            // CMAP 12 header
            {"name": 'cmap12PlatformID', "type": 'USHORT', "value": 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere
            {"name": 'cmap12EncodingID', "type": 'USHORT', "value": 10},
            {"name": 'cmap12Offset', "type": 'ULONG', "value": 0}
        ]);

      cmapTable.addAll([
        // CMAP 4 Subtable
        {"name": 'format', "type": 'USHORT', "value": 4},
        {"name": 'cmap4Length', "type": 'USHORT', "value": 0},
        {"name": 'language', "type": 'USHORT', "value": 0},
        {"name": 'segCountX2', "type": 'USHORT', "value": 0},
        {"name": 'searchRange', "type": 'USHORT', "value": 0},
        {"name": 'entrySelector', "type": 'USHORT', "value": 0},
        {"name": 'rangeShift', "type": 'USHORT', "value": 0}
    ]);

    var t = new Table('cmap', cmapTable, null);

    t.segments = [];
    for (i = 0; i < glyphs.length; i += 1) {
        var glyph = glyphs.get(i);
        for (var j = 0; j < glyph.unicodes.length; j += 1) {
            addSegment(t, glyph.unicodes[j], i);
        }

        t.segments = t.segments.sort((a, b) {
            return a.start - b.start;
        });
    }

    addTerminatorSegment(t);

    var segCount = t.segments.length;
    var segCountToRemove = 0;

    // CMAP 4
    // Set up parallel segment arrays.
    var endCounts = [];
    var startCounts = [];
    var idDeltas = [];
    var idRangeOffsets = [];
    var glyphIds = [];

    // CMAP 12
    var cmap12Groups = [];

    // Reminder this loop is not following the specification at 100%
    // The specification -> find suites of characters and make a group
    // Here we're doing one group for each letter
    // Doing as the spec can save 8 times (or more) space
    for (i = 0; i < segCount; i += 1) {
        var segment = t.segments[i];

        // CMAP 4
        if (segment.end <= 65535 && segment.start <= 65535) {
            endCounts.add({"name": 'end_' + i, "type": 'USHORT', "value": segment.end});
            startCounts.add({"name": 'start_' + i, "type": 'USHORT', "value": segment.start});
            idDeltas.add({"name": 'idDelta_' + i, "type": 'SHORT', "value": segment.delta});
            idRangeOffsets.add({"name": 'idRangeOffset_' + i, "type": 'USHORT', "value": segment.offset});
            if (segment.glyphId != null) {
              glyphIds.add({"name": 'glyph_' + i, "type": 'USHORT', "value": segment.glyphId});
            }
        } else {
            // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12
            segCountToRemove += 1;
        }

        // CMAP 12
        // Skip Terminator Segment
        if (!isPlan0Only && segment.glyphIndex != null) {
            cmap12Groups.add({"name": 'cmap12Start_' + i, "type": 'ULONG', "value": segment.start});
            cmap12Groups.add({"name": 'cmap12End_' + i, "type": 'ULONG', "value": segment.end});
            cmap12Groups.add({"name": 'cmap12Glyph_' + i, "type": 'ULONG', "value": segment.glyphIndex});
        }
    }

    // CMAP 4 Subtable
    t.segCountX2 = (segCount - segCountToRemove) * 2;
    t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2;
    t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2);
    t.rangeShift = t.segCountX2 - t.searchRange;

    t.fields = t.fields.concat(endCounts);
    t.fields.push({"name": 'reservedPad', "type": 'USHORT', "value": 0});
    t.fields = t.fields.concat(startCounts);
    t.fields = t.fields.concat(idDeltas);
    t.fields = t.fields.concat(idRangeOffsets);
    t.fields = t.fields.concat(glyphIds);

    t.cmap4Length = 14 + // Subtable header
        endCounts.length * 2 +
        2 + // reservedPad
        startCounts.length * 2 +
        idDeltas.length * 2 +
        idRangeOffsets.length * 2 +
        glyphIds.length * 2;

    if (!isPlan0Only) {
        // CMAP 12 Subtable
        var cmap12Length = 16 + // Subtable header
            cmap12Groups.length * 4;

        t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length;
        t.fields = t.fields.concat([
            {"name": 'cmap12Format', "type": 'USHORT', "value": 12},
            {"name": 'cmap12Reserved', "type": 'USHORT', "value": 0},
            {"name": 'cmap12Length', "type": 'ULONG', "value": cmap12Length},
            {"name": 'cmap12Language', "type": 'ULONG', "value": 0},
            {"name": 'cmap12nGroups', "type": 'ULONG', "value": cmap12Groups.length / 3}
        ]);

        t.fields = t.fields.concat(cmap12Groups);
    }

    return t;
}