import { organizeWaitOperations } from "./organizeWaitOperations.js"; /** * @typedef PDF * @property {string} originalFileName * @property {string} fileName * @property {Uint8Array} buffer */ /** * * @param {JSON} operations * @param {PDF|PDF[]} input * @param {import('./functions.js')} Functions * @returns {} */ export async function * traverseOperations(operations, input, Functions) { const waitOperations = organizeWaitOperations(operations); /** @type {PDF[]} */ let results = []; yield* nextOperation(operations, input); return results; /** * * @param {JSON} operations * @param {PDF|PDF[]} input * @returns {undefined} */ async function * nextOperation(operations, input) { if(Array.isArray(operations) && operations.length == 0) { // isEmpty if(Array.isArray(input)) { console.log("operation done: " + input[0].fileName + input.length > 1 ? "+" : ""); results = results.concat(input); return; } else { console.log("operation done: " + input.fileName); results.push(input); return; } } for (let i = 0; i < operations.length; i++) { yield* computeOperation(operations[i], structuredClone(input)); } } /** * * @param {JSON} operation * @param {PDF|PDF[]} input * @returns {undefined} */ async function * computeOperation(operation, input) { yield "Starting: " + operation.type; switch (operation.type) { case "done": // Skip this, because it is a valid node. break; case "wait": const waitOperation = waitOperations[operation.values.id]; if(Array.isArray(input)) { waitOperation.input.concat(input); // TODO: May have unexpected concequences. Needs further testing! } else { waitOperation.input.push(input); } waitOperation.waitCount--; if(waitOperation.waitCount == 0) { yield* nextOperation(waitOperation.doneOperation.operations, waitOperation.input); } break; case "extract": yield* nToN(input, operation, async (input) => { input.fileName += "_extractedPages"; input.buffer = await Functions.extractPages(input.buffer, operation.values["pagesToExtractArray"]); }); break; case "impose": yield* nToN(input, operation, async (input) => { input.fileName += "_imposed"; input.buffer = await Functions.impose(input.buffer, operation.values["nup"], operation.values["format"]); }); break; case "merge": yield* nToOne(input, operation, async (inputs) => { return { originalFileName: inputs.map(input => input.originalFileName).join("_and_"), fileName: inputs.map(input => input.fileName).join("_and_") + "_merged", buffer: await Functions.mergePDFs(inputs.map(input => input.buffer)) } }); break; case "rotate": yield* nToN(input, operation, async (input) => { input.fileName += "_turned"; input.buffer = await Functions.rotatePages(input.buffer, operation.values["rotation"]); }); break; case "split": // TODO: A split might break the done condition, it may count multiple times. Needs further testing! yield* oneToN(input, operation, async (input) => { const splitResult = await Functions.splitPDF(input.buffer, operation.values["pagesToSplitAfterArray"]); const splits = []; for (let j = 0; j < splitResult.length; j++) { splits.push({ originalFileName: input.originalFileName, fileName: input.fileName + "_split" + j, buffer: splitResult[j] }) } return splits; }); break; case "editMetadata": yield* nToN(input, operation, async (input) => { input.fileName += "_metadataEdited"; input.buffer = await Functions.editMetadata(input.buffer, operation.values["metadata"]); }); break; case "organizePages": yield* nToN(input, operation, async (input) => { input.fileName += "_pagesOrganized"; input.buffer = await Functions.organizePages(input.buffer, operation.values["operation"], operation.values["customOrderString"]); }); break; case "removeBlankPages": yield* nToN(input, operation, async (input) => { input.fileName += "_removedBlanks"; input.buffer = await Functions.removeBlankPages(input.buffer, operation.values["whiteThreashold"]); }); break; case "splitOn": yield* oneToN(input, operation, async (input) => { const splitResult = await Functions.splitOn(input.buffer, operation.values["type"], operation.values["whiteThreashold"]); const splits = []; for (let j = 0; j < splitResult.length; j++) { splits.push({ originalFileName: input.originalFileName, fileName: input.fileName + "_split" + j, buffer: splitResult[j] }) } return splits; }); break; default: throw new Error(`${operation.type} not implemented yet.`); break; } } /** * * @param {PDF|PDF[]} input * @param {JSON} operation * @returns {undefined} */ async function * nToOne(inputs, operation, callback) { inputs = Array.from(inputs); // Convert single values to array, keep arrays as is. inputs = await callback(inputs); yield* nextOperation(operation.operations, inputs); } /** * * @param {PDF|PDF[]} input * @param {JSON} operation * @returns {undefined} */ async function * oneToN(input, operation, callback) { if(Array.isArray(input)) { let output = []; for (let i = 0; i < input.length; i++) { output = output.concat(await callback(input[i])); } yield* nextOperation(operation.operations, output); } else { input = await callback(input); yield* nextOperation(operation.operations, input); } } /** * * @param {PDF|PDF[]} input * @param {JSON} operation * @returns {undefined} */ async function * nToN(input, operation, callback) { if(Array.isArray(input)) { for (let i = 0; i < input.length; i++) { await callback(input[i]); } yield* nextOperation(operation.operations, input); } else { await callback(input); yield* nextOperation(operation.operations, input); } } }