"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CffCharStringInterpreter = void 0;
exports.callCharString = callCharString;
exports.interpretCharString = interpretCharString;
exports.computeSubroutineBias = computeSubroutineBias;
const bin_util_1 = require("@ot-builder/bin-util");
const errors_1 = require("@ot-builder/errors");
const variance_1 = require("@ot-builder/variance");
const CffInterp = require("../../interp/ir");
const ir_source_1 = require("../../interp/ir-source");
const operator_1 = require("../../interp/operator");
class CffCharStringInterpreter extends CffInterp.Interpreter {
    constructor(irSource, state, subroutines, sink) {
        super();
        this.irSource = irSource;
        this.state = state;
        this.subroutines = subroutines;
        this.sink = sink;
        this.halt = false;
        this.end = false;
    }
    doOperand(x) {
        this.state.log += x + " ";
        this.state.push(x);
    }
    // Justification: This is a dispatch table and does not contain substantial complexity
    // eslint-disable-next-line complexity
    doOperator(opCode) {
        this.state.log += operator_1.CharStringOperator[opCode] + " ";
        switch (opCode) {
            // Variation
            case operator_1.CharStringOperator.VsIndex:
                return this.state.doVsIndex();
            case operator_1.CharStringOperator.Blend:
                return this.state.doBlend();
            // Hints
            case operator_1.CharStringOperator.HStem:
            case operator_1.CharStringOperator.VStem:
            case operator_1.CharStringOperator.HStemHM:
            case operator_1.CharStringOperator.VStemHM:
                return this.doStemHint(opCode === operator_1.CharStringOperator.VStem || opCode === operator_1.CharStringOperator.VStemHM, this.state.allArgs());
            case operator_1.CharStringOperator.HintMask:
            case operator_1.CharStringOperator.CntrMask:
                this.doStemHint(this.sink.stemQuantity > 0, this.state.allArgs());
                return this.sink.addHintMask(opCode === operator_1.CharStringOperator.CntrMask, this.irSource.pullFlags(this.sink.stemQuantity));
            case operator_1.CharStringOperator.RMoveTo:
            case operator_1.CharStringOperator.HMoveTo:
            case operator_1.CharStringOperator.VMoveTo:
                return this.doMove(opCode === operator_1.CharStringOperator.HMoveTo, opCode === operator_1.CharStringOperator.VMoveTo);
            // Graphics
            case operator_1.CharStringOperator.RLineTo:
                return this.doRLineTo(this.state.allArgs());
            case operator_1.CharStringOperator.HLineTo:
                return this.doHLineTo(this.state.allArgs());
            case operator_1.CharStringOperator.VLineTo:
                return this.doVLineTo(this.state.allArgs());
            case operator_1.CharStringOperator.RRCurveTo:
                return this.doRRCurveTo(this.state.allArgs());
            case operator_1.CharStringOperator.RCurveLine:
                return this.doRCurveLine(this.state.allArgs());
            case operator_1.CharStringOperator.RLineCurve:
                return this.doRLineCurve(this.state.allArgs());
            case operator_1.CharStringOperator.HHCurveTo:
                return this.doHHCurveTo(this.state.allArgs());
            case operator_1.CharStringOperator.VVCurveTo:
                return this.doVVCurveTo(this.state.allArgs());
            case operator_1.CharStringOperator.HVCurveTo:
                return this.doHVCurveTo(this.state.allArgs());
            case operator_1.CharStringOperator.VHCurveTo:
                return this.doVHCurveTo(this.state.allArgs());
            case operator_1.CharStringOperator.Flex:
                return this.doFlex(this.state.allArgs(12));
            case operator_1.CharStringOperator.HFlex:
                return this.doHFlex(this.state.allArgs(7));
            case operator_1.CharStringOperator.Flex1:
                return this.doFlex1(this.state.allArgs(11));
            case operator_1.CharStringOperator.HFlex1:
                return this.doHFlex1(this.state.allArgs(11));
            // Logic
            case operator_1.CharStringOperator.And:
                return this.doAnd();
            case operator_1.CharStringOperator.Or:
                return this.doOr();
            case operator_1.CharStringOperator.Not:
                return this.doNot();
            case operator_1.CharStringOperator.Eq:
                return this.doEq();
            // Arith
            case operator_1.CharStringOperator.Add:
                return this.doAdd();
            case operator_1.CharStringOperator.Sub:
                return this.doSub();
            case operator_1.CharStringOperator.Neg:
                return this.doNeg();
            case operator_1.CharStringOperator.Abs:
                return this.doAbs();
            case operator_1.CharStringOperator.Mul:
                return this.doMul();
            case operator_1.CharStringOperator.Div:
                return this.doDiv();
            case operator_1.CharStringOperator.Sqrt:
                return this.doSqrt();
            case operator_1.CharStringOperator.Random:
                return this.doRandom();
            // Stack manipulation
            case operator_1.CharStringOperator.Dup:
                return this.doDup();
            case operator_1.CharStringOperator.Exch:
                return this.doExch();
            case operator_1.CharStringOperator.Index:
                return this.doIndex();
            case operator_1.CharStringOperator.Roll:
                return this.doRoll();
            case operator_1.CharStringOperator.Drop:
                return this.doDrop();
            // Memory
            case operator_1.CharStringOperator.Put:
                return this.doPut();
            case operator_1.CharStringOperator.Get:
                return this.doGet();
            // Flow control
            case operator_1.CharStringOperator.IfElse:
                return this.doConditional();
            case operator_1.CharStringOperator.CallSubr:
            case operator_1.CharStringOperator.CallGSubr: {
                const subr = variance_1.OtVar.Ops.originOf(this.state.pop());
                const buf = opCode === operator_1.CharStringOperator.CallSubr
                    ? this.getLocalSubroutine(subr)
                    : this.getGlobalSubroutine(subr);
                this.halt = callCharString(buf, this.state, this.subroutines, this.sink);
                return;
            }
            case operator_1.CharStringOperator.Return:
                return this.doReturn();
            case operator_1.CharStringOperator.EndChar:
                return this.doEndChar();
            default:
                return errors_1.Errors.Cff.OperatorNotSupported(opCode);
        }
    }
    // Hinting
    doStemHint(isVertical, args) {
        if (args.length % 2) {
            this.sink.setWidth(variance_1.OtVar.Ops.add(this.subroutines.nominalWidthX, args[0]));
        }
        this.sink.stemQuantity += args.length >> 1;
        let hintBase = 0;
        for (let argId = args.length % 2; argId < args.length; argId += 2) {
            const startEdge = variance_1.OtVar.Ops.add(hintBase, args[argId]);
            const endEdge = variance_1.OtVar.Ops.add(startEdge, args[argId + 1]);
            this.sink.addStemHint(isVertical, startEdge, endEdge);
            hintBase = endEdge;
        }
    }
    // Graphics Functions
    doMove(isH, isV) {
        this.sink.startContour();
        if (!isH && !isV) {
            const [dx, dy] = this.state.args(2);
            this.sink.lineTo(dx, dy);
        }
        else {
            const [d] = this.state.args(1);
            if (isH)
                this.sink.lineTo(d, 0);
            else
                this.sink.lineTo(0, d);
        }
        if (this.state.stackHeight()) {
            this.sink.setWidth(variance_1.OtVar.Ops.add(this.subroutines.nominalWidthX, this.state.pop()));
        }
        this.state.allArgs();
    }
    doRLineTo(args) {
        for (let index = 0; index < args.length; index += 2) {
            this.sink.lineTo(args[index], args[index + 1]);
        }
    }
    doVLineTo(args) {
        if (args.length % 2 === 1) {
            this.sink.lineTo(0.0, args[0]);
            for (let index = 1; index < args.length; index += 2) {
                this.sink.lineTo(args[index], 0.0);
                this.sink.lineTo(0.0, args[index + 1]);
            }
        }
        else {
            for (let index = 0; index < args.length; index += 2) {
                this.sink.lineTo(0.0, args[index]);
                this.sink.lineTo(args[index + 1], 0.0);
            }
        }
    }
    doHLineTo(args) {
        if (args.length % 2 === 1) {
            this.sink.lineTo(args[0], 0);
            for (let index = 1; index < args.length; index += 2) {
                this.sink.lineTo(0, args[index]);
                this.sink.lineTo(args[index + 1], 0);
            }
        }
        else {
            for (let index = 0; index < args.length; index += 2) {
                this.sink.lineTo(args[index], 0);
                this.sink.lineTo(0, args[index + 1]);
            }
        }
    }
    doRRCurveTo(args) {
        for (let index = 0; index < args.length; index += 6) {
            this.sink.curveTo(args[index], args[index + 1], args[index + 2], args[index + 3], args[index + 4], args[index + 5]);
        }
    }
    doRCurveLine(args) {
        for (let index = 0; index < args.length - 2; index += 6) {
            this.sink.curveTo(args[index], args[index + 1], args[index + 2], args[index + 3], args[index + 4], args[index + 5]);
        }
        this.sink.lineTo(args[args.length - 2], args[args.length - 1]);
    }
    doRLineCurve(args) {
        for (let index = 0; index < args.length - 6; index += 2) {
            this.sink.lineTo(args[index], args[index + 1]);
        }
        {
            this.sink.curveTo(args[args.length - 6], args[args.length - 5], args[args.length - 4], args[args.length - 3], args[args.length - 2], args[args.length - 1]);
        }
    }
    doVVCurveTo(args) {
        if (args.length % 4 === 1) {
            this.sink.curveTo(args[0], args[1], args[2], args[3], 0.0, args[4]);
            for (let index = 5; index < args.length; index += 4) {
                this.sink.curveTo(0.0, args[index], args[index + 1], args[index + 2], 0.0, args[index + 3]);
            }
        }
        else {
            for (let index = 0; index < args.length; index += 4) {
                this.sink.curveTo(0.0, args[index], args[index + 1], args[index + 2], 0.0, args[index + 3]);
            }
        }
    }
    doHHCurveTo(args) {
        if (args.length % 4 === 1) {
            this.sink.curveTo(args[1], args[0], args[2], args[3], args[4], 0.0);
            for (let index = 5; index < args.length; index += 4) {
                this.sink.curveTo(args[index], 0.0, args[index + 1], args[index + 2], args[index + 3], 0.0);
            }
        }
        else {
            for (let index = 0; index < args.length; index += 4) {
                this.sink.curveTo(args[index], 0.0, args[index + 1], args[index + 2], args[index + 3], 0.0);
            }
        }
    }
    doVHCurveTo(args) {
        const bezierCount = args.length % 4 === 1 ? (args.length - 5) / 4 : args.length / 4;
        for (let index = 0; index < 4 * bezierCount; index += 4) {
            if ((index / 4) % 2 === 0) {
                this.sink.curveTo(0.0, args[index], args[index + 1], args[index + 2], args[index + 3], 0.0);
            }
            else {
                this.sink.curveTo(args[index], 0.0, args[index + 1], args[index + 2], 0.0, args[index + 3]);
            }
        }
        if (args.length % 8 === 5) {
            this.sink.curveTo(0.0, args[args.length - 5], args[args.length - 4], args[args.length - 3], args[args.length - 2], args[args.length - 1]);
        }
        else if (args.length % 8 === 1) {
            this.sink.curveTo(args[args.length - 5], 0.0, args[args.length - 4], args[args.length - 3], args[args.length - 1], args[args.length - 2]);
        }
    }
    doHVCurveTo(args) {
        const bezierCount = args.length % 4 === 1 ? (args.length - 5) / 4 : args.length / 4;
        for (let index = 0; index < 4 * bezierCount; index += 4) {
            if ((index / 4) % 2 === 0) {
                this.sink.curveTo(args[index], 0.0, args[index + 1], args[index + 2], 0.0, args[index + 3]);
            }
            else {
                this.sink.curveTo(0.0, args[index], args[index + 1], args[index + 2], args[index + 3], 0.0);
            }
        }
        if (args.length % 8 === 5) {
            this.sink.curveTo(args[args.length - 5], 0.0, args[args.length - 4], args[args.length - 3], args[args.length - 1], args[args.length - 2]);
        }
        else if (args.length % 8 === 1) {
            this.sink.curveTo(0.0, args[args.length - 5], args[args.length - 4], args[args.length - 3], args[args.length - 2], args[args.length - 1]);
        }
    }
    doFlex(args) {
        this.sink.curveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
        this.sink.curveTo(args[6], args[7], args[8], args[9], args[10], args[11]);
    }
    doHFlex(args) {
        this.sink.curveTo(args[0], 0.0, args[1], args[2], args[3], 0.0);
        this.sink.curveTo(args[4], 0.0, args[5], -args[2], args[6], 0.0);
    }
    doHFlex1(args) {
        this.sink.curveTo(args[0], args[1], args[2], args[3], args[4], 0.0);
        this.sink.curveTo(args[5], 0.0, args[6], args[7], args[8], variance_1.OtVar.Ops.negate(variance_1.OtVar.Ops.add(variance_1.OtVar.Ops.add(args[1], args[3]), args[7])));
    }
    doFlex1(args) {
        let dx = variance_1.OtVar.Ops.sum(args[0], args[2], args[4], args[6], args[8]);
        let dy = variance_1.OtVar.Ops.sum(args[1], args[3], args[5], args[7], args[9]);
        // Note: we assume that this condition won't change during variation
        if (Math.abs(variance_1.OtVar.Ops.originOf(dx)) > Math.abs(variance_1.OtVar.Ops.originOf(dy))) {
            dx = args[10];
            dy = variance_1.OtVar.Ops.negate(dy);
        }
        else {
            dx = variance_1.OtVar.Ops.negate(dx);
            dy = args[10];
        }
        this.sink.curveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
        this.sink.curveTo(args[6], args[7], args[8], args[9], dx, dy);
    }
    // LOGIC
    doAnd() {
        const [a, b] = this.state.args(2);
        this.state.push(variance_1.OtVar.Ops.originOf(a) && variance_1.OtVar.Ops.originOf(b) ? 1 : 0);
    }
    doOr() {
        const [a, b] = this.state.args(2);
        this.state.push(variance_1.OtVar.Ops.originOf(a) || variance_1.OtVar.Ops.originOf(b) ? 1 : 0);
    }
    doNot() {
        const a = this.state.pop();
        this.state.push(!variance_1.OtVar.Ops.originOf(a) ? 1 : 0);
    }
    doEq() {
        const [a, b] = this.state.args(2);
        const diff = variance_1.OtVar.Ops.minus(b, a);
        this.state.push(variance_1.OtVar.Ops.originOf(diff) ? 1 : 0);
    }
    // ARITH
    doAbs() {
        const a = this.state.pop();
        this.state.push(Math.abs(variance_1.OtVar.Ops.originOf(a)));
    }
    doAdd() {
        const [a, b] = this.state.args(2);
        this.state.push(variance_1.OtVar.Ops.add(a, b));
    }
    doSub() {
        const [a, b] = this.state.args(2);
        this.state.push(variance_1.OtVar.Ops.minus(a, b));
    }
    doNeg() {
        const a = this.state.pop();
        this.state.push(variance_1.OtVar.Ops.negate(a));
    }
    doMul() {
        const [a, b] = this.state.args(2);
        if (variance_1.OtVar.Ops.isConstant(a)) {
            this.state.push(variance_1.OtVar.Ops.scale(variance_1.OtVar.Ops.originOf(a), b));
        }
        else if (variance_1.OtVar.Ops.isConstant(b)) {
            this.state.push(variance_1.OtVar.Ops.scale(variance_1.OtVar.Ops.originOf(b), a));
        }
        else {
            this.state.push(variance_1.OtVar.Ops.originOf(a) * variance_1.OtVar.Ops.originOf(b));
        }
    }
    doDiv() {
        const [a, b] = this.state.args(2);
        if (!variance_1.OtVar.Ops.isConstant(b)) {
            this.state.push(variance_1.OtVar.Ops.originOf(a) / variance_1.OtVar.Ops.originOf(b));
        }
        else {
            this.state.push(variance_1.OtVar.Ops.scale(1 / variance_1.OtVar.Ops.originOf(b), a));
        }
    }
    doSqrt() {
        const a = this.state.pop();
        this.state.push(Math.sqrt(variance_1.OtVar.Ops.originOf(a)));
    }
    doRandom() {
        this.state.push(this.state.getRandom());
    }
    // STACK MANIPULATION
    doDrop() {
        this.state.pop();
    }
    doDup() {
        const t = this.state.pop();
        this.state.push(t);
        this.state.push(t);
    }
    doExch() {
        const [a, b] = this.state.args(2);
        this.state.push(a);
        this.state.push(b);
    }
    doIndex() {
        const index = this.state.pop();
        this.state.push(this.state.topIndex(variance_1.OtVar.Ops.originOf(index)));
    }
    doRoll() {
        const [xRollN, xRollJ] = this.state.args(2);
        const rollJ = variance_1.OtVar.Ops.originOf(xRollJ);
        const rollN = variance_1.OtVar.Ops.originOf(xRollN);
        const len = this.state.stackHeight();
        const last = len - 1;
        const first = len - rollN;
        this.state.reverseStack(first, last);
        this.state.reverseStack(last - rollJ + 1, last);
        this.state.reverseStack(first, last - rollJ);
    }
    // MEMORY
    doGet() {
        const index = this.state.pop();
        const v = this.state.transient[variance_1.OtVar.Ops.originOf(index)];
        if (v === undefined)
            throw errors_1.Errors.Cff.TransientInvalid(variance_1.OtVar.Ops.originOf(index));
        this.state.push(v);
    }
    doPut() {
        const [val, index] = this.state.args(2);
        this.state.transient[variance_1.OtVar.Ops.originOf(index)] = val;
    }
    // FLOW CONTROL
    doConditional() {
        const [s1, s2, v1, v2] = this.state.args(4);
        const diff = variance_1.OtVar.Ops.minus(v1, v2);
        this.state.push(variance_1.OtVar.Ops.originOf(diff) <= 0 ? s1 : s2);
    }
    doEndChar() {
        if (this.state.stackHeight()) {
            this.sink.setWidth(variance_1.OtVar.Ops.add(this.subroutines.nominalWidthX, this.state.pop()));
        }
        this.state.allArgs();
        this.halt = this.end = true;
    }
    doReturn() {
        this.halt = true;
    }
    getLocalSubroutine(n) {
        const bias = computeSubroutineBias(this.subroutines.local.length);
        const data = this.subroutines.local[bias + n];
        if (!data)
            throw errors_1.Errors.Cff.SubroutineNotFound("local", n);
        return data;
    }
    getGlobalSubroutine(n) {
        const bias = computeSubroutineBias(this.subroutines.global.length);
        const data = this.subroutines.global[bias + n];
        if (!data)
            throw errors_1.Errors.Cff.SubroutineNotFound("global", n);
        return data;
    }
}
exports.CffCharStringInterpreter = CffCharStringInterpreter;
function callCharString(buf, st, ss, ds) {
    st.log += "{ ";
    const irSource = new ir_source_1.CharStringIrSource(new bin_util_1.BinaryView(buf), buf.byteLength);
    const interp = new CffCharStringInterpreter(irSource, st, ss, ds);
    while (!interp.halt) {
        const ir = irSource.next();
        if (!ir)
            break;
        interp.next(ir);
    }
    st.log += "} ";
    return interp.end;
}
function interpretCharString(buf, st, ss, ds) {
    ds.setWidth(ss.defaultWidthX);
    callCharString(buf, st, ss, ds);
}
function computeSubroutineBias(cnt) {
    if (cnt < 1240)
        return 107;
    else if (cnt < 33900)
        return 1131;
    else
        return 32768;
}
//# sourceMappingURL=interpreter.js.map