"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.codeGenGlyph = codeGenGlyph;
const ot_glyphs_1 = require("@ot-builder/ot-glyphs");
const variance_1 = require("@ot-builder/variance");
const operator_1 = require("../../interp/operator");
const draw_call_1 = require("./draw-call");
class CffGlyphHandler {
    constructor(widthHandler, st) {
        this.widthHandler = widthHandler;
        this.st = st;
        this.geometryAlgebra = new CffGeometryHandler(st);
        this.hintAlgebra = new CffHintHandler(st);
    }
    process(glyph) {
        if (glyph.hints)
            this.hintAlgebra.process(glyph.hints);
        if (glyph.geometry)
            this.geometryAlgebra.process(glyph.geometry);
        this.processWidth(glyph);
    }
    processWidth(glyph) {
        if (!this.widthHandler)
            return;
        const width = variance_1.OtVar.Ops.minus(glyph.horizontal.end, glyph.horizontal.start);
        if (variance_1.OtVar.Ops.equal(width, this.widthHandler.defaultWidthX, 1 / 0x10000))
            return;
        const arg = variance_1.OtVar.Ops.minus(width, this.widthHandler.nominalWidthX);
        if (!this.st.rawDrawCalls.length) {
            this.st.pushRawCall(new draw_call_1.CffDrawCallRaw([arg], operator_1.CharStringOperator.EndChar));
        }
        else {
            this.st.rawDrawCalls[0] = new draw_call_1.CffDrawCallRaw([arg, ...this.st.rawDrawCalls[0].args], this.st.rawDrawCalls[0].operator, this.st.rawDrawCalls[0].flags);
        }
    }
}
function byMaskPosition(a, b) {
    return ot_glyphs_1.OtGlyph.PointRef.compare(a.at, b.at);
}
class CffCodeGenState {
    constructor() {
        // Compilation
        this.rawDrawCalls = [];
        this.cx = 0;
        this.cy = 0;
        this.maskPr = { geometry: 0, contour: 0, index: 0 };
        this.maskIndex = 0;
        this.masks = [];
        // Stats
        this.statContours = 0;
        this.statPoints = 0;
        this.bBoxStat = new ot_glyphs_1.OtGlyph.Stat.BoundingBoxBuilder();
    }
    advance(geom, contour, knot) {
        if (geom) {
            this.maskPr.geometry += geom;
            this.maskPr.contour = 0;
            this.maskPr.index = 0;
        }
        if (contour) {
            this.maskPr.contour += contour;
            this.maskPr.index = 0;
        }
        this.maskPr.index += knot;
        while (this.maskIndex < this.masks.length
            && ot_glyphs_1.OtGlyph.PointRef.compare(this.masks[this.maskIndex].at, this.maskPr) <= 0) {
            const mask = this.masks[this.maskIndex];
            this.maskIndex += 1;
            this.addMask(mask);
        }
    }
    addMask(mask) {
        if (this.rawDrawCalls.length
            && (this.rawDrawCalls[this.rawDrawCalls.length - 1].operator
                === operator_1.CharStringOperator.VStem
                || this.rawDrawCalls[this.rawDrawCalls.length - 1].operator
                    === operator_1.CharStringOperator.VStemHM)) {
            this.rawDrawCalls[this.rawDrawCalls.length - 1] = {
                args: this.rawDrawCalls[this.rawDrawCalls.length - 1].args,
                operator: mask.isCounter
                    ? operator_1.CharStringOperator.CntrMask
                    : operator_1.CharStringOperator.HintMask,
                flags: mask.flags
            };
        }
        else {
            if (mask.isCounter) {
                this.pushRawCall(new draw_call_1.CffDrawCallRaw([], operator_1.CharStringOperator.CntrMask, mask.flags));
            }
            else {
                this.pushRawCall(new draw_call_1.CffDrawCallRaw([], operator_1.CharStringOperator.HintMask, mask.flags));
            }
        }
    }
    pushRawCall(ir) {
        this.rawDrawCalls.push(ir);
    }
    getDrawCalls(ctx) {
        return draw_call_1.CffDrawCall.charStringSeqFromRawSeq(ctx, this.rawDrawCalls);
    }
    addContourStat(nPoints) {
        this.statContours += 1;
        this.statPoints += nPoints;
    }
    getStat() {
        return {
            eigenContours: this.statContours,
            eigenPoints: this.statPoints,
            extent: this.bBoxStat.getResult(),
            depth: 0
        };
    }
}
class CffHintHandler {
    constructor(st) {
        this.st = st;
    }
    process(h) {
        if (h.type === ot_glyphs_1.OtGlyph.HintType.CffHint) {
            const hasHints = h.hStems.length || h.vStems.length;
            const hasHintMask = hasHints && h.hintMasks.length;
            const hasCounterMask = hasHints && h.counterMasks.length;
            this.pushStemList(hasHintMask ? operator_1.CharStringOperator.HStemHM : operator_1.CharStringOperator.HStem, h.hStems);
            this.pushStemList(hasHintMask ? operator_1.CharStringOperator.VStemHM : operator_1.CharStringOperator.VStem, h.vStems);
            if (hasHintMask || hasCounterMask) {
                for (const mask of h.counterMasks) {
                    this.st.masks.push(this.makeCtMask(h, true, mask));
                }
                for (const mask of h.hintMasks) {
                    this.st.masks.push(this.makeCtMask(h, false, mask));
                }
                this.st.masks.sort(byMaskPosition);
            }
        }
    }
    pushStemList(op, stemList) {
        if (!stemList.length)
            return;
        let current = 0;
        const args = [];
        for (const s of stemList) {
            const arg1 = variance_1.OtVar.Ops.minus(s.start, current);
            const arg2 = variance_1.OtVar.Ops.minus(s.end, s.start);
            current = s.end;
            args.push(arg1, arg2);
        }
        this.st.pushRawCall(new draw_call_1.CffDrawCallRaw(args, op));
    }
    makeCtMask(h, contour, mask) {
        const flags = [];
        for (const s of h.hStems) {
            if (mask.maskH.has(s))
                flags.push(1);
            else
                flags.push(0);
        }
        for (const s of h.vStems) {
            if (mask.maskV.has(s))
                flags.push(1);
            else
                flags.push(0);
        }
        return { at: mask.at, isCounter: contour, flags };
    }
}
class CffGeometryHandler {
    constructor(st) {
        this.st = st;
    }
    process(geom) {
        switch (geom.type) {
            case ot_glyphs_1.OtGlyph.GeometryType.ContourSet:
                this.st.advance(0, 0, 0);
                for (const contour of geom.contours) {
                    const ch = new CffContourHandler(this.st);
                    ch.begin();
                    ch.visitContour(contour);
                    ch.end();
                }
                this.st.advance(1, 0, 0);
        }
    }
}
// A contour handler holds a state machine that processes off-curve control knots knot-by-knot.
class CffContourHandler {
    constructor(st) {
        this.st = st;
        // Internal states
        this.knotsHandled = 0;
        this.pendingKnots = [];
        this.firstKnot = null;
    }
    begin() {
        this.st.advance(0, 0, 0);
    }
    visitContour(c) {
        for (const z of c)
            this.addKnotImpl(z);
    }
    addKnotImpl(knot) {
        if (!this.knotsHandled)
            this.firstKnot = knot;
        if (knot.kind === ot_glyphs_1.OtGlyph.PointType.Lead && this.pendingKnots.length === 0) {
            this.pendingKnots.push(knot);
        }
        else if (knot.kind === ot_glyphs_1.OtGlyph.PointType.Follow && this.pendingKnots.length === 1) {
            this.pendingKnots.push(knot);
        }
        else if (knot.kind === ot_glyphs_1.OtGlyph.PointType.Corner && this.pendingKnots.length === 2) {
            this.pushCurve(this.pendingKnots[0], this.pendingKnots[1], knot);
            this.pendingKnots.length = 0;
        }
        else {
            for (const pk of this.pendingKnots)
                this.pushCorner(pk);
            this.pendingKnots.length = 0;
            this.pushCorner(knot);
        }
        this.knotsHandled += 1;
    }
    pushCorner(a) {
        this.st.bBoxStat.addPoint(variance_1.OtVar.Ops.originOf(a.x), variance_1.OtVar.Ops.originOf(a.y));
        const dx = variance_1.OtVar.Ops.minus(a.x, this.st.cx);
        const dy = variance_1.OtVar.Ops.minus(a.y, this.st.cy);
        this.st.cx = a.x;
        this.st.cy = a.y;
        if (!this.knotsHandled) {
            this.st.pushRawCall(new draw_call_1.CffDrawCallRaw([dx, dy], operator_1.CharStringOperator.RMoveTo));
        }
        else {
            this.st.pushRawCall(new draw_call_1.CffDrawCallRaw([dx, dy], operator_1.CharStringOperator.RLineTo));
        }
        this.st.advance(0, 0, 1);
    }
    pushCurve(a, b, c) {
        this.st.bBoxStat.addBox(ot_glyphs_1.OtGlyph.Stat.bezierCurveBoundingBox(variance_1.OtVar.Ops.originOf(this.st.cx), variance_1.OtVar.Ops.originOf(this.st.cy), variance_1.OtVar.Ops.originOf(a.x), variance_1.OtVar.Ops.originOf(a.y), variance_1.OtVar.Ops.originOf(b.x), variance_1.OtVar.Ops.originOf(b.y), variance_1.OtVar.Ops.originOf(c.x), variance_1.OtVar.Ops.originOf(c.y)));
        const dxA = variance_1.OtVar.Ops.minus(a.x, this.st.cx);
        const dyA = variance_1.OtVar.Ops.minus(a.y, this.st.cy);
        const dxB = variance_1.OtVar.Ops.minus(b.x, a.x);
        const dyB = variance_1.OtVar.Ops.minus(b.y, a.y);
        const dxC = variance_1.OtVar.Ops.minus(c.x, b.x);
        const dyC = variance_1.OtVar.Ops.minus(c.y, b.y);
        this.st.cx = c.x;
        this.st.cy = c.y;
        this.st.pushRawCall(new draw_call_1.CffDrawCallRaw([dxA, dyA, dxB, dyB, dxC, dyC], operator_1.CharStringOperator.RRCurveTo));
        this.st.advance(0, 0, 3);
    }
    end() {
        // Close contour
        this.st.addContourStat(this.knotsHandled);
        if (this.firstKnot && this.pendingKnots.length) {
            this.addKnotImpl({ ...this.firstKnot, kind: ot_glyphs_1.OtGlyph.PointType.Corner });
        }
        this.st.advance(0, 1, 0);
    }
}
function codeGenGlyph(wCtx, gid, glyph, pd) {
    const st = new CffCodeGenState();
    const wh = wCtx.version > 1 ? null : pd || { defaultWidthX: 0, nominalWidthX: 0 };
    new CffGlyphHandler(wh, st).process(glyph);
    const calls = st.getDrawCalls(wCtx);
    const gStat = st.getStat();
    wCtx.stat.setMetric(gid, glyph.horizontal, glyph.vertical, gStat.extent);
    wCtx.stat.simpleGlyphStat(gStat);
    return calls;
}
//# sourceMappingURL=draw-call-gen.js.map