var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
import { PdfRedactionAnnotation, PdfRotationAngle } from '@syncfusion/ej2-pdf';
import { _GraphicState } from '../graphic-state';
import { _PdfContentParserHelper } from '../content-parser-helper';
import { _TextProcessingMode } from '../enum';
import { _addFontResources, _getXObjectResources } from '../utils';
import { _TextGlyphMapper } from './text-glyph-mapper';
import { _PdfRedactionProcessor } from './pdf-redaction-processor';
import { PdfRedactionRegion } from './pdf-redaction-region';
/**
 * Represents a content redactor from an existing PDF document.
 *
 * ```typescript
 * let document: PdfDocument = new PdfDocument(data, password);
 * // Create a new text extractor
 * let redactor: PdfRedactor = new PdfRedactor(document);
 * // Add redactions to the collection
 * let redactions: PdfRedactionRegion[] = [];
 * redactions.push(new PdfRedactionRegion(0, {x: 10, y: 10, width: 100, height: 50}));
 * redactions.push(new PdfRedactionRegion(2, {x: 10, y: 10, width: 100, height: 50}, true, {r: 255, g: 0, b: 0}));
 * redactor.add(redactions);
 * // Apply redactions on the PDF document
 * redactor.redactSync();
 * // Save the document
 * document.save('output.pdf');
 * // Destroy the document
 * document.destroy();
 * ```
 */
