From a484a804adfb06e8f33ddeb301647d0f09155b5b Mon Sep 17 00:00:00 2001 From: Felix Kaspar Date: Fri, 17 May 2024 23:10:32 +0200 Subject: [PATCH] scaleContent, rotatePage allow negative rotations, validateOperations casts action.values now --- package-lock.json | 3 + shared-operations/package.json | 3 + .../src/functions/rotatePages.ts | 4 +- .../src/functions/scaleContent.ts | 109 ++++++++++++------ .../src/workflow/validateOperations.ts | 1 + .../src/wrappers/CommaArrayJoiExt.ts | 6 +- shared-operations/tsconfig.json | 3 +- 7 files changed, 90 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 53b26165e..9379b0ccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10651,6 +10651,9 @@ "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.2.67" + }, + "devDependencies": { + "@types/multer": "^1.4.11" } }, "shared-operations/node_modules/buffer": { diff --git a/shared-operations/package.json b/shared-operations/package.json index de891973c..bb6787f18 100644 --- a/shared-operations/package.json +++ b/shared-operations/package.json @@ -17,5 +17,8 @@ "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", "pdfjs-dist": "^4.2.67" + }, + "devDependencies": { + "@types/multer": "^1.4.11" } } diff --git a/shared-operations/src/functions/rotatePages.ts b/shared-operations/src/functions/rotatePages.ts index d6b203053..f5a19fae3 100644 --- a/shared-operations/src/functions/rotatePages.ts +++ b/shared-operations/src/functions/rotatePages.ts @@ -20,8 +20,8 @@ export class RotatePages extends Operator { protected static inputSchema = JoiPDFFileSchema.label(i18next.t("inputs.pdffile.name")).description(i18next.t("inputs.pdffile.description")); protected static valueSchema = Joi.object({ rotation: Joi.alternatives().try( - Joi.number().min(0).max(360).allow(null), - CommaArrayJoiExt.comma_array().items(Joi.number().integer().min(0).max(360)) + Joi.number().integer().min(-360).max(360), + CommaArrayJoiExt.comma_array().items(Joi.number().integer().min(-360).max(360)) ).label(i18next.t("values.rotation.friendlyName", { ns: "rotatePages" })).description(i18next.t("values.rotation.description", { ns: "rotatePages" })) .example("90").example("-180").example("[90, 0, 270]"), }); diff --git a/shared-operations/src/functions/scaleContent.ts b/shared-operations/src/functions/scaleContent.ts index 682e3c04a..7633456e4 100644 --- a/shared-operations/src/functions/scaleContent.ts +++ b/shared-operations/src/functions/scaleContent.ts @@ -1,42 +1,83 @@ +import { Operator, Progress, oneToOne } from "."; + +import Joi from "@stirling-tools/joi"; +import { JoiPDFFileSchema } from "../wrappers/PdfFileJoi"; + +import i18next from "i18next"; + +import CommaArrayJoiExt from "../wrappers/CommaArrayJoiExt"; + import { PDFPage } from "pdf-lib"; import { PdfFile, RepresentationType } from "../wrappers/PdfFile"; -export interface ScaleContentParamsType { - file: PdfFile; - scaleFactor: number|number[]; -} +export class ScaleContent extends Operator { + static type = "scaleContent"; -export async function scaleContent(params: ScaleContentParamsType): Promise { - const { file, scaleFactor } = params; - - const pdfDoc = await file.pdfLibDocument; - const pages = pdfDoc.getPages(); + /** + * Validation & Localisation + */ - if (Array.isArray(scaleFactor)) { - if (scaleFactor.length != pages.length) { - throw new Error(`Number of given scale factors '${scaleFactor.length}' is not the same as the number of pages '${pages.length}'`); - } - for (let i=0; i { scalePage(page, scaleFactor) }); + protected static inputSchema = JoiPDFFileSchema.label(i18next.t("inputs.pdffile.name")).description(i18next.t("inputs.pdffile.description")); + protected static valueSchema = Joi.object({ + scaleFactor: Joi.alternatives().try( + Joi.number(), + CommaArrayJoiExt.comma_array().items(Joi.number()) + ).label(i18next.t("values.scaleFactor.friendlyName", { ns: "scaleContent" })).description(i18next.t("values.scaleFactor.description", { ns: "scaleContent" })) + .example("2").example("1.5").example("[1, 1.5, 0.9]"), + }); + protected static outputSchema = JoiPDFFileSchema.label(i18next.t("outputs.pdffile.name")).description(i18next.t("outputs.pdffile.description")); + + static schema = Joi.object({ + input: ScaleContent.inputSchema, + values: ScaleContent.valueSchema.required(), + output: ScaleContent.outputSchema + }).label(i18next.t("friendlyName", { ns: "scaleContent" })).description(i18next.t("description", { ns: "scaleContent" })); + + + /** + * Logic + */ + + /** Detect and remove white pages */ + async run(input: PdfFile[], progressCallback: (state: Progress) => void): Promise { + return oneToOne(input, async (input, index, max) => { + + const pdfDoc = await input.pdfLibDocument; + const pages = pdfDoc.getPages(); + + // Different rotations applied to each page + if (Array.isArray(this.actionValues.scaleFactor)) { + if (this.actionValues.scaleFactor.length != pages.length) { + throw new Error(`Number of given rotations '${this.actionValues.scaleFactor.length}' is not the same as the number of pages '${pages.length}'`); + } + for (let pageIdx = 0; pageIdx < this.actionValues.scaleFactor.length; pageIdx++) { + ScaleContent.scalePageContent(pages[pageIdx], this.actionValues.scaleFactor[pageIdx]); + } + } + // Only one rotation applied to each page + else { + console.log(typeof this.actionValues.scaleFactor); + pages.forEach(page => { ScaleContent.scalePageContent(page, this.actionValues.scaleFactor) }); + } + + progressCallback({ curFileProgress: 1, operationProgress: index/max }); + + return new PdfFile(input.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, input.filename + "_rotated"); + }); } - return new PdfFile(file.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, file.filename+"_scaledContent"); -} - -function scalePage(page: PDFPage, scaleFactor: number) { - const width = page.getWidth(); - const height = page.getHeight(); - - // Scale content - page.scaleContent(scaleFactor, scaleFactor); - const scaled_diff = { - width: Math.round(width - scaleFactor * width), - height: Math.round(height - scaleFactor * height), - }; - - // Center content in new page format - page.translateContent(Math.round(scaled_diff.width / 2), Math.round(scaled_diff.height / 2)); + private static scalePageContent(page: PDFPage, scaleFactor: number) { + const width = page.getWidth(); + const height = page.getHeight(); + + // Scale content + page.scaleContent(scaleFactor, scaleFactor); + const scaled_diff = { + width: Math.round(width - scaleFactor * width), + height: Math.round(height - scaleFactor * height) + }; + + // Center content in new page format + page.translateContent(Math.round(scaled_diff.width / 2), Math.round(scaled_diff.height / 2)); + } } \ No newline at end of file diff --git a/shared-operations/src/workflow/validateOperations.ts b/shared-operations/src/workflow/validateOperations.ts index 260328cb4..3757c0549 100644 --- a/shared-operations/src/workflow/validateOperations.ts +++ b/shared-operations/src/workflow/validateOperations.ts @@ -24,6 +24,7 @@ export async function validateOperations(actions: Action[]): Promise<{ valid: bo if(validationResult.error) { return { valid: false, reason: validationResult.error.message}; } + action.values = validationResult.value.values; if (action.actions) { // Check io compatibility of the operators diff --git a/shared-operations/src/wrappers/CommaArrayJoiExt.ts b/shared-operations/src/wrappers/CommaArrayJoiExt.ts index 7ff0be82f..85520a16b 100644 --- a/shared-operations/src/wrappers/CommaArrayJoiExt.ts +++ b/shared-operations/src/wrappers/CommaArrayJoiExt.ts @@ -12,14 +12,16 @@ export default Joi.extend((joi) => { from: 'string', method(value, helpers) { - if (typeof value !== 'string' || !/(\d+)(,\s*\d+)*/.test(value)) { // is string and in format "[number], [number]" + if (typeof value !== 'string') { return; } try { return { value: value.split(",").map(v => v.trim()) }; } - catch (ignoreErr) { } + catch (err) { + helpers.error(err); + } } } diff --git a/shared-operations/tsconfig.json b/shared-operations/tsconfig.json index 67d4c786c..028d835db 100644 --- a/shared-operations/tsconfig.json +++ b/shared-operations/tsconfig.json @@ -8,7 +8,8 @@ "#pdfcpu": ["../../shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server"], }, "types": [ - "vite/client" + "vite/client", + "multer" ], } /* Specify a set of entries that re-map imports to additional lookup locations. */ } \ No newline at end of file