Solved type issues in traverseOperations etc. Changed functions to take a object single parameter. Condensed endpoints into a function call

This commit is contained in:
Saud Fatayerji 2023-11-14 03:26:42 +03:00
parent 77274e6117
commit 9956367384
18 changed files with 377 additions and 260 deletions

View File

@ -1,93 +1,50 @@
import Operations from '../../utils/pdf-operations';
import { respondWithPdfFile, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils';
import { PdfFile, fromMulterFile } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile'
import { PdfFile, PdfFileSchema, fromMulterFile, fromMulterFiles } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile'
import express, { Request, Response } from 'express';
import express, { Request, Response, RequestHandler } from 'express';
const router = express.Router();
import multer from 'multer';
const upload = multer();
import Joi from 'joi';
import Joi, { array } from 'joi';
router.post('/merge-pdfs', upload.single("pdfFile"), async function(req: Request, res: Response) {
const schema = Joi.object({
deleteAll: Joi.string(),
author: Joi.string(),
creationDate: Joi.string(),
creator: Joi.string(),
keywords: Joi.string(),
modificationDate: Joi.string(),
producer: Joi.string(),
subject: Joi.string(),
title: Joi.string(),
trapped: Joi.string(),
allRequestParams: Joi.object().pattern(Joi.string(), Joi.string()),
}).required();
const { error, value } = schema.validate(req.body);
if (error) {
res.status(400).send(error.details);
return;
}
if (!req.file) {
response_mustHaveExactlyOneFile(res);
return;
}
function registerEndpoint(endpoint: string,
nameToAppend: string,
fileHandler: RequestHandler,
operationFunction: (params: any) => Promise<PdfFile|PdfFile[]>,
joiSchema: Joi.ObjectSchema<any>
): void {
router.post(endpoint, fileHandler, async function(req: Request, res: Response) {
const body = req.body;
if (req.file) {
body.file = fromMulterFile(req.file);
}
if (req.files) {
if (Array.isArray(req.files))
body.files = fromMulterFiles(req.files);
else {
const flattenedFiles = Object.values(req.files).flatMap(va => va);
body.files = fromMulterFiles(flattenedFiles);
}
}
const arrayFile = fromMulterFile(req.file);
const processed = await Operations.updateMetadata(arrayFile, value)
const newFilename = appendToFilename(req.file.originalname, '_edited-metadata');
respondWithPdfFile(res, processed);
});
router.post('/rotate-pdf', upload.single("pdfFile"), async function(req: Request, res: Response) {
const schema = Joi.object({
angle: Joi.number().required()
console.log(req.body)
const { error, value } = joiSchema.validate(req.body);
if (error) {
res.status(400).send(error.details);
return;
}
const processed = await operationFunction(value)
if (Array.isArray(processed)) {
// TODO zip multiple files
} else {
processed.filename = appendToFilename(processed.filename, nameToAppend);
respondWithPdfFile(res, processed);
}
});
const { error, value } = schema.validate(req.body);
if (error) {
res.status(400).send(error.details);
return;
}
if (!req.file) {
response_mustHaveExactlyOneFile(res);
return;
}
const arrayFile = fromMulterFile(req.file);
const rotated = await Operations.rotatePages(arrayFile, value.angle)
rotated.filename = appendToFilename(arrayFile.filename, '_rotated');
respondWithPdfFile(res, rotated);
});
router.post('/update-metadata', upload.single("pdfFile"), async function(req: Request, res: Response) {
const schema = Joi.object({
deleteAll: Joi.string(),
author: Joi.string(),
creationDate: Joi.string(),
creator: Joi.string(),
keywords: Joi.string(),
modificationDate: Joi.string(),
producer: Joi.string(),
subject: Joi.string(),
title: Joi.string(),
trapped: Joi.string(),
allRequestParams: Joi.object().pattern(Joi.string(), Joi.string()),
}).required();
const { error, value } = schema.validate(req.body);
if (error) {
res.status(400).send(error.details);
return;
}
if (!req.file) {
response_mustHaveExactlyOneFile(res);
return;
}
const arrayFile = fromMulterFile(req.file);
const processed = await Operations.updateMetadata(arrayFile, value)
processed.filename = appendToFilename(arrayFile.filename, '_edited-metadata');
respondWithPdfFile(res, processed);
});
}
/**
* appends a string before the last '.' of the given filename
@ -96,4 +53,28 @@ function appendToFilename(filename: string, str: string) {
return filename.replace(/(\.[^.]+)$/, str+'$1')
}
registerEndpoint("/merge-pdfs", "_merged", upload.single("file"), Operations.mergePDFs, Joi.object({
files: Joi.array().items(PdfFileSchema).required(),
}).required())
registerEndpoint("/rotate-pdf", "_rotated", upload.single("file"), Operations.rotatePages, Joi.object({
file: PdfFileSchema.required(),
rotation: Joi.alternatives().try(Joi.number(), Joi.array().items(Joi.number())).required(),
}).required())
registerEndpoint("/update-metadata", "_edited-metadata", upload.single("file"), Operations.updateMetadata, Joi.object({
file: PdfFileSchema.required(),
deleteAll: Joi.string(),
author: Joi.string(),
creationDate: Joi.string(),
creator: Joi.string(),
keywords: Joi.string(),
modificationDate: Joi.string(),
producer: Joi.string(),
subject: Joi.string(),
title: Joi.string(),
trapped: Joi.string(),
allRequestParams: Joi.object().pattern(Joi.string(), Joi.string()),
}).required())
export default router;

View File

@ -1,14 +1,17 @@
import SharedOperations, { OperationsUseages } from "@stirling-pdf/shared-operations/src";
import SharedOperations, { OperationsType } from "@stirling-pdf/shared-operations/src";
import { ImposeParamsType } from '@stirling-pdf/shared-operations/src/functions/impose'
import { PdfFile } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
// Import injected libraries here!
//import * as pdfcpuWrapper from "@stirling-pdf/shared-operations/wasm/pdfcpu/pdfcpu-wrapper-node.js";
async function impose(snapshot: any, nup: number, format: string) {
return SharedOperations.impose(snapshot, nup, format, null); // TODO change null to pdfcpuWrapper
async function impose(params: ImposeParamsType): Promise<PdfFile> {
const paramsToUse = { ...params, pdfcpuWrapper: null }; // TODO change null to pdfcpuWrapper
return SharedOperations.impose(paramsToUse);
}
const toExport: OperationsUseages = {
const toExport: OperationsType = {
...SharedOperations,
impose,
}

View File

@ -0,0 +1,5 @@
export interface Action {
values: any;
type: string;
actions?: Action[];
}

View File

@ -1,5 +0,0 @@
export interface Operation {
values: {id:any};
type: string;
operations?: Operation[];
}

View File

@ -0,0 +1,5 @@
export type ValuesType<T> = T[keyof T];
// https://dev.to/vborodulin/ts-how-to-override-properties-with-type-intersection-554l
export type Override<T1, T2> = Omit<T1, keyof T2> & T2;

View File

@ -1,12 +1,25 @@
export async function impose(snapshot: any, nup: number, format: string, pdfcpuWrapper: any) {
return await pdfcpuWrapper.oneToOne([
import { PdfFile, fromPdfLib } from '../wrappers/PdfFile';
export type ImposeParamsType = {
file: any;
nup: number;
format: string;
}
export type ImposeParamsBaseType = {
file: any;
nup: number;
format: string;
pdfcpuWrapper: any;
}
export async function impose(params: ImposeParamsBaseType) {
return await params.pdfcpuWrapper.oneToOne([
"pdfcpu.wasm",
"nup",
"-c",
"disable",
'f:' + format,
'f:' + params.format,
"/output.pdf",
String(nup),
String(params.nup),
"input.pdf",
], snapshot);
], params.file);
}

View File

@ -1,10 +1,15 @@
import { PDFDocument } from 'pdf-lib';
import { PdfFile, convertAllToPdfLibFile, fromPdfLib } from '../wrappers/PdfFile';
import Joi from 'joi';
export async function mergePDFs(files: PdfFile[]): Promise<PdfFile> {
export type MergeParamsType = {
files: PdfFile[];
}
const pdfLibFiles = await convertAllToPdfLibFile(files);
export async function mergePDFs(params: MergeParamsType): Promise<PdfFile> {
const pdfLibFiles = await convertAllToPdfLibFile(params.files);
const mergedPdf = await PDFDocument.create();
@ -14,5 +19,5 @@ export async function mergePDFs(files: PdfFile[]): Promise<PdfFile> {
copiedPages.forEach((page) => mergedPdf.addPage(page));
}
return fromPdfLib(mergedPdf, files[0].filename);
return fromPdfLib(mergedPdf, params.files[0].filename);
};

View File

@ -2,7 +2,14 @@
import { degrees } from 'pdf-lib';
import { PdfFile, fromPdfLib } from '../wrappers/PdfFile';
export async function rotatePages(file: PdfFile, rotation: number|number[]): Promise<PdfFile> {
export type RotateParamsType = {
file: PdfFile;
rotation: number|number[];
}
export async function rotatePages(params: RotateParamsType): Promise<PdfFile> {
const { file, rotation } = params;
const pdfDoc = await file.getAsPdfLib();
const pages = pdfDoc.getPages();

View File

@ -2,7 +2,14 @@
import { PDFPage } from 'pdf-lib';
import { PdfFile, fromPdfLib } from '../wrappers/PdfFile';
export async function scaleContent(file: PdfFile, scaleFactor: number|number[]): Promise<PdfFile> {
export type ScaleContentParamsType = {
file: PdfFile;
scaleFactor: number|number[];
}
export async function scaleContent(params: ScaleContentParamsType): Promise<PdfFile> {
const { file, scaleFactor } = params;
const pdfDoc = await file.getAsPdfLib();
const pages = pdfDoc.getPages();

View File

@ -2,7 +2,14 @@
import { PDFPage } from 'pdf-lib';
import { PdfFile, fromPdfLib } from '../wrappers/PdfFile';
export async function scalePage(file: PdfFile, pageSize: {width?:number,height?:number}|{width?:number,height?:number}[]): Promise<PdfFile> {
export type ScalePageParamsType = {
file: PdfFile;
pageSize: { width?:number,height?:number }|{ width?:number,height?:number }[];
}
export async function scalePage(params: ScalePageParamsType): Promise<PdfFile> {
const { file, pageSize } = params;
const pdfDoc = await file.getAsPdfLib();
const pages = pdfDoc.getPages();

View File

@ -6,10 +6,15 @@ import { getImagesOnPage } from "./common/getImagesOnPage.js";
import { selectPages } from "./subDocumentFunctions";
import { PdfFile } from '../wrappers/PdfFile.js';
export async function splitOn(
file: PdfFile,
type: "BAR_CODE"|"QR_CODE"|"BLANK_PAGE",
whiteThreashold: number) {
export type SplitOnParamsType = {
file: PdfFile;
type: "BAR_CODE"|"QR_CODE"|"BLANK_PAGE";
whiteThreashold: number;
}
export async function splitOn(params: SplitOnParamsType) {
const { file, type, whiteThreashold } = params;
let splitAtPages: number[] = [];
switch (type) {
@ -43,7 +48,7 @@ export async function splitOn(
console.log(i);
if(i == splitAfter) {
if(pagesArray.length > 0) {
subDocuments.push(await selectPages(file, pagesArray));
subDocuments.push(await selectPages({file, pagesToExtractArray:pagesArray}));
pagesArray = [];
}
splitAfter = splitAtPages.shift();
@ -54,7 +59,7 @@ export async function splitOn(
}
}
if(pagesArray.length > 0) {
subDocuments.push(await selectPages(file, pagesArray));
subDocuments.push(await selectPages({file, pagesToExtractArray:pagesArray}));
}
pagesArray = [];

View File

@ -2,7 +2,14 @@
import { selectPages } from "./subDocumentFunctions";
import { PdfFile } from '../wrappers/PdfFile';
export async function splitPDF(file: PdfFile, splitAfterPageArray: number[]): Promise<PdfFile[]> {
export type SplitPdfParamsType = {
file: PdfFile;
splitAfterPageArray: number[];
}
export async function splitPDF(params: SplitPdfParamsType): Promise<PdfFile[]> {
const { file, splitAfterPageArray } = params;
const byteFile = await file.convertToPdfLibFile();
if (!byteFile?.pdfLib) return [];
@ -14,13 +21,13 @@ export async function splitPDF(file: PdfFile, splitAfterPageArray: number[]): Pr
for (let i = 0; i < numberOfPages; i++) {
if(splitAfter && i > splitAfter && pagesArray.length > 0) {
subDocuments.push(await selectPages(byteFile, pagesArray));
subDocuments.push(await selectPages({file:byteFile, pagesToExtractArray:pagesArray}));
splitAfter = splitAfterPageArray.shift();
pagesArray = [];
}
pagesArray.push(i);
}
subDocuments.push(await selectPages(byteFile, pagesArray));
subDocuments.push(await selectPages({file:byteFile, pagesToExtractArray:pagesArray}));
pagesArray = [];
return subDocuments;

View File

@ -4,9 +4,16 @@ import { PdfFile, fromPdfLib } from '../wrappers/PdfFile.js';
import { detectEmptyPages } from "./common/detectEmptyPages.js";
export async function sortPagesWithPreset(file: PdfFile, sortPreset: string, fancyPageSelector: string) {
export type SortPagesWithPresetParamsType = {
file: PdfFile;
sortPreset: string;
fancyPageSelector: string;
}
export async function sortPagesWithPreset(params: SortPagesWithPresetParamsType) {
const { file, sortPreset } = params;
if (sortPreset === "CUSTOM_PAGE_ORDER") {
return rearrangePages(file, fancyPageSelector);
return rearrangePages(params); // fancyPageSelector passed down with params
}
const sortFunction = sorts[sortPreset];
@ -19,19 +26,32 @@ export async function sortPagesWithPreset(file: PdfFile, sortPreset: string, fan
const pageCount = byteFile.pdfLib.getPageCount();
const sortIndecies = sortFunction(pageCount);
return selectPages(byteFile, sortIndecies);
return selectPages({file:byteFile, pagesToExtractArray:sortIndecies});
}
export async function rearrangePages(file: PdfFile, fancyPageSelector: string): Promise<PdfFile> {
export type RearrangePagesParamsType = {
file: PdfFile;
sortPreset: string;
fancyPageSelector: string;
}
export async function rearrangePages(params: RearrangePagesParamsType): Promise<PdfFile> {
const { file, fancyPageSelector } = params;
const byteFile = await file.convertToPdfLibFile();
if (!byteFile?.pdfLib) return byteFile;
const pagesToExtractArray = parseFancyPageSelector(fancyPageSelector, byteFile.pdfLib.getPageCount());
const newDocument = selectPages(byteFile, pagesToExtractArray);
const newDocument = selectPages({file:byteFile, pagesToExtractArray});
return newDocument;
};
export async function selectPages(file: PdfFile, pagesToExtractArray: number[]): Promise<PdfFile> {
export type SelectPagesParamsType = {
file: PdfFile;
pagesToExtractArray: number[];
}
export async function selectPages(params: SelectPagesParamsType): Promise<PdfFile> {
const { file, pagesToExtractArray } = params;
const byteFile = await file.convertToPdfLibFile();
if (!byteFile?.pdfLib) return byteFile;
@ -51,18 +71,30 @@ export async function selectPages(file: PdfFile, pagesToExtractArray: number[]):
return fromPdfLib(subDocument, file.filename);
}
export async function removePages(file: PdfFile, pagesToRemoveArray: number[]): Promise<PdfFile> {
export type RemovePagesParamsType = {
file: PdfFile;
pagesToRemoveArray: number[];
}
export async function removePages(params: RemovePagesParamsType): Promise<PdfFile> {
const { file, pagesToRemoveArray } = params;
const byteFile = await file.convertToPdfLibFile();
if (!byteFile?.pdfLib) return byteFile;
const pagesToExtractArray = invertSelection(pagesToRemoveArray, byteFile.pdfLib.getPageIndices())
return selectPages(byteFile, pagesToExtractArray);
return selectPages({file:byteFile, pagesToExtractArray});
}
export async function removeBlankPages(file: PdfFile, whiteThreashold: number) {
export type RemoveBlankPagesParamsType = {
file: PdfFile;
whiteThreashold: number;
}
export async function removeBlankPages(params: RemoveBlankPagesParamsType) {
const { file, whiteThreashold } = params;
const emptyPages = await detectEmptyPages(file, whiteThreashold);
console.log("Empty Pages: ", emptyPages);
return removePages(file, emptyPages);
return removePages({file, pagesToRemoveArray:emptyPages});
}

View File

@ -1,7 +1,8 @@
import { PdfFile, fromPdfLib } from '../wrappers/PdfFile';
export type Metadata = {
export type UpdateMetadataParams = {
file: PdfFile,
deleteAll?: boolean, // Delete all metadata if set to true
author?: string, // The author of the document
creationDate?: Date, // The creation date of the document (format: yyyy/MM/dd HH:mm:ss)
@ -15,10 +16,10 @@ export type Metadata = {
//allRequestParams?: {[key: string]: [key: string]}, // Map list of key and value of custom parameters. Note these must start with customKey and customValue if they are non-standard
}
export async function updateMetadata(file: PdfFile, metadata: Metadata|null): Promise<PdfFile> {
const pdfDoc = await file.getAsPdfLib();
export async function updateMetadata(params: UpdateMetadataParams): Promise<PdfFile> {
const pdfDoc = await params.file.getAsPdfLib();
if (!metadata || metadata.deleteAll) {
if (params.deleteAll) {
pdfDoc.setAuthor("");
pdfDoc.setCreationDate(new Date(0))
pdfDoc.setCreator("")
@ -28,28 +29,25 @@ export async function updateMetadata(file: PdfFile, metadata: Metadata|null): Pr
pdfDoc.setSubject("")
pdfDoc.setTitle("")
}
if (!metadata) {
return fromPdfLib(pdfDoc, file.filename);
}
if(metadata.author)
pdfDoc.setAuthor(metadata.author);
if(metadata.creationDate)
pdfDoc.setCreationDate(metadata.creationDate)
if(metadata.creator)
pdfDoc.setCreator(metadata.creator)
if(metadata.keywords)
pdfDoc.setKeywords(metadata.keywords.split(","))
if(metadata.modificationDate)
pdfDoc.setModificationDate(metadata.modificationDate)
if(metadata.producer)
pdfDoc.setProducer(metadata.producer)
if(metadata.subject)
pdfDoc.setSubject(metadata.subject)
if(metadata.title)
pdfDoc.setTitle(metadata.title)
if(params.author)
pdfDoc.setAuthor(params.author);
if(params.creationDate)
pdfDoc.setCreationDate(params.creationDate)
if(params.creator)
pdfDoc.setCreator(params.creator)
if(params.keywords)
pdfDoc.setKeywords(params.keywords.split(","))
if(params.modificationDate)
pdfDoc.setModificationDate(params.modificationDate)
if(params.producer)
pdfDoc.setProducer(params.producer)
if(params.subject)
pdfDoc.setSubject(params.subject)
if(params.title)
pdfDoc.setTitle(params.title)
// TODO add trapped and custom metadata. May need another library
return fromPdfLib(pdfDoc, file.filename);
return fromPdfLib(pdfDoc, params.file.filename);
};

View File

@ -1,16 +1,25 @@
import {
sortPagesWithPreset, SortPagesWithPresetParamsType,
rearrangePages, RearrangePagesParamsType,
selectPages, SelectPagesParamsType,
removePages, RemovePagesParamsType,
removeBlankPages, RemoveBlankPagesParamsType
} from "./functions/subDocumentFunctions";
import { impose, ImposeParamsBaseType, ImposeParamsType } from "./functions/impose";
import { mergePDFs, MergeParamsType } from './functions/mergePDFs';
import { rotatePages, RotateParamsType } from './functions/rotatePages';
import { scaleContent, ScaleContentParamsType} from './functions/scaleContent';
import { scalePage, ScalePageParamsType } from './functions/scalePage';
import { splitOn, SplitOnParamsType } from './functions/splitOn';
import { splitPDF, SplitPdfParamsType } from './functions/splitPDF';
import { updateMetadata, UpdateMetadataParams } from "./functions/updateMetadata";
import { PdfFile } from "./wrappers/PdfFile";
import { Override } from '../declarations/TypeScriptUtils'
// Import injected libraries here!
import { sortPagesWithPreset, rearrangePages, selectPages, removePages, removeBlankPages } from "./functions/subDocumentFunctions";
import { impose } from "./functions/impose";
import { mergePDFs } from './functions/mergePDFs';
import { rotatePages } from './functions/rotatePages';
import { scaleContent} from './functions/scaleContent';
import { scalePage } from './functions/scalePage';
import { splitOn } from './functions/splitOn';
import { splitPDF } from './functions/splitPDF';
import { updateMetadata } from "./functions/updateMetadata";
const toExport = {
sortPagesWithPreset, rearrangePages, selectPages, removePages, removeBlankPages,
impose,
@ -24,9 +33,29 @@ const toExport = {
}
export default toExport;
export type OperationsParametersBaseType = {
sortPagesWithPreset: SortPagesWithPresetParamsType;
rearrangePages: RearrangePagesParamsType;
selectPages: SelectPagesParamsType;
removePages: RemovePagesParamsType;
removeBlankPages: RemoveBlankPagesParamsType;
impose: ImposeParamsBaseType;
mergePDFs: MergeParamsType;
rotatePages: RotateParamsType;
scaleContent: ScaleContentParamsType;
scalePage: ScalePageParamsType;
splitOn: SplitOnParamsType;
splitPDF: SplitPdfParamsType;
updateMetadata: UpdateMetadataParams;
}
export type OperationsBaseType = typeof toExport;
// Overide fields in the type of toExport, with the given fields and types. This seems to magically work!
// https://dev.to/vborodulin/ts-how-to-override-properties-with-type-intersection-554l
type Override<T1, T2> = Omit<T1, keyof T2> & T2;
export type OperationsUseages = Override<typeof toExport, {
impose: (snapshot: any, nup: number, format: string) => any;
}>;
export type OperationsType = Override<OperationsBaseType, {
impose: (params: ImposeParamsType) => Promise<PdfFile>;
}>;
export type OperationsParametersType = Override<OperationsParametersBaseType, {
impose: ImposeParamsType;
}>;

View File

@ -1,37 +1,38 @@
import { Operation } from "../../declarations/Operation";
import { Action } from "../../declarations/Action";
import { PdfFile } from "../wrappers/PdfFile";
export function organizeWaitOperations(operations: Operation[]) {
export function organizeWaitOperations(actions: Action[]) {
// Initialize an object to store the counts and associated "done" operations
const waitCounts = {};
const doneOperations = {};
const waitCounts: {[key: string]: number} = {};
const doneOperations: {[key: string]: Action} = {};
// Function to count "type: wait" operations and associate "done" operations per id
function countWaitOperationsAndDone(operations: Operation[]) {
for (const operation of operations) {
if (operation.type === "wait") {
const id = operation.values.id;
function countWaitOperationsAndDone(actions: Action[]) {
for (const action of actions) {
if (action.type === "wait") {
const id = action.values.id;
if (id in waitCounts) {
waitCounts[id]++;
} else {
waitCounts[id] = 1;
}
}
if (operation.type === "done") {
const id = operation.values.id;
doneOperations[id] = operation;
if (action.type === "done") {
const id = action.values.id;
doneOperations[id] = action;
}
if (operation.operations) {
countWaitOperationsAndDone(operation.operations);
if (action.actions) {
countWaitOperationsAndDone(action.actions);
}
}
}
// Start counting and associating from the root operations
countWaitOperationsAndDone(operations);
countWaitOperationsAndDone(actions);
// Combine counts and associated "done" operations
const result = {};
const result: ResultType = {};
for (const id in waitCounts) {
result[id] = {
waitCount: waitCounts[id],
@ -42,3 +43,10 @@ export function organizeWaitOperations(operations: Operation[]) {
return result;
}
export type ResultType = {
[key: string]: {
waitCount: number,
doneOperation: Action,
input: PdfFile[]
}
}

View File

@ -1,39 +1,42 @@
import { organizeWaitOperations } from "./organizeWaitOperations.js";
import { Operation } from "../../declarations/Operation.js";
import { PDF } from "../../declarations/PDF.js";
import { organizeWaitOperations } from "./organizeWaitOperations";
import { Action } from "../../declarations/Action";
import { OperationsType } from "../../src/index";
import { PdfFile } from "../wrappers/PdfFile";
export async function * traverseOperations(operations: Operation[], input: PDF[] | PDF, Operations: AllOperations) {
import { ValuesType } from "../../declarations/TypeScriptUtils"
export async function * traverseOperations(operations: Action[], input: PdfFile[] | PdfFile, Operations: OperationsType) {
const waitOperations = organizeWaitOperations(operations);
let results: PDF[] = [];
let results: PdfFile[] = [];
yield* nextOperation(operations, input);
return results;
async function * nextOperation(operations: Operation[], input: PDF[] | PDF) {
if(Array.isArray(operations) && operations.length == 0) { // isEmpty
async function * nextOperation(actions: Action[], input: PdfFile[] | PdfFile): AsyncGenerator<any, any, unknown> {
if(Array.isArray(actions) && actions.length == 0) { // isEmpty
if(Array.isArray(input)) {
console.log("operation done: " + input[0].fileName + (input.length > 1 ? "+" : ""));
console.log("operation done: " + input[0].filename + (input.length > 1 ? "+" : ""));
results = results.concat(input);
return;
}
else {
console.log("operation done: " + input.fileName);
console.log("operation done: " + input.filename);
results.push(input);
return;
}
}
for (let i = 0; i < operations.length; i++) {
yield* computeOperation(operations[i], structuredClone(input));
for (let i = 0; i < actions.length; i++) {
yield* computeOperation(actions[i], structuredClone(input));
}
}
async function * computeOperation(operation: Operation, input: PDF|PDF[]) {
yield "Starting: " + operation.type;
switch (operation.type) {
async function * computeOperation(action: Action, input: PdfFile|PdfFile[]) {
yield "Starting: " + action.type;
switch (action.type) {
case "done": // Skip this, because it is a valid node.
break;
case "wait":
const waitOperation = waitOperations[operation.values.id];
const waitOperation = waitOperations[action.values.id];
if(Array.isArray(input)) {
waitOperation.input.concat(input); // TODO: May have unexpected concequences. Needs further testing!
@ -43,141 +46,138 @@ export async function * traverseOperations(operations: Operation[], input: PDF[]
}
waitOperation.waitCount--;
if(waitOperation.waitCount == 0) {
yield* nextOperation(waitOperation.doneOperation.operations, waitOperation.input);
if(waitOperation.waitCount == 0 && waitOperation.doneOperation.actions) {
yield* nextOperation(waitOperation.doneOperation.actions, waitOperation.input);
}
break;
case "extract":
yield* nToN(input, operation, async (input) => {
input.fileName += "_extractedPages";
input.buffer = await Operations.extractPages(input.buffer, operation.values["pagesToExtractArray"]);
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.selectPages({file: input, pagesToExtractArray: action.values["pagesToExtractArray"]});
newPdf.filename += "_extractedPages";
return newPdf;
});
break;
case "impose":
yield* nToN(input, operation, async (input) => {
input.fileName += "_imposed";
input.buffer = await Operations.impose(input.buffer, operation.values["nup"], operation.values["format"]);
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.impose({file: input, nup: action.values["nup"], format: action.values["format"]});
newPdf.filename += "_imposed";
return newPdf;
});
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 Operations.mergePDFs(inputs.map(input => input.buffer))
}
yield* nToOne(input, action, async (inputs) => {
const newPdf = await Operations.mergePDFs({files: inputs});
newPdf.filename = inputs.map(input => input.filename).join("_and_") + "_merged";
return newPdf;
});
break;
case "rotate":
yield* nToN(input, operation, async (input) => {
input.fileName += "_turned";
input.buffer = await Operations.rotatePages(input.buffer, operation.values["rotation"]);
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.rotatePages({file: input, rotation: action.values["rotation"]});
newPdf.filename += "_turned";
return newPdf;
});
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 Operations.splitPDF(input.buffer, operation.values["pagesToSplitAfterArray"]);
const splits = [];
yield* oneToN(input, action, async (input) => {
const splitResult = await Operations.splitPDF({file: input, splitAfterPageArray: action.values["splitAfterPageArray"]});
for (let j = 0; j < splitResult.length; j++) {
splits.push({
originalFileName: input.originalFileName,
fileName: input.fileName + "_split" + j,
buffer: splitResult[j]
})
splitResult[j].filename = splitResult[j].filename + "_split" + j;
}
return splits;
return splitResult;
});
break;
case "updateMetadata":
yield* nToN(input, operation, async (input) => {
input.fileName += "_metadataEdited";
input.buffer = await Operations.updateMetadata(input.buffer, operation.values["metadata"]);
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.updateMetadata({file: input, ...action.values["metadata"]});
newPdf.filename += "_metadataEdited";
return newPdf;
});
break;
case "organizePages":
yield* nToN(input, operation, async (input) => {
input.fileName += "_pagesOrganized";
input.buffer = await Operations.organizePages(input.buffer, operation.values["operation"], operation.values["customOrderString"]);
case "sortPagesWithPreset":
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.sortPagesWithPreset({file: input, sortPreset: action.values["sortPreset"], fancyPageSelector: action.values["fancyPageSelector"]});
newPdf.filename += "_pagesOrganized";
return newPdf;
});
break;
case "removeBlankPages":
yield* nToN(input, operation, async (input) => {
input.fileName += "_removedBlanks";
input.buffer = await Operations.removeBlankPages(input.buffer, operation.values["whiteThreashold"]);
yield* nToN(input, action, async (input) => {
const newPdf = await Operations.removeBlankPages({file: input, whiteThreashold: action.values["whiteThreashold"]});
newPdf.filename += "_removedBlanks";
return newPdf;
});
break;
case "splitOn":
yield* oneToN(input, operation, async (input) => {
const splitResult = await Operations.splitOn(input.buffer, operation.values["type"], operation.values["whiteThreashold"]);
const splits = [];
yield* oneToN(input, action, async (input) => {
const splitResult = await Operations.splitOn({file: input, type: action.values["type"], whiteThreashold: action.values["whiteThreashold"]});
for (let j = 0; j < splitResult.length; j++) {
splits.push({
originalFileName: input.originalFileName,
fileName: input.fileName + "_split" + j,
buffer: splitResult[j]
})
splitResult[j].filename = splitResult[j].filename + "_split" + j;
}
return splits;
return splitResult;
});
break;
default:
throw new Error(`${operation.type} not implemented yet.`);
throw new Error(`${action.type} not implemented yet.`);
break;
}
}
/**
*
* @param {PDF|PDF[]} input
* @param {JSON} operation
* @param {PdfFile|PdfFile[]} input
* @param {JSON} action
* @returns {undefined}
*/
async function * nToOne(inputs, operation, callback) {
inputs = Array.from(inputs); // Convert single values to array, keep arrays as is.
async function * nToOne(inputs: PdfFile|PdfFile[], action: Action, callback: (pdf: PdfFile[]) => Promise<PdfFile>): AsyncGenerator<any, any, unknown> {
const input = Array.isArray(inputs) ? inputs : [inputs]; // Convert single values to array, keep arrays as is.
inputs = await callback(inputs);
yield* nextOperation(operation.operations, inputs);
const newInputs = await callback(input);
if (action.actions) {
yield* nextOperation(action.actions, newInputs);
}
}
/**
*
* @param {PDF|PDF[]} input
* @param {JSON} operation
* @param {PdfFile|PdfFile[]} input
* @param {JSON} action
* @returns {undefined}
*/
async function * oneToN(input, operation, callback) {
async function * oneToN(input: PdfFile|PdfFile[], action: Action, callback: (pdf: PdfFile) => Promise<PdfFile[]>): AsyncGenerator<any, any, unknown> {
if(Array.isArray(input)) {
let output = [];
let output: PdfFile[] = [];
for (let i = 0; i < input.length; i++) {
output = output.concat(await callback(input[i]));
}
yield* nextOperation(operation.operations, output);
if (action.actions) {
yield* nextOperation(action.actions, output);
}
}
else {
input = await callback(input);
yield* nextOperation(operation.operations, input);
const nextInput = await callback(input);
if (action.actions) {
yield* nextOperation(action.actions, nextInput);
}
}
}
/**
*
* @param {PDF|PDF[]} input
* @param {JSON} operation
* @returns {undefined}
*/
async function * nToN(input, operation, callback) {
async function * nToN(input: PdfFile|PdfFile[], action: Action, callback: (pdf: PdfFile) => Promise<PdfFile>): AsyncGenerator<any, any, unknown> {
if(Array.isArray(input)) {
const nextInputs: PdfFile[] = []
for (let i = 0; i < input.length; i++) {
await callback(input[i]);
nextInputs.concat(await callback(input[i]));
}
if (action.actions) {
yield* nextOperation(action.actions, nextInputs);
}
yield* nextOperation(operation.operations, input);
}
else {
await callback(input);
yield* nextOperation(operation.operations, input);
const nextInput = await callback(input);
if (action.actions) {
yield* nextOperation(action.actions, nextInput);
}
}
}
}

View File

@ -2,6 +2,7 @@
import { PDFDocument } from 'pdf-lib';
import * as PDFJS from 'pdfjs-dist';
import { PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api';
import Joi from 'joi';
export class PdfFile {
byteArray: Uint8Array | null;
@ -57,10 +58,19 @@ export class PdfFile {
return file.pdfJs!;
}
}
export const PdfFileSchema = Joi.any().custom((value, helpers) => {
if (!(value instanceof PdfFile)) {
throw new Error('value is not a PdfFile');
}
return value;
}, "PdfFile validation");
export function fromMulterFile(value: Express.Multer.File): PdfFile {
return fromUint8Array(value.buffer, value.originalname)
}
export function fromMulterFiles(values: Express.Multer.File[]): PdfFile[] {
return values.map(v => fromUint8Array(v.buffer, v.originalname));
}
export function fromUint8Array(value: Uint8Array, filename: string): PdfFile {
const out = new PdfFile();
out.byteArray = value;