var PdfRedactor = /** @class */ (function () {
    function PdfRedactor(document, redactions) {
        this._isHex = false;
        this._redactionRegion = [];
        this._redaction = new Map();
        this._object = new _PdfRedactionProcessor();
        if (document) {
            if (document._isLoaded) {
                this._document = document;
                this._crossReference = document._crossReference;
            }
            else {
                throw new Error('Redaction cannot be applied to a newly created document.');
            }
            this._document.fileStructure.isIncrementalUpdate = false;
            this._parser = new _PdfContentParserHelper(_TextProcessingMode.redaction, this);
            if (redactions && redactions.length > 0) {
                this.add(redactions);
            }
        }
        else {
            throw new Error('PDF document instance cannot be null or undefined');
        }
    }
    /**
     * Add redactions with specified options.
     *
     * @param {PdfRedactionRegion[]} redactions An array of redaction objects to specify the page index, bounds and appearance of the redaction to be applied.
     * @returns {void} Nothing.
     *
     * ```typescript
     * // Load an existing PDF document
     * let document: PdfDocument = new PdfDocument(data, password);
     * // Add redactions to the collection
     * let redactions: PdfRedactionRegion[] = [];
     * // Initialize a new instance of the `PdfRedactor` class
     * let redactor: PdfRedactor = new PdfRedactor(document);
     * // Initialize a new instance of the `PdfRedactionRegion` class.
     * let redaction: PdfRedactionRegion = new PdfRedactionRegion(0, {x: 40, y: 41.809, width: 80, height: 90});
     * // Sets the fill color used to fill the redacted area.
     * redaction.fillColor = {r: 255, g: 0, b: 0};
     * redactions.push(redaction);
     * // Add redactions with specified options.
     * redactor.add(redactions);
     * // Apply redactions on the PDF document
     * redactor.redactSync();
     * // Save the document
     * document.save('output.pdf');
     * // Destroy the document
     * document.destroy();
     * ```
     */
    PdfRedactor.prototype.add = function (redactions) {
        var options = redactions;
        for (var i = 0; i < options.length; i++) {
            var pageIndex = options[Number.parseInt(i.toString(), 10)].pageIndex;
            if (!this._redaction.has(pageIndex)) {
                this._redaction.set(pageIndex, []);
            }
            var redactionArray = this._redaction.get(pageIndex);
            if (redactionArray) {
                redactionArray.push(options[Number.parseInt(i.toString(), 10)]);
            }
        }
    };
    /**
     * Redacts all marked regions in the PDF document synchronously.
     *
     * This method removes text and vector content within the specified redaction regions. It does **not** support image redaction.To redact images inside the PDF, use the asynchronous `redact()` method instead.
     *
     * @returns {void} Nothing.
     *
     * ```typescript
     * // Load an existing PDF document
     * let document: PdfDocument = new PdfDocument(data, password);
     * // Add redactions to the collection
     * let redactions: PdfRedactionRegion[] = [];
     * // Initialize a new instance of the `PdfRedactor` class
     * let redactor: PdfRedactor = new PdfRedactor(document);
     * // Initialize a new instance of the `PdfRedactionRegion` class.
     * let redaction: PdfRedactionRegion = new PdfRedactionRegion(0, {x: 40, y: 41.809, width: 80, height: 90});
     * // Sets the fill color used to fill the redacted area.
     * redaction.fillColor = {r: 255, g: 0, b: 0};
     * redactions.push(redaction);
     * // Add redactions with specified options.
     * redactor.add(redactions);
     * // Apply redactions on the PDF document
     * redactor.redactSync();
     * // Save the document
     * document.save('output.pdf');
     * // Destroy the document
     * document.destroy();
     * ```
     */
    PdfRedactor.prototype.redactSync = function () {
        var _this = this;
        if (this._document && this._document.pageCount > 0) {
            for (var i = 0; i < this._document.pageCount; i++) {
                var page = this._document.getPage(i);
                if (page && page.annotations.count > 0) {
                    this._applyRedaction(page);
                }
            }
        }
        this._redaction.forEach(function (value, key) {
            _this._redactionRegion = [];
            _this._combineBounds(value);
            var option = value;
            var page = _this._document.getPage(key);
            var graphicState = new _GraphicState();
            var recordCollection = _this._parser._getPageRecordCollection(page);
            var resource = page._pageDictionary.get('Resources');
            var fontCollection;
            var xObjectCollection;
            if (typeof (resource) !== 'undefined') {
                fontCollection = _addFontResources(resource, page._pageDictionary._crossReference);
                xObjectCollection = _getXObjectResources(resource, page._pageDictionary._crossReference);
            }
            var stream; // eslint-disable-line
            if (_this._redactionRegion.length > 0) {
                stream = _this._parser._processRecordCollection(recordCollection, page, fontCollection, xObjectCollection, graphicState);
            }
            page._needInitializeGraphics = true;
            _this._object._updateContentStream(page, stream, option, _this._document);
        });
    };
    /**
     * Redacts all marked regions in the PDF document.
     *
     * @param {Function} callBack - A function that supplies a canvas element along with the current application platform.The canvas is used internally to render and redact image content.
     * @returns {void} A promise that resolves once all redactions have been applied.
     *
     * ```typescript
     * // Load an existing PDF document
     * let doc: PdfDocument = new PdfDocument(data, password);
     * // Add redactions to the collection
     * let redactions: PdfRedactionRegion[] = [];
     * // Initialize a new instance of the `PdfRedactor` class
     * let redactor: PdfRedactor = new PdfRedactor(doc);
     * // Initialize a new instance of the `PdfRedactionRegion` class.
     * let redaction: PdfRedactionRegion = new PdfRedactionRegion(0, {x: 40, y: 41.809, width: 80, height: 90});
     * // Sets the fill color used to fill the redacted area.
     * redaction.fillColor = {r: 255, g: 0, b: 0};
     * redactions.push(redaction);
     * // Add redactions with specified options.
     * redactor.add(redactions);
     * // Define a canvas render callback that returns a canvas element and the application platform.
     * const canvasRenderCallback = (): {canvas: any, applicationPlatform: ApplicationPlatform} => {
     *     const canvas = document.createElement('canvas');
     *     return { canvas: canvas, applicationPlatform: ApplicationPlatform.typescript };
     * };
     * // Apply redactions on the PDF document
     * await redactor.redact(callBack: canvasRenderCallback);
     * // Save the document
     * doc.save('output.pdf');
     * // Destroy the document
     * doc.destroy();
     * ```
     */
    PdfRedactor.prototype.redact = function (callBack) {
        return __awaiter(this, void 0, void 0, function () {
            var i, page, canvas, isValidCanvas, entries, i, _a, key, value, option, page, graphicState, recordCollection, resource, fontCollection, xObjectCollection, stream, mode;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        if (this._document && this._document.pageCount > 0) {
                            this._document.fileStructure.isIncrementalUpdate = false;
                            for (i = 0; i < this._document.pageCount; i++) {
                                page = this._document.getPage(i);
                                if (page && page.annotations.count > 0) {
                                    this._applyRedaction(page);
                                }
                            }
                        }
                        isValidCanvas = false;
                        if (typeof (callBack) !== 'undefined') {
                            canvas = callBack();
                            if (typeof (canvas) !== 'undefined' && typeof (canvas.canvas) !== 'undefined') {
                                isValidCanvas = true;
                            }
                        }
                        entries = Array.from(this._redaction.entries());
                        i = 0;
                        _b.label = 1;
                    case 1:
                        if (!(i < entries.length)) return [3 /*break*/, 5];
                        _a = entries[i], key = _a[0], value = _a[1];
                        this._redactionRegion = [];
                        this._combineBounds(value);
                        option = value;
                        page = this._document.getPage(key);
                        this._document._crossReference._isDecoderSupport = true;
                        graphicState = new _GraphicState();
                        recordCollection = this._parser._getPageRecordCollection(page);
                        resource = page._pageDictionary.get('Resources');
                        fontCollection = void 0;
                        xObjectCollection = void 0;
                        if (typeof (resource) !== 'undefined') {
                            fontCollection = _addFontResources(resource, page._pageDictionary._crossReference);
                            xObjectCollection = _getXObjectResources(resource, this._crossReference, _TextProcessingMode.imageRedaction, page);
                        }
                        stream = void 0;
                        mode = void 0;
                        if (!(this._redactionRegion.length > 0)) return [3 /*break*/, 3];
                        mode = _TextProcessingMode.imageRedaction;
                        return [4 /*yield*/, this._parser._processImageRecordCollection(recordCollection, page, fontCollection, xObjectCollection, graphicState, canvas, mode, option, isValidCanvas)];
                    case 2:
                        stream = _b.sent();
                        _b.label = 3;
                    case 3:
                        page._needInitializeGraphics = true;
                        this._object._updateContentStream(page, stream, option, this._document);
                        _b.label = 4;
                    case 4:
                        i++;
                        return [3 /*break*/, 1];
                    case 5: return [2 /*return*/];
                }
            });
        });
    };
    PdfRedactor.prototype._applyRedaction = function (page) {
        var _this = this;
        var redactRegions = [];
        var _loop_1 = function (k) {
            var annotation = page.annotations.at(k);
            if (annotation && annotation instanceof PdfRedactionAnnotation) {
                var redactionAnnotation_1 = annotation;
                redactionAnnotation_1.flatten = true;
                var rotation_1 = page.rotation;
                if (redactionAnnotation_1.boundsCollection && redactionAnnotation_1.boundsCollection.length > 1) {
                    redactionAnnotation_1.boundsCollection.forEach(function (rawRect, index) {
                        var normRect = _this._calculateRotatedBounds(page, rawRect);
                        var appearance = redactionAnnotation_1._createNormalAppearance(index, normRect, rotation_1);
                        var redact = new PdfRedactionRegion(page._pageIndex, normRect);
                        redact.appearance.normal = appearance;
                        redactRegions.push(redact);
                    });
                }
                else {
                    var normRect = this_1._calculateRotatedBounds(page, redactionAnnotation_1.bounds);
                    var appearance = redactionAnnotation_1._createNormalAppearance(undefined, normRect, rotation_1);
                    var redact = new PdfRedactionRegion(page._pageIndex, normRect);
                    redact.appearance.normal = appearance;
                    redactRegions.push(redact);
                }
                page.annotations.removeAt(k);
                k--;
            }
            out_k_1 = k;
        };
        var this_1 = this, out_k_1;
        for (var k = 0; k < page.annotations.count; k++) {
            _loop_1(k);
            k = out_k_1;
        }
        if (redactRegions.length > 0) {
            var existingRedactions = this._redaction.get(page._pageIndex) || [];
            this._redaction.set(page._pageIndex, existingRedactions.concat(redactRegions));
        }
    };
    PdfRedactor.prototype._calculateRotatedBounds = function (page, bounds) {
        var rotation = page.rotation;
        var pageWidth = page.size.width;
        var pageHeight = page.size.height;
        switch (rotation) {
            case PdfRotationAngle.angle0:
                return bounds;
            case PdfRotationAngle.angle90:
                return {
                    x: pageHeight - (bounds.y + bounds.height),
                    y: bounds.x,
                    width: bounds.height,
                    height: bounds.width
                };
            case PdfRotationAngle.angle180:
                return {
                    x: pageWidth - (bounds.x + bounds.width),
                    y: pageHeight - (bounds.y + bounds.height),
                    width: bounds.width,
                    height: bounds.height
                };
            case PdfRotationAngle.angle270:
                return {
                    x: bounds.y,
                    y: pageWidth - bounds.x - bounds.width,
                    width: bounds.height,
                    height: bounds.width
                };
            default:
                return bounds;
        }
    };
    PdfRedactor.prototype._combineBounds = function (options) {
        for (var i = 0; i < options.length; i++) {
            this._redactionRegion.push(options[Number.parseInt(i.toString(), 10)]);
        }
    };
    PdfRedactor.prototype._optimizeContent = function (recordCollection, index, updatedText, stream) {
        var record = recordCollection[Number.parseInt(index.toString(), 10)];
        if (record) {
            if (typeof (record._operands) !== 'undefined' && record._operands.length >= 1) {
                if (record._operator === 'ID') {
                    var builder = [];
                    for (var k = 0; k < record._operands.length; k++) {
                        if (k + 1 < record._operands.length && record._operands[k].indexOf("/") !== -1 && record._operands[k + 1].indexOf("/") !== -1) { // eslint-disable-line
                            builder.push(record._operands[Number.parseInt(k.toString(), 10)], ' ', record._operands[k + 1], '\r\n');
                            k = k + 1;
                        }
                        else if (k + 1 < record._operands.length && record._operands[k].indexOf("/") !== -1) { // eslint-disable-line
                            builder.push(record._operands[Number.parseInt(k.toString(), 10)], ' ', record._operands[k + 1], '\r\n');
                            k = k + 1;
                        }
                        else {
                            builder.push(record._operands[Number.parseInt(k.toString(), 10)], ' ');
                        }
                    }
                    var text = builder.join(""); // eslint-disable-line
                    var bytes = this._getBytes(text);
                    stream.write(bytes);
                }
                else {
                    for (var i = 0; i < record._operands.length; i++) {
                        var operand = record._operands[Number.parseInt(i.toString(), 10)];
                        if (record._operator === 'Tj' || record._operator === "'" || record._operator === '\"' || record._operator === 'TJ') { // eslint-disable-line
                            if (updatedText !== '') {
                                operand = updatedText;
                                if (record._operator === "'" || record._operator === '\"') { // eslint-disable-line
                                    stream.write('T*');
                                    stream.write(' ');
                                    if (record._operator === '\"') { // eslint-disable-line
                                        i += 2;
                                    }
                                }
                                record._operator = 'TJ';
                            }
                        }
                        var bytes = this._getBytes(operand);
                        stream.write(bytes);
                        if (record._operator !== 'Tj' && record._operator !== "'" && record._operator !== '\"' && record._operator !== 'TJ') { // eslint-disable-line
                            stream.write(' ');
                        }
                    }
                }
            }
            else if (typeof (record._operands) === 'undefined' && typeof (record._inlineImageBytes) !== 'undefined') {
                var numberArray = Array.from(record._inlineImageBytes);
                stream.write(numberArray);
                stream.write(' ');
            }
            stream.write(record._operator);
        }
        var count = recordCollection.length;
        if ((index) < count) {
            if (record._operator === 'ID') {
                stream.write('\n');
            }
            else if ((record._operator === 'W' || record._operator === 'W*') && recordCollection[index + 1]._operator === 'n') {
                stream.write(' ');
            }
            else if (record._operator === 'w' || record._operator === 'EI') {
                stream.write(' ');
            }
            else {
                stream.write('\r\n');
            }
        }
    };
    PdfRedactor.prototype._getBytes = function (text) {
        var bytes = [];
        for (var i = 0; i < text.length; i++) {
            var charCode = text.charCodeAt(i);
            bytes.push(charCode);
        }
        return bytes;
    };
    PdfRedactor.prototype._isFoundBounds = function (values, redactionBounds) {
        for (var _i = 0, redactionBounds_1 = redactionBounds; _i < redactionBounds_1.length; _i++) {
            var bounds = redactionBounds_1[_i];
            if (this._contains(bounds._bounds, [values.x, values.y]) || this._intersectsWith(bounds._bounds, values)) {
                return true;
            }
        }
        return false;
    };
    PdfRedactor.prototype._contains = function (bounds, point) {
        return (point[0] >= bounds.x &&
            point[0] <= bounds.x + bounds.width &&
            point[1] >= bounds.y &&
            point[1] <= bounds.y + bounds.height);
    };
    PdfRedactor.prototype._intersectsWith = function (rect1, rect2) {
        return (rect2.x < rect1.x + rect1.width) && (rect1.x < (rect2.x + rect2.width)) && (rect2.y < rect1.y + rect1.height) &&
            (rect1.y < rect2.y + rect2.height);
    };
    PdfRedactor.prototype._splitHexString = function (hexString) {
        var hexList = [];
        hexString = hexString.slice(1, -1);
        var size = hexString.startsWith('0') ? 4 : 2;
        for (var i = 0; i < hexString.length; i += size) {
            var chunk = hexString.substring(i, i + size);
            if (chunk.indexOf('\n') !== -1) {
                var extraChar = hexString.charAt(i + size);
                chunk += extraChar;
                i++;
            }
            hexList.push(chunk);
        }
        return hexList;
    };
    PdfRedactor.prototype._replacedText = function (glyph, text, originalText, decodeText) {
        var isReplacedText = false;
        var isOtherText = false;
        for (var i = 0; i < glyph.length; i++) {
            if (this._isFoundBounds(glyph[Number.parseInt(i.toString(), 10)]._bounds, this._redactionRegion)) {
                isReplacedText = true;
                glyph[Number.parseInt(i.toString(), 10)]._isReplace = true;
            }
            else {
                isOtherText = true;
                glyph[Number.parseInt(i.toString(), 10)]._text = text[Number.parseInt(i.toString(), 10)];
            }
        }
        var updatedText = '';
        if (!isReplacedText && isOtherText) {
            return originalText;
        }
        else {
            var mainTextCollection = [];
            if (originalText[0] === '(') {
                mainTextCollection = decodeText;
            }
            else if (originalText[0] === '[') {
                mainTextCollection = decodeText;
            }
            else if (originalText[0] === '<') {
                mainTextCollection = decodeText;
                this._isHex = true;
            }
            var map = this._mapString(mainTextCollection, glyph);
            for (var i = 0; i < map.length; i++) {
                map[Number.parseInt(i.toString(), 10)]._isHex = this._isHex;
                updatedText += map[Number.parseInt(i.toString(), 10)]._getText();
            }
            updatedText = '[' + updatedText + ']';
            this._isHex = false;
        }
        return updatedText;
    };
    PdfRedactor.prototype._mapString = function (mainTextCollection, imageGlyph) {
        var mappedString = [];
        var glyphList = imageGlyph;
        var startIndex = 0;
        for (var i = 0; i < mainTextCollection.length; i++) {
            var endChar = mainTextCollection[Number.parseInt(i.toString(), 10)].length - 1;
            if (mainTextCollection[Number.parseInt(i.toString(), 10)][0] !== '(' && mainTextCollection[Number.parseInt(i.toString(), 10)][Number.parseInt(endChar.toString(), 10)] !== ')') {
                var mapping = new _TextGlyphMapper();
                mapping.text = mainTextCollection[Number.parseInt(i.toString(), 10)];
                mappedString.push(mapping);
            }
            else {
                var mapping = new _TextGlyphMapper();
                mapping.text = mainTextCollection[Number.parseInt(i.toString(), 10)];
                var text = mainTextCollection[Number.parseInt(i.toString(), 10)];
                var subString = text.length >= 2;
                var start = text.startsWith('(');
                var end = text.endsWith(')');
                if (subString && start && !end) {
                    text = text.substring(1, text.length);
                }
                else if (subString && !start && end) {
                    text = text.substring(0, text.length - 1);
                }
                else if (subString) {
                    text = text.substring(1, text.length - 1);
                }
                else {
                    continue;
                }
                var length_1 = text.length;
                mapping.glyph = glyphList.slice(startIndex, startIndex + length_1);
                startIndex += length_1;
                mappedString.push(mapping);
            }
        }
        return mappedString;
    };
    return PdfRedactor;
}());
export { PdfRedactor };
