"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const pako_1 = tslib_1.__importDefault(require("pako"));
const PDFHeader_1 = tslib_1.__importDefault(require("./document/PDFHeader"));
const errors_1 = require("./errors");
const PDFArray_1 = tslib_1.__importDefault(require("./objects/PDFArray"));
const PDFBool_1 = tslib_1.__importDefault(require("./objects/PDFBool"));
const PDFDict_1 = tslib_1.__importDefault(require("./objects/PDFDict"));
const PDFHexString_1 = tslib_1.__importDefault(require("./objects/PDFHexString"));
const PDFName_1 = tslib_1.__importDefault(require("./objects/PDFName"));
const PDFNull_1 = tslib_1.__importDefault(require("./objects/PDFNull"));
const PDFNumber_1 = tslib_1.__importDefault(require("./objects/PDFNumber"));
const PDFObject_1 = tslib_1.__importDefault(require("./objects/PDFObject"));
const PDFRawStream_1 = tslib_1.__importDefault(require("./objects/PDFRawStream"));
const PDFRef_1 = tslib_1.__importDefault(require("./objects/PDFRef"));
const PDFStream_1 = tslib_1.__importDefault(require("./objects/PDFStream"));
const PDFString_1 = tslib_1.__importDefault(require("./objects/PDFString"));
const PDFOperator_1 = tslib_1.__importDefault(require("./operators/PDFOperator"));
const PDFOperatorNames_1 = tslib_1.__importDefault(require("./operators/PDFOperatorNames"));
const PDFContentStream_1 = tslib_1.__importDefault(require("./structures/PDFContentStream"));
const utils_1 = require("../utils");
const rng_1 = require("../utils/rng");
const byAscendingObjectNumber = ([a], [b]) => a.objectNumber - b.objectNumber;
class PDFContext {
    constructor() {
        this.isDecrypted = true;
        this.largestObjectNumber = 0;
        this.header = PDFHeader_1.default.forVersion(1, 7);
        this.trailerInfo = {};
        this.indirectObjects = new Map();
        this.rng = rng_1.SimpleRNG.withSeed(1);
    }
    assign(ref, object) {
        this.indirectObjects.set(ref, object);
        if (ref.objectNumber > this.largestObjectNumber) {
            this.largestObjectNumber = ref.objectNumber;
        }
    }
    nextRef() {
        this.largestObjectNumber += 1;
        return PDFRef_1.default.of(this.largestObjectNumber);
    }
    register(object) {
        const ref = this.nextRef();
        this.assign(ref, object);
        return ref;
    }
    delete(ref) {
        return this.indirectObjects.delete(ref);
    }
    lookupMaybe(ref, ...types) {
        // TODO: `preservePDFNull` is for backwards compatibility. Should be
        // removed in next breaking API change.
        const preservePDFNull = types.includes(PDFNull_1.default);
        const result = ref instanceof PDFRef_1.default ? this.indirectObjects.get(ref) : ref;
        if (!result || (result === PDFNull_1.default && !preservePDFNull))
            return undefined;
        for (let idx = 0, len = types.length; idx < len; idx++) {
            const type = types[idx];
            if (type === PDFNull_1.default) {
                if (result === PDFNull_1.default)
                    return result;
            }
            else {
                if (result instanceof type)
                    return result;
            }
        }
        throw new errors_1.UnexpectedObjectTypeError(types, result);
    }
    lookup(ref, ...types) {
        const result = ref instanceof PDFRef_1.default ? this.indirectObjects.get(ref) : ref;
        if (types.length === 0)
            return result;
        for (let idx = 0, len = types.length; idx < len; idx++) {
            const type = types[idx];
            if (type === PDFNull_1.default) {
                if (result === PDFNull_1.default)
                    return result;
            }
            else {
                if (result instanceof type)
                    return result;
            }
        }
        throw new errors_1.UnexpectedObjectTypeError(types, result);
    }
    getObjectRef(pdfObject) {
        const entries = Array.from(this.indirectObjects.entries());
        for (let idx = 0, len = entries.length; idx < len; idx++) {
            const [ref, object] = entries[idx];
            if (object === pdfObject) {
                return ref;
            }
        }
        return undefined;
    }
    enumerateIndirectObjects() {
        return Array.from(this.indirectObjects.entries()).sort(byAscendingObjectNumber);
    }
    obj(literal) {
        if (literal instanceof PDFObject_1.default) {
            return literal;
        }
        else if (literal === null || literal === undefined) {
            return PDFNull_1.default;
        }
        else if (typeof literal === 'string') {
            return PDFName_1.default.of(literal);
        }
        else if (typeof literal === 'number') {
            return PDFNumber_1.default.of(literal);
        }
        else if (typeof literal === 'boolean') {
            return literal ? PDFBool_1.default.True : PDFBool_1.default.False;
        }
        else if (Array.isArray(literal)) {
            const array = PDFArray_1.default.withContext(this);
            for (let idx = 0, len = literal.length; idx < len; idx++) {
                array.push(this.obj(literal[idx]));
            }
            return array;
        }
        else {
            const dict = PDFDict_1.default.withContext(this);
            const keys = Object.keys(literal);
            for (let idx = 0, len = keys.length; idx < len; idx++) {
                const key = keys[idx];
                const value = literal[key];
                if (value !== undefined)
                    dict.set(PDFName_1.default.of(key), this.obj(value));
            }
            return dict;
        }
    }
    getLiteral(obj, { deep = true, literalRef = false, literalStreamDict = false, literalString = false, } = {}) {
        const cfg = { deep, literalRef, literalStreamDict, literalString };
        if (obj instanceof PDFArray_1.default) {
            const lit = obj.asArray();
            return deep ? lit.map((value) => this.getLiteral(value, cfg)) : lit;
        }
        else if (obj instanceof PDFBool_1.default) {
            return obj.asBoolean();
        }
        else if (obj instanceof PDFDict_1.default) {
            const lit = {};
            const entries = obj.entries();
            for (let idx = 0, len = entries.length; idx < len; idx++) {
                const [name, value] = entries[idx];
                lit[this.getLiteral(name)] = deep ? this.getLiteral(value, cfg) : value;
            }
            return lit;
        }
        else if (obj instanceof PDFName_1.default) {
            return obj.decodeText();
        }
        else if (obj === PDFNull_1.default) {
            return null;
        }
        else if (obj instanceof PDFNumber_1.default) {
            return obj.asNumber();
        }
        else if (obj instanceof PDFRef_1.default && literalRef) {
            return obj.objectNumber;
        }
        else if (obj instanceof PDFStream_1.default && literalStreamDict) {
            return this.getLiteral(obj.dict, cfg);
        }
        else if ((obj instanceof PDFString_1.default || obj instanceof PDFHexString_1.default) &&
            literalString) {
            return obj.asString();
        }
        return obj;
    }
    stream(contents, dict = {}) {
        return PDFRawStream_1.default.of(this.obj(dict), (0, utils_1.typedArrayFor)(contents));
    }
    flateStream(contents, dict = {}) {
        return this.stream(pako_1.default.deflate((0, utils_1.typedArrayFor)(contents)), {
            ...dict,
            Filter: 'FlateDecode',
        });
    }
    contentStream(operators, dict = {}) {
        return PDFContentStream_1.default.of(this.obj(dict), operators);
    }
    formXObject(operators, dict = {}) {
        return this.contentStream(operators, {
            BBox: this.obj([0, 0, 0, 0]),
            Matrix: this.obj([1, 0, 0, 1, 0, 0]),
            ...dict,
            Type: 'XObject',
            Subtype: 'Form',
        });
    }
    /*
     * Reference to PDFContentStream that contains a single PDFOperator: `q`.
     * Used by [[PDFPageLeaf]] instances to ensure that when content streams are
     * added to a modified PDF, they start in the default, unchanged graphics
     * state.
     */
    getPushGraphicsStateContentStream() {
        if (this.pushGraphicsStateContentStreamRef) {
            return this.pushGraphicsStateContentStreamRef;
        }
        const dict = this.obj({});
        const op = PDFOperator_1.default.of(PDFOperatorNames_1.default.PushGraphicsState);
        const stream = PDFContentStream_1.default.of(dict, [op]);
        this.pushGraphicsStateContentStreamRef = this.register(stream);
        return this.pushGraphicsStateContentStreamRef;
    }
    /*
     * Reference to PDFContentStream that contains a single PDFOperator: `Q`.
     * Used by [[PDFPageLeaf]] instances to ensure that when content streams are
     * added to a modified PDF, they start in the default, unchanged graphics
     * state.
     */
    getPopGraphicsStateContentStream() {
        if (this.popGraphicsStateContentStreamRef) {
            return this.popGraphicsStateContentStreamRef;
        }
        const dict = this.obj({});
        const op = PDFOperator_1.default.of(PDFOperatorNames_1.default.PopGraphicsState);
        const stream = PDFContentStream_1.default.of(dict, [op]);
        this.popGraphicsStateContentStreamRef = this.register(stream);
        return this.popGraphicsStateContentStreamRef;
    }
    addRandomSuffix(prefix, suffixLength = 4) {
        return `${prefix}-${Math.floor(this.rng.nextInt() * 10 ** suffixLength)}`;
    }
}
PDFContext.create = () => new PDFContext();
exports.default = PDFContext;
//# sourceMappingURL=PDFContext.js.map