"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GlyfTableWrite = void 0;
const bin_util_1 = require("@ot-builder/bin-util");
const ImpLib = require("@ot-builder/common-impl");
const ot_glyphs_1 = require("@ot-builder/ot-glyphs");
const primitive_1 = require("@ot-builder/primitive");
const variance_1 = require("@ot-builder/variance");
const classifier_1 = require("./classifier");
const shared_1 = require("./shared");
class FlagShrinker {
    constructor(cfg) {
        this.cfg = cfg;
        this.flags = [];
        this.repeating = 0;
        this.last = 0;
        this.count = 0;
    }
    push(flag) {
        if (0 === this.count) {
            this.last = flag;
            this.flags.push(flag);
        }
        else if (flag !== this.last) {
            this.repeating = 0;
            this.last = flag;
            this.flags.push(flag);
        }
        else if (this.repeating && this.repeating < 0xff) {
            this.flags[this.flags.length - 1]++;
            this.repeating++;
        }
        else if (this.repeating === 0) {
            this.flags[this.flags.length - 1] |= shared_1.SimpleGlyphFlag.REPEAT_FLAG;
            this.flags.push(1);
            this.repeating = 1;
        }
        else {
            this.repeating = 0;
            this.last = flag;
            this.flags.push(flag);
        }
        this.count++;
    }
    finalizeAndGetFlags() {
        if (this.cfg.ttf.glyfIncludeOverlapSimpleFlag && this.flags.length) {
            this.flags[0] |= shared_1.SimpleGlyphFlag.OVERLAP_SIMPLE;
        }
        return this.flags;
    }
    static decideAndWrite(delta, flagShort, flagPosZero, fDelta) {
        let flag = 0;
        if (delta === 0) {
            flag |= flagPosZero;
        }
        else if (delta > 0 && delta < 0x100) {
            flag |= flagShort | flagPosZero;
            fDelta.uint8(delta);
        }
        else if (delta < 0 && delta > -0x100) {
            flag |= flagShort;
            fDelta.uint8(-delta);
        }
        else {
            fDelta.int16(delta);
        }
        return flag;
    }
}
// TODO: could we optimize MORE?
function collectSimpleGlyphOutlineData(sg, cfg) {
    let endPtsOfContours = -1;
    const endPtsOfContoursArray = [];
    const shrinker = new FlagShrinker(cfg);
    const fragX = new bin_util_1.Frag();
    const fragY = new bin_util_1.Frag();
    let cx = 0, cy = 0;
    for (const geom of sg.outlines) {
        for (const contour of geom.contours) {
            endPtsOfContours += contour.length;
            endPtsOfContoursArray.push(endPtsOfContours);
            for (const z of contour) {
                const px = ImpLib.Arith.Round.Coord(variance_1.OtVar.Ops.originOf(z.x));
                const py = ImpLib.Arith.Round.Coord(variance_1.OtVar.Ops.originOf(z.y));
                let flag = z.kind === ot_glyphs_1.OtGlyph.PointType.Corner ? shared_1.SimpleGlyphFlag.ON_CURVE_POINT : 0;
                flag |= FlagShrinker.decideAndWrite(px - cx, shared_1.SimpleGlyphFlag.X_SHORT_VECTOR, shared_1.SimpleGlyphFlag.X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR, fragX);
                flag |= FlagShrinker.decideAndWrite(py - cy, shared_1.SimpleGlyphFlag.Y_SHORT_VECTOR, shared_1.SimpleGlyphFlag.Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR, fragY);
                shrinker.push(flag);
                cx = px;
                cy = py;
            }
        }
    }
    return { flags: shrinker.finalizeAndGetFlags(), fragX, fragY, endPtsOfContoursArray };
}
const SimpleGlyphData = (0, bin_util_1.Write)((frag, sg, cfg) => {
    const st = sg.getStatData();
    frag.int16(st.eigenContours)
        .int16(st.extent.xMin)
        .int16(st.extent.yMin)
        .int16(st.extent.xMax)
        .int16(st.extent.yMax);
    const analysis = collectSimpleGlyphOutlineData(sg, cfg);
    for (const zid of analysis.endPtsOfContoursArray)
        frag.uint16(zid);
    frag.uint16(sg.instructions.byteLength);
    frag.bytes(sg.instructions);
    for (const flag of analysis.flags)
        frag.uint8(flag);
    frag.embed(analysis.fragX);
    frag.embed(analysis.fragY);
});
function analyzeComponent(ref) {
    let flag = 0, arg1, arg2;
    if (ref.pointAttachment) {
        arg1 = ref.pointAttachment.outer.pointIndex;
        arg2 = ref.pointAttachment.inner.pointIndex;
        if (arg1 !== primitive_1.UInt8.from(arg1) || arg2 !== primitive_1.UInt8.from(arg2)) {
            flag |= shared_1.ComponentFlag.ARG_1_AND_2_ARE_WORDS;
        }
    }
    else {
        arg1 = ImpLib.Arith.Round.Coord(variance_1.OtVar.Ops.originOf(ref.transform.dx));
        arg2 = ImpLib.Arith.Round.Coord(variance_1.OtVar.Ops.originOf(ref.transform.dy));
        flag |= shared_1.ComponentFlag.ARGS_ARE_XY_VALUES;
        if (arg1 !== primitive_1.Int8.from(arg1) || arg2 !== primitive_1.Int8.from(arg2)) {
            flag |= shared_1.ComponentFlag.ARG_1_AND_2_ARE_WORDS;
        }
    }
    if (ref.transform.scaledOffset)
        flag |= shared_1.ComponentFlag.SCALED_COMPONENT_OFFSET;
    else
        flag |= shared_1.ComponentFlag.UNSCALED_COMPONENT_OFFSET;
    if (ref.roundXyToGrid)
        flag |= shared_1.ComponentFlag.ROUND_XY_TO_GRID;
    if (ref.useMyMetrics)
        flag |= shared_1.ComponentFlag.USE_MY_METRICS;
    const { xx, xy, yx, yy } = ref.transform;
    if (xy || yx)
        flag |= shared_1.ComponentFlag.WE_HAVE_A_TWO_BY_TWO;
    else if (primitive_1.F2D14.from(xx) !== 1 || primitive_1.F2D14.from(yy) !== 1) {
        if (primitive_1.F2D14.from(xx) === primitive_1.F2D14.from(yy)) {
            flag |= shared_1.ComponentFlag.WE_HAVE_A_SCALE;
        }
        else {
            flag |= shared_1.ComponentFlag.WE_HAVE_AN_X_AND_Y_SCALE;
        }
    }
    return { flag, arg1, arg2 };
}
function writeComponentArgs(frag, flag, arg1, arg2) {
    if (shared_1.ComponentFlag.ARGS_ARE_XY_VALUES & flag) {
        if (shared_1.ComponentFlag.ARG_1_AND_2_ARE_WORDS & flag) {
            frag.int16(arg1);
            frag.int16(arg2);
        }
        else {
            frag.int8(arg1);
            frag.int8(arg2);
        }
    }
    else {
        if (shared_1.ComponentFlag.ARG_1_AND_2_ARE_WORDS & flag) {
            frag.uint16(arg1);
            frag.uint16(arg2);
        }
        else {
            frag.uint8(arg1);
            frag.uint8(arg2);
        }
    }
}
function writeComponentTransform(frag, flag, transform) {
    if (shared_1.ComponentFlag.WE_HAVE_A_SCALE & flag) {
        frag.push(primitive_1.F2D14, transform.xx);
    }
    else if (shared_1.ComponentFlag.WE_HAVE_AN_X_AND_Y_SCALE & flag) {
        frag.push(primitive_1.F2D14, transform.xx);
        frag.push(primitive_1.F2D14, transform.yy);
    }
    else if (shared_1.ComponentFlag.WE_HAVE_A_TWO_BY_TWO & flag) {
        frag.push(primitive_1.F2D14, transform.xx); // xScale
        frag.push(primitive_1.F2D14, transform.xy); // scale01
        frag.push(primitive_1.F2D14, transform.yx); // scale10
        frag.push(primitive_1.F2D14, transform.yy); // yScale
    }
}
const CompositeGlyphData = (0, bin_util_1.Write)((frag, cg, iGlyph, gOrd, extraInfoSink) => {
    const st = cg.getStatData();
    frag.int16(-1)
        .int16(st.extent.xMin)
        .int16(st.extent.yMin)
        .int16(st.extent.xMax)
        .int16(st.extent.yMax);
    for (let rid = 0; rid < cg.references.length; rid++) {
        const ref = cg.references[rid];
        let { flag, arg1, arg2 } = analyzeComponent(ref);
        if (rid === 0)
            flag |= shared_1.ComponentFlag.OVERLAP_COMPOUND; // Always set overlapping flag
        if (rid + 1 < cg.references.length)
            flag |= shared_1.ComponentFlag.MORE_COMPONENTS;
        else if (cg.instructions.byteLength)
            flag |= shared_1.ComponentFlag.WE_HAVE_INSTRUCTIONS;
        const targetGID = gOrd.reverse(ref.to);
        frag.uint16(flag);
        frag.uint16(targetGID);
        writeComponentArgs(frag, flag, arg1, arg2);
        writeComponentTransform(frag, flag, ref.transform);
        if (flag & shared_1.ComponentFlag.WE_HAVE_INSTRUCTIONS) {
            frag.uint16(cg.instructions.byteLength);
            frag.bytes(cg.instructions);
        }
        extraInfoSink.setComponentInfo(iGlyph, rid, flag, targetGID, arg1, arg2, ref.transform.xx, // xScale
        ref.transform.xy, // scale01
        ref.transform.yx, // scale10
        ref.transform.yy // yScale
        );
    }
});
exports.GlyfTableWrite = (0, bin_util_1.Write)((frag, gOrd, cfg, outLoca, stat, extraInfoSink) => {
    const sink = new StdGlyfDataSink(outLoca, frag);
    sink.begin();
    stat.setNumGlyphs(gOrd.length);
    const classifier = new classifier_1.GlyphClassifier(gOrd);
    for (let iGlyph = 0; iGlyph < gOrd.length; iGlyph++) {
        const glyph = gOrd.at(iGlyph);
        const cg = classifier.classify(glyph);
        cg.stat(stat);
        const fGlyph = new bin_util_1.Frag();
        if (cg instanceof classifier_1.SimpleGlyph) {
            fGlyph.push(SimpleGlyphData, cg, cfg);
        }
        else if (cg instanceof classifier_1.CompositeGlyph) {
            fGlyph.push(CompositeGlyphData, cg, iGlyph, gOrd, extraInfoSink);
        }
        sink.add(fGlyph);
    }
    stat.settle();
    sink.end();
});
class StdGlyfDataSink {
    constructor(loca, frag) {
        this.loca = loca;
        this.frag = frag;
        this.offset = 0;
    }
    begin() { }
    add(fGlyph) {
        this.loca.glyphOffsets.push(this.offset);
        const bGlyph = (0, bin_util_1.alignBufferSize)(bin_util_1.Frag.pack(fGlyph), shared_1.GlyfOffsetAlign);
        this.frag.bytes(bGlyph);
        this.offset += bGlyph.byteLength;
    }
    end() {
        this.loca.glyphOffsets.push(this.offset);
    }
}
//# sourceMappingURL=write.js.map