scaleContent, rotatePage allow negative rotations, validateOperations casts action.values now

This commit is contained in:
Felix Kaspar 2024-05-17 23:10:32 +02:00
parent 6f4bb8242b
commit a484a804ad
7 changed files with 90 additions and 39 deletions

3
package-lock.json generated
View File

@ -10651,6 +10651,9 @@
"next-i18next": "^15.1.1", "next-i18next": "^15.1.1",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^4.2.67" "pdfjs-dist": "^4.2.67"
},
"devDependencies": {
"@types/multer": "^1.4.11"
} }
}, },
"shared-operations/node_modules/buffer": { "shared-operations/node_modules/buffer": {

View File

@ -17,5 +17,8 @@
"next-i18next": "^15.1.1", "next-i18next": "^15.1.1",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"pdfjs-dist": "^4.2.67" "pdfjs-dist": "^4.2.67"
},
"devDependencies": {
"@types/multer": "^1.4.11"
} }
} }

View File

@ -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 inputSchema = JoiPDFFileSchema.label(i18next.t("inputs.pdffile.name")).description(i18next.t("inputs.pdffile.description"));
protected static valueSchema = Joi.object({ protected static valueSchema = Joi.object({
rotation: Joi.alternatives().try( rotation: Joi.alternatives().try(
Joi.number().min(0).max(360).allow(null), Joi.number().integer().min(-360).max(360),
CommaArrayJoiExt.comma_array().items(Joi.number().integer().min(0).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" })) ).label(i18next.t("values.rotation.friendlyName", { ns: "rotatePages" })).description(i18next.t("values.rotation.description", { ns: "rotatePages" }))
.example("90").example("-180").example("[90, 0, 270]"), .example("90").example("-180").example("[90, 0, 270]"),
}); });

View File

@ -1,32 +1,72 @@
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 { PDFPage } from "pdf-lib";
import { PdfFile, RepresentationType } from "../wrappers/PdfFile"; import { PdfFile, RepresentationType } from "../wrappers/PdfFile";
export interface ScaleContentParamsType { export class ScaleContent extends Operator {
file: PdfFile; static type = "scaleContent";
scaleFactor: number|number[];
}
export async function scaleContent(params: ScaleContentParamsType): Promise<PdfFile> { /**
const { file, scaleFactor } = params; * Validation & Localisation
*/
const pdfDoc = await file.pdfLibDocument; 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<PdfFile[]> {
return oneToOne<PdfFile, PdfFile>(input, async (input, index, max) => {
const pdfDoc = await input.pdfLibDocument;
const pages = pdfDoc.getPages(); const pages = pdfDoc.getPages();
if (Array.isArray(scaleFactor)) { // Different rotations applied to each page
if (scaleFactor.length != pages.length) { if (Array.isArray(this.actionValues.scaleFactor)) {
throw new Error(`Number of given scale factors '${scaleFactor.length}' is not the same as the number of pages '${pages.length}'`); 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 i=0; i<scaleFactor.length; i++) { for (let pageIdx = 0; pageIdx < this.actionValues.scaleFactor.length; pageIdx++) {
scalePage(pages[i], scaleFactor[i]); ScaleContent.scalePageContent(pages[pageIdx], this.actionValues.scaleFactor[pageIdx]);
} }
} else { }
pages.forEach(page => { scalePage(page, scaleFactor) }); // Only one rotation applied to each page
else {
console.log(typeof this.actionValues.scaleFactor);
pages.forEach(page => { ScaleContent.scalePageContent(page, this.actionValues.scaleFactor) });
} }
return new PdfFile(file.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, file.filename+"_scaledContent"); progressCallback({ curFileProgress: 1, operationProgress: index/max });
return new PdfFile(input.originalFilename, pdfDoc, RepresentationType.PDFLibDocument, input.filename + "_rotated");
});
} }
function scalePage(page: PDFPage, scaleFactor: number) { private static scalePageContent(page: PDFPage, scaleFactor: number) {
const width = page.getWidth(); const width = page.getWidth();
const height = page.getHeight(); const height = page.getHeight();
@ -34,9 +74,10 @@ function scalePage(page: PDFPage, scaleFactor: number) {
page.scaleContent(scaleFactor, scaleFactor); page.scaleContent(scaleFactor, scaleFactor);
const scaled_diff = { const scaled_diff = {
width: Math.round(width - scaleFactor * width), width: Math.round(width - scaleFactor * width),
height: Math.round(height - scaleFactor * height), height: Math.round(height - scaleFactor * height)
}; };
// Center content in new page format // Center content in new page format
page.translateContent(Math.round(scaled_diff.width / 2), Math.round(scaled_diff.height / 2)); page.translateContent(Math.round(scaled_diff.width / 2), Math.round(scaled_diff.height / 2));
} }
}

View File

@ -24,6 +24,7 @@ export async function validateOperations(actions: Action[]): Promise<{ valid: bo
if(validationResult.error) { if(validationResult.error) {
return { valid: false, reason: validationResult.error.message}; return { valid: false, reason: validationResult.error.message};
} }
action.values = validationResult.value.values;
if (action.actions) { if (action.actions) {
// Check io compatibility of the operators // Check io compatibility of the operators

View File

@ -12,14 +12,16 @@ export default Joi.extend((joi) => {
from: 'string', from: 'string',
method(value, helpers) { method(value, helpers) {
if (typeof value !== 'string' || !/(\d+)(,\s*\d+)*/.test(value)) { // is string and in format "[number], [number]" if (typeof value !== 'string') {
return; return;
} }
try { try {
return { value: value.split(",").map(v => v.trim()) }; return { value: value.split(",").map(v => v.trim()) };
} }
catch (ignoreErr) { } catch (err) {
helpers.error(err);
}
} }
} }

View File

@ -8,7 +8,8 @@
"#pdfcpu": ["../../shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server"], "#pdfcpu": ["../../shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server"],
}, },
"types": [ "types": [
"vite/client" "vite/client",
"multer"
], ],
} /* Specify a set of entries that re-map imports to additional lookup locations. */ } /* Specify a set of entries that re-map imports to additional lookup locations. */
} }