"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GlyphHasher = exports.GlyphSharingRectifier = exports.SharedGlyphStore = void 0;
const Crypto = require("crypto");
const ImpLib = require("@ot-builder/common-impl");
const Ot = require("@ot-builder/ot");
class SharedGlyphProp {
    constructor(glyph, fid, priority) {
        this.glyph = glyph;
        this.fid = fid;
        this.priority = priority;
    }
    compare(b) {
        return this.fid - b.fid || this.priority - b.priority;
    }
}
class SharedGlyphStore {
    constructor() {
        this.mapping = new Map();
    }
    decideOrder() {
        return Ot.ListGlyphStoreFactory.createStoreFromList(Array.from(this.mapping.values())
            .sort((a, b) => a.compare(b))
            .map(g => g.glyph)).decideOrder();
    }
}
exports.SharedGlyphStore = SharedGlyphStore;
class GlyphSharingRectifier {
    constructor(gs) {
        this.gs = gs;
        this.rankMap = new Map();
        this.mapping = new Map();
    }
    amendHashByRank(rawHash) {
        const rank = this.rankMap.get(rawHash) || 0;
        this.rankMap.set(rawHash, rank + 1);
        return rawHash + "/" + rank;
    }
    put(g, rawHash, fid, priority) {
        const hash = this.amendHashByRank(rawHash);
        const existing = this.gs.mapping.get(hash);
        if (existing) {
            this.mapping.set(g, existing.glyph);
            this.mapping.set(existing.glyph, existing.glyph);
            return existing.glyph;
        }
        else {
            this.gs.mapping.set(hash, new SharedGlyphProp(g, fid, priority));
            this.mapping.set(g, g);
            return g;
        }
    }
    glyphRef(a) {
        return this.mapping.get(a);
    }
}
exports.GlyphSharingRectifier = GlyphSharingRectifier;
class GlyphHasher {
    constructor(session) {
        this.session = session;
        this.computed = new Map();
        this.reverse = new Map();
    }
    get(glyph) {
        return this.computed.get(glyph);
    }
    compute(glyph) {
        const existing = this.computed.get(glyph);
        if (existing)
            return existing;
        const hash = Crypto.createHash("sha256");
        this.computeImpl(glyph).transfer(hash);
        const result = hash.digest("hex");
        for (let suffix = 0;; suffix++) {
            const decidedHash = result + "." + suffix;
            if (!this.reverse.has(decidedHash)) {
                this.computed.set(glyph, decidedHash);
                this.reverse.set(decidedHash, glyph);
                return decidedHash;
            }
        }
    }
    computeImpl(glyph) {
        const geomAlg = new HashGeometry(this, this.session.vp);
        const hintAlg = new HashHinting(this, this.session.vp);
        const hr = new ImpLib.Hasher();
        const hrMetrics = hr.begin();
        hrMetrics.numbers(this.session.vp.toArrayRep(glyph.horizontal.start));
        hrMetrics.numbers(this.session.vp.toArrayRep(glyph.horizontal.end));
        hrMetrics.numbers(this.session.vp.toArrayRep(glyph.vertical.start));
        hrMetrics.numbers(this.session.vp.toArrayRep(glyph.vertical.end));
        hr.include(glyph.geometry ? geomAlg.process(glyph.geometry) : geomAlg.empty());
        hr.include(glyph.hints ? hintAlg.process(glyph.hints) : hintAlg.empty());
        return hr;
    }
}
exports.GlyphHasher = GlyphHasher;
class HashGeometry {
    constructor(gh, vp) {
        this.gh = gh;
        this.vp = vp;
    }
    process(geom) {
        switch (geom.type) {
            case Ot.Glyph.GeometryType.ContourSet:
                return this.contourSet(geom);
            case Ot.Glyph.GeometryType.GeometryList:
                return this.geometryList(geom.items.map(item => this.process(item)));
            case Ot.Glyph.GeometryType.TtReference:
                return this.ttReference(geom);
        }
    }
    empty() {
        const hr = new ImpLib.Hasher();
        hr.string("Empty");
        return hr;
    }
    contourSet(cs) {
        const hrCs = new ImpLib.Hasher();
        hrCs.string("ContourSet");
        hrCs.number(cs.contours.length);
        for (const c of cs.contours) {
            const hrC = hrCs.begin();
            hrC.string("Contour");
            hrC.number(c.length);
            for (const z of c) {
                hrC.number(z.kind);
                hrC.numbers(this.vp.toArrayRep(z.x));
                hrC.numbers(this.vp.toArrayRep(z.y));
            }
        }
        return hrCs;
    }
    ttReference(ref) {
        const hr = new ImpLib.Hasher();
        hr.string("TTReference");
        hr.string(this.gh.compute(ref.to));
        hr.number(ref.transform.xx, ref.transform.xy, ref.transform.yx, ref.transform.yy);
        hr.numbers(this.vp.toArrayRep(ref.transform.dx));
        hr.numbers(this.vp.toArrayRep(ref.transform.dy));
        hr.flag(!!ref.transform.scaledOffset, !!ref.useMyMetrics, !!ref.pointAttachment);
        if (ref.pointAttachment) {
            hr.begin().number(ref.pointAttachment.outer.pointIndex, ref.pointAttachment.inner.pointIndex);
        }
        return hr;
    }
    geometryList(items) {
        const hr = new ImpLib.Hasher();
        hr.string("GeometryList");
        hr.number(items.length);
        for (const item of items)
            hr.include(item);
        return hr;
    }
}
class HashHinting {
    constructor(gh, vp) {
        this.gh = gh;
        this.vp = vp;
    }
    process(geom) {
        switch (geom.type) {
            case Ot.Glyph.HintType.TtInstruction:
                return this.ttInstructions(geom);
            case Ot.Glyph.HintType.CffHint:
                return this.cffHint(geom);
        }
    }
    empty() {
        const hr = new ImpLib.Hasher();
        hr.string("Empty");
        return hr;
    }
    ttInstructions(tt) {
        const hr = new ImpLib.Hasher();
        hr.string("TTInstructions");
        hr.buffer(tt.instructions);
        return hr;
    }
    cffHint(hint) {
        const hr = new ImpLib.Hasher();
        hr.string("CFFHint");
        hr.include(this.stems("HStem", hint.hStems));
        hr.include(this.stems("VStem", hint.vStems));
        hr.include(this.masks("HintMasks", hint.hintMasks));
        hr.include(this.masks("CounterMasks", hint.counterMasks));
        return hr;
    }
    stems(title, stems) {
        const hr = new ImpLib.Hasher();
        hr.string(title);
        hr.number(stems.length);
        for (const stem of stems) {
            hr.numbers(this.vp.toArrayRep(stem.start));
            hr.numbers(this.vp.toArrayRep(stem.end));
        }
        return hr;
    }
    masks(title, masks) {
        const hr = new ImpLib.Hasher();
        hr.string(title);
        hr.number(masks.length);
        for (const mask of masks) {
            const hrM = hr.begin();
            hrM.number(mask.at.geometry, mask.at.geometry, mask.at.index);
            hrM.include(this.stems("MaskH", [...mask.maskH]));
            hrM.include(this.stems("MaskV", [...mask.maskV]));
        }
        return hr;
    }
}
//# sourceMappingURL=glyph-hasher.js.map