"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnicodeBmp = void 0;
const bin_util_1 = require("@ot-builder/bin-util");
const errors_1 = require("@ot-builder/errors");
const ot_encoding_1 = require("@ot-builder/ot-encoding");
const primitive_1 = require("@ot-builder/primitive");
const general_1 = require("./general");
const unicode_encoding_collector_1 = require("./unicode-encoding-collector");
class UnicodeBmp {
    constructor() {
        this.mapping = new ot_encoding_1.Cmap.EncodingMap();
        this.key = general_1.SubtableHandlerKey.UnicodeBmp;
    }
    acceptEncoding(platform, encoding, format) {
        return platform === 3 && encoding === 1 && format === 4;
    }
    read(view, gOrd) {
        const format = view.uint16();
        errors_1.Assert.FormatSupported("subtable format", format, 4);
        const length = view.uint16();
        const language = view.uint16();
        const segCount = view.uint16() >> 1;
        const searchRange = view.uint16();
        const entrySelector = view.uint16();
        const rangeShift = view.uint16();
        const endCountParser = view.lift(14);
        const startCountParser = view.lift(16 + segCount * primitive_1.UInt16.size * 1);
        const idDeltaParser = view.lift(16 + segCount * primitive_1.UInt16.size * 2);
        const idRangeOffsetParser = view.lift(16 + segCount * primitive_1.UInt16.size * 3);
        for (let seg = 0; seg < segCount; seg += 1) {
            let glyphIndex;
            const endCount = endCountParser.uint16();
            const startCount = startCountParser.uint16();
            const idDelta = idDeltaParser.uint16();
            const idRangeOffset = idRangeOffsetParser.uint16();
            for (let c = startCount; c <= endCount; c += 1) {
                if (idRangeOffset !== 0) {
                    const addrIdRangeOffsetSeg = 16 + segCount * 6 + primitive_1.UInt16.size * seg;
                    const glyphIndexOffset = addrIdRangeOffsetSeg + (c - startCount) * primitive_1.UInt16.size + idRangeOffset;
                    glyphIndex = primitive_1.UInt16.from(view.lift(glyphIndexOffset).uint16() + idDelta);
                }
                else {
                    glyphIndex = primitive_1.UInt16.from(c + idDelta);
                }
                if (c < primitive_1.UInt16.max)
                    this.mapping.set(c, gOrd.at(glyphIndex));
            }
        }
    }
    writeOpt(cmap, gOrd, options) {
        const col = new unicode_encoding_collector_1.UnicodeEncodingCollector(cmap.unicode, gOrd, primitive_1.UInt16.max).collect();
        const writer = new CmapFormat4Writer();
        const buf = writer.getFrag(col);
        if (!buf) {
            options.forceWriteUnicodeFull = true;
            if (options.encoding.forceCmapSubtableFormatToBePresent)
                return writer.getDummy();
            else
                return null;
        }
        else {
            return buf;
        }
    }
    apply(cmap) {
        for (const [c, g] of this.mapping.entries()) {
            cmap.unicode.set(c, g);
        }
    }
    createAssignments(frag) {
        if (!frag || !frag.size)
            return [];
        return [
            { platform: 3, encoding: 1, frag },
            { platform: 0, encoding: 3, frag }
        ];
    }
}
exports.UnicodeBmp = UnicodeBmp;
class Run {
    constructor(link, unicodeStart, unicodeEnd, gidStart, gidEnd, glyphIdArray, cost) {
        this.link = link;
        this.unicodeStart = unicodeStart;
        this.unicodeEnd = unicodeEnd;
        this.gidStart = gidStart;
        this.gidEnd = gidEnd;
        this.glyphIdArray = glyphIdArray;
        this.cost = cost;
    }
    static Linked(started, link, unicode, gid) {
        if (started && !link)
            return null;
        if (!link)
            return new Run(null, unicode, unicode, gid, gid, null, primitive_1.UInt16.size * 4);
        else
            return new Run(link, unicode, unicode, gid, gid, null, link.cost + primitive_1.UInt16.size * 4);
    }
    static GrowSequent(old, unicode, gid) {
        if (!old || unicode !== old.unicodeEnd + 1 || gid !== old.gidEnd + 1)
            return null;
        return new Run(old.link, old.unicodeStart, unicode, old.gidStart, gid, null, old.cost);
    }
    static GrowRagged(old, unicode, gid) {
        if (!old || unicode !== old.unicodeEnd + 1)
            return null;
        let gidArray = old.glyphIdArray ? [...old.glyphIdArray] : null;
        let cost = old.cost;
        if (!gidArray) {
            gidArray = [];
            for (let gidJ = old.gidStart; gidJ <= old.gidEnd; gidJ++) {
                gidArray.push(gidJ);
            }
            cost += gidArray.length * primitive_1.UInt16.size;
        }
        gidArray.push(gid);
        cost += primitive_1.UInt16.size;
        return new Run(old.link, old.unicodeStart, unicode, old.gidStart, gid, gidArray, cost);
    }
}
const TrackLength = 2;
const Track = 1 << TrackLength;
const TrackRestMask = (1 << (TrackLength - 1)) - 1;
// Find out the optimal run segmentation for a CMAP format 4 subtable
class CmapSegDpState {
    constructor() {
        this.started = false;
        this.sequent = [];
        this.ragged = [];
    }
    min(...runs) {
        let best = null;
        for (const run of runs) {
            if (!run)
                continue;
            if (!best || run.cost < best.cost)
                best = run;
        }
        return best;
    }
    process(unicode, gid) {
        const sequent = [...this.sequent];
        const ragged = [...this.ragged];
        for (let tr = 0; tr < Track; tr++)
            this.sequent[tr] = this.ragged[tr] = null;
        for (let mode = 0; mode < Track * 2; mode++) {
            const original = mode >>> 1;
            const track = ((original & TrackRestMask) << 1) | (mode & 1);
            if (mode & 1) {
                this.sequent[track] = this.min(this.sequent[track], Run.GrowSequent(sequent[original], unicode, gid));
                this.ragged[track] = this.min(this.ragged[track], Run.GrowRagged(ragged[original], unicode, gid));
            }
            else {
                const basis = this.min(sequent[original], ragged[original]);
                this.sequent[track] = this.ragged[track] = this.min(this.sequent[track], Run.Linked(this.started, basis, unicode, gid));
            }
        }
        this.started = true;
    }
    processForce(unicode, gid) {
        for (let tr = 0; tr < Track; tr++) {
            this.sequent[tr] = Run.Linked(this.started, this.sequent[tr], unicode, gid);
            this.ragged[tr] = Run.Linked(this.started, this.ragged[tr], unicode, gid);
        }
        this.started = true;
    }
    getChainStart() {
        if (!this.sequent || !this.ragged)
            throw errors_1.Errors.Unreachable();
        return this.min(...this.sequent, ...this.ragged);
    }
    getChain() {
        const chain = [];
        let start = this.getChainStart();
        while (start) {
            chain.push(start);
            start = start.link;
        }
        return chain.reverse();
    }
}
class CmapFormat4Writer {
    constructor() {
        this.runs = [];
    }
    iterateSegments(collected) {
        const dp = new CmapSegDpState();
        for (const [unicode, gid] of collected) {
            dp.process(unicode, gid);
        }
        dp.processForce(primitive_1.UInt16.max, 0);
        this.runs = dp.getChain();
    }
    collectArrays() {
        const endCode = [];
        const startCode = [];
        const idDelta = [];
        const idRangeOffsets = [];
        const glyphIdArray = [];
        for (let rid = 0; rid < this.runs.length; rid++) {
            const run = this.runs[rid];
            endCode.push(run.unicodeEnd);
            startCode.push(run.unicodeStart);
            if (!run.glyphIdArray || !run.glyphIdArray.length) {
                idDelta.push(primitive_1.Int16.from(run.gidStart - run.unicodeStart));
                idRangeOffsets.push(0);
            }
            else {
                errors_1.Assert.SizeMatch(`CMAP format 4 run glyph id array`, run.glyphIdArray.length, run.unicodeEnd - run.unicodeStart + 1);
                const idRgOffset = primitive_1.UInt16.size * (glyphIdArray.length + (this.runs.length - rid));
                if (idRgOffset != primitive_1.UInt16.from(idRgOffset)) {
                    // idRangeOffset overflows -- this only happens at crafted situations.
                    return null;
                }
                idRangeOffsets.push(idRgOffset);
                for (const gid of run.glyphIdArray)
                    glyphIdArray.push(gid);
                idDelta.push(0);
            }
        }
        return { endCode, startCode, idDelta, idRangeOffsets, glyphIdArray };
    }
    computeSearchRange(sc) {
        let searchRange = 0;
        let entrySelector = 0;
        for (entrySelector = 0, searchRange = 1; searchRange <= sc; ++entrySelector) {
            searchRange <<= 1;
        }
        return { searchRange, entrySelector: entrySelector - 1, rangeShift: 2 * sc - searchRange };
    }
    makeTarget(ca) {
        const { endCode, startCode, idDelta, idRangeOffsets, glyphIdArray } = ca;
        const fr = new bin_util_1.Frag();
        fr.uint16(4);
        const hLength = fr.reserve(primitive_1.UInt16);
        fr.uint16(0); // language -- set to 0
        fr.uint16(endCode.length * 2);
        const sr = this.computeSearchRange(endCode.length);
        fr.uint16(sr.searchRange);
        fr.uint16(sr.entrySelector);
        fr.uint16(sr.rangeShift);
        fr.array(primitive_1.UInt16, endCode);
        fr.uint16(0);
        fr.array(primitive_1.UInt16, startCode);
        fr.array(primitive_1.Int16, idDelta);
        fr.array(primitive_1.UInt16, idRangeOffsets);
        fr.array(primitive_1.UInt16, glyphIdArray);
        hLength.fill(fr.size);
        return fr;
    }
    getDummy() {
        return this.makeTarget({
            endCode: [0xffff],
            startCode: [0xffff],
            idDelta: [1],
            idRangeOffsets: [0],
            glyphIdArray: []
        });
    }
    getFrag(collected) {
        if (!collected || !collected.length)
            return null;
        this.iterateSegments(collected);
        if (this.runs.length > primitive_1.Int16.max)
            return null;
        const ca = this.collectArrays();
        if (!ca)
            return null;
        return this.makeTarget(ca);
    }
}
//# sourceMappingURL=unicode-bmp.js.map