"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sparseShareGvarData = sparseShareGvarData;
const tslib_1 = require("tslib");
const Crypto = tslib_1.__importStar(require("node:crypto"));
const sparse_common_1 = require("./sparse-common");
// Layout:                             Viewed from font 1        Viewed font font 2
// [GVAR header of font 1   ] ◆──╮   ⎡GVAR header       ⎤
// [Shared tuples of font 1 ] ◁──┤   ⎢Shared tuples     ⎥
// [GVD #0 of font 1        ] ◁──╯   ⎢GVD of glyph 0    ⎥
// [GVAR header of font 2   ] ◆──╮   ⎢                  ⎥      ⎡GVAR header       ⎤
// [Shared tuples of font 2 ] ◁──┤   ⎢                  ⎥      ⎢Shared tuples     ⎥
// [GVD #0 of font 2        ] ◁──╯   ⎢    L A R G E     ⎥      ⎢GVD of glyph 0    ⎥
//  .......................           ⎢                  ⎥      ⎢                  ⎥
//  .......................           ⎢      G A P       ⎥      ⎢    L A R G E     ⎥
//  .......................           ⎢                  ⎥      ⎢      G A P       ⎥
//  .......................           ⎢                  ⎥      ⎢                  ⎥
// [GVD of #0 of all fonts  ] ─────── ⎢── Ignored      ──⎥ ──── ⎢── Ignored      ──⎥
// [GVD of other glyphs     ]         ⎣GVD of rest glyphs⎦      ⎣GVD of rest glyphs⎦
//
// Why duplicate the GVD of first glyph in each font? Because many font engines
// require that shared tuple list occurs right after the header, and GVD occurs
// right after shared tuple list. So we have to use the gap between GVDs to
// interlace tables together.
function sparseShareGvarData(fonts, sharing) {
    const tasks = getGvarTasks(fonts, sharing);
    if (!tasks.length)
        return;
    const shared = [];
    for (const task of tasks) {
        (0, sparse_common_1.pushGlyphs)(shared, task.fontID, task.glyphData, sharing);
    }
    const db = (0, sparse_common_1.buildDataBlock)(shared);
    let totalInitialSize = 0;
    for (const task of tasks)
        totalInitialSize += gvarInitialSize(task);
    const backBuffer = Buffer.alloc(totalInitialSize + db.dataBlock.byteLength);
    db.dataBlock.copy(backBuffer, totalInitialSize);
    let start = 0;
    for (const task of tasks) {
        const offsetLoca = start + task.headerSlice.byteLength;
        const offsetSharedTuples = offsetLoca + 4 * (task.glyphData.length + 1);
        const offsetFirstGvd = offsetSharedTuples + task.sharedTupleSlice.byteLength;
        const locaBuf = taskToLocaBuf(task, sharing, db, totalInitialSize - offsetFirstGvd);
        // Data copying
        task.headerSlice.copy(backBuffer, start);
        locaBuf.copy(backBuffer, offsetLoca);
        task.sharedTupleSlice.copy(backBuffer, offsetSharedTuples);
        task.glyphData[0].buffer.copy(backBuffer, offsetFirstGvd);
        // Amend pointers and flags
        backBuffer.writeUInt16BE(1, start + GvarFlagsOffset);
        backBuffer.writeUInt32BE(offsetSharedTuples - start, start + GvarSharedTuplesOffsetOffset);
        backBuffer.writeUInt32BE(offsetFirstGvd - start, start + GvarGvdOffsetOffset);
        // Store back
        task.table.data = backBuffer;
        task.table.start = start;
        task.table.length = backBuffer.byteLength - start;
        start += gvarInitialSize(task);
    }
}
function taskToLocaBuf(task, sharing, db, deltaOffsetOfRest) {
    const sh = sharing[task.fontID];
    const entryOffsets = [];
    for (let gid = 0; gid < task.glyphData.length; gid++) {
        entryOffsets[gid] = db.offsets[db.saGidMaps[sh[gid]].get(task.glyphData[gid].hash)];
    }
    entryOffsets.push(db.dataBlock.byteLength);
    // Amend the offsets
    entryOffsets[0] = 0;
    for (let gid = 1; gid < entryOffsets.length; gid++) {
        entryOffsets[gid] += deltaOffsetOfRest;
    }
    return (0, sparse_common_1.buildOffsetIndex)(entryOffsets, false);
}
function gvarInitialSize(task) {
    let s = 8
        + task.headerSlice.byteLength
        + 4 * task.glyphData.length
        + task.sharedTupleSlice.byteLength
        + task.glyphData[0].buffer.byteLength;
    while (s % 4)
        s++;
    return s;
}
function getGvarTasks(fonts, sharing) {
    const results = [];
    for (let fid = 0; fid < fonts.length; fid++) {
        const font = fonts[fid];
        const gvar = font.tables.get("gvar");
        if (!gvar)
            continue;
        results.push(produceGvarTask(fid, gvar));
    }
    return results;
}
const GvarHeaderSize = 2 * 6 + 4 * 2;
const GvarSharedTuplesOffsetOffset = 8;
const GvarFlagsOffset = 14;
const GvarGvdOffsetOffset = 16;
function produceGvarTask(fontID, table) {
    const headerSlice = Buffer.from(table.data.slice(0, GvarHeaderSize));
    const axisCount = headerSlice.readUInt16BE(4);
    const sharedTupleCount = headerSlice.readUInt16BE(6);
    const sharedTuplesOffset = headerSlice.readUInt32BE(GvarSharedTuplesOffsetOffset);
    const sharedTupleSlice = Buffer.from(table.data.slice(sharedTuplesOffset, sharedTuplesOffset + 2 * axisCount * sharedTupleCount));
    const glyphCount = headerSlice.readUInt16BE(12);
    const flags = headerSlice.readUInt16BE(GvarFlagsOffset);
    const gvdOffset = headerSlice.readUInt32BE(GvarGvdOffsetOffset);
    const useLongOffset = !!(flags & 1);
    const offsets = [];
    for (let j = 0; j <= glyphCount; j++) {
        if (useLongOffset) {
            offsets[j] = table.data.readUInt32BE(GvarHeaderSize + 4 * j);
        }
        else {
            offsets[j] = 2 * table.data.readUInt16BE(GvarHeaderSize + 2 * j);
        }
    }
    const glyphData = [];
    for (let j = 0; j < glyphCount; j++) {
        const offset = offsets[j], size = offsets[j + 1] - offsets[j];
        if (!size || size % 4)
            throw new Error("Unreachable! Gvar data blocks should be aligned");
        const buf = table.data.slice(gvdOffset + offset, gvdOffset + offset + size);
        glyphData[j] = { hash: computeHashBuf(buf), buffer: buf };
    }
    return { fontID, table, headerSlice, sharedTupleSlice, glyphData };
}
function computeHashBuf(buffer) {
    return Crypto.createHash("sha256").update(buffer).digest("hex");
}
//# sourceMappingURL=sparse-gvar-data-processor.js.map