mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 23:45:02 +00:00
WIP joi validation for impose
This commit is contained in:
parent
09aa3a8bc9
commit
ba2588ea24
@ -42,10 +42,7 @@ router.post("/:workflowUuid?", [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace with static multer function of pdffile
|
const inputs = PdfFile.fromMulterFiles(req.files as Express.Multer.File[]);
|
||||||
const inputs = await Promise.all((req.files as Express.Multer.File[]).map(async file => {
|
|
||||||
return new PdfFile(file.originalname.replace(/\.[^/.]+$/, ""), new Uint8Array(await file.buffer), RepresentationType.Uint8Array, file.originalname.replace(/\.[^/.]+$/, ""));
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Allow option to do it synchronously and just make a long request
|
// Allow option to do it synchronously and just make a long request
|
||||||
if(req.body.async === "false") {
|
if(req.body.async === "false") {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { PdfFileSchema } from '../wrappers/PdfFile';
|
import { JoiPdfFileSchema } from '../wrappers/PdfFile';
|
||||||
|
|
||||||
export class RecordConstraint {
|
export class RecordConstraint {
|
||||||
|
|
||||||
@ -55,10 +55,10 @@ export class FieldConstraint {
|
|||||||
} else if (typeof this.type == 'string') {
|
} else if (typeof this.type == 'string') {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "file.pdf":
|
case "file.pdf":
|
||||||
schema = PdfFileSchema;
|
schema = JoiPdfFileSchema;
|
||||||
break;
|
break;
|
||||||
case "files.pdf":
|
case "files.pdf":
|
||||||
schema = Joi.array().items(PdfFileSchema);
|
schema = Joi.array().items(JoiPdfFileSchema);
|
||||||
break;
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
schema = Joi.string();
|
schema = Joi.string();
|
||||||
|
@ -1,27 +1,117 @@
|
|||||||
|
|
||||||
import { PdfFile, RepresentationType } from "../wrappers/PdfFile";
|
import { PdfFile, RepresentationType } from "../wrappers/PdfFile";
|
||||||
import { FieldConstraint, RecordConstraint } from '../dynamic-ui/OperatorConstraints'
|
import { FieldConstraint, RecordConstraint } from '../dynamic-ui/OperatorConstraints'
|
||||||
import { IOType, Operator, Progress } from ".";
|
import { Operator, Progress, oneToOne } from ".";
|
||||||
|
|
||||||
import * as pdfcpuWrapper from "#pdfcpu"; // This is updated by tsconfig.json/paths for the context (browser, node, etc.) this module is used in.
|
import * as pdfcpuWrapper from "#pdfcpu"; // This is updated by tsconfig.json/paths for the context (browser, node, etc.) this module is used in.
|
||||||
|
|
||||||
export type ImposeParamsType = {
|
import Joi from "joi";
|
||||||
file: PdfFile;
|
import { JoiPDFFileSchema } from "../wrappers/PdfFileJoi";
|
||||||
/** Accepted values are 2, 3, 4, 8, 9, 12, 16 - see: {@link https://pdfcpu.io/generate/nup.html#n-up-value} */
|
|
||||||
nup: 2 | 3 | 4 | 8 | 9 | 12 | 16;
|
|
||||||
/** A0-A10, other formats available - see: {@link https://pdfcpu.io/paper.html} */
|
// TODO: This will be replaced by a real translator
|
||||||
format: string;
|
const translationObject = {
|
||||||
}
|
operators: {
|
||||||
|
nup: {
|
||||||
|
friendlyName: "PDF-Imposition / PDF-N-Up",
|
||||||
|
description: "Put multiple pages of the input document into a single page of the output document.",
|
||||||
|
values: {
|
||||||
|
nup: {
|
||||||
|
friendlyName: "Page Format",
|
||||||
|
description: "The Page Size of the ouput document. Append L or P to force Landscape or Portrait."
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
friendlyName: "N-Up-Value",
|
||||||
|
description: "How many pages should be in one output page"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
pdfFile: {
|
||||||
|
name: "PDF-File(s)",
|
||||||
|
description: "This operator takes a PDF-File(s) as input"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
pdfFile: {
|
||||||
|
name: "PDF-File(s)",
|
||||||
|
description: "This operator outputs PDF-File(s)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Impose extends Operator {
|
export class Impose extends Operator {
|
||||||
static type: string = "impose";
|
static type: string = "impose";
|
||||||
|
|
||||||
static mayInput: IOType = IOType.PDF;
|
/**
|
||||||
static willOutput: IOType = IOType.PDF;
|
* Validation
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inputSchema = JoiPDFFileSchema.label(translationObject.inputs.pdfFile.name).description(translationObject.inputs.pdfFile.description);
|
||||||
|
static valueSchema = Joi.object({
|
||||||
|
nup: Joi.number().integer().valid(2, 3, 4, 8, 9, 12, 16).required()
|
||||||
|
.label(translationObject.operators.nup.values.nup.friendlyName).description(translationObject.operators.nup.values.nup.description)
|
||||||
|
.example("3").example("4"),
|
||||||
|
format: Joi.string().valid(...[
|
||||||
|
// ISO 216:1975 A
|
||||||
|
"4A0", "2A0", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10",
|
||||||
|
|
||||||
|
// ISO 216:1975 B
|
||||||
|
"B0+", "B0", "B1+", "B1", "B2+", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10",
|
||||||
|
|
||||||
|
// ISO 269:1985 C
|
||||||
|
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
|
||||||
|
|
||||||
|
// ISO 217:2013 untrimmed
|
||||||
|
"RA0", "RA1", "RA2", "RA3", "RA4", "SRA0", "SRA1", "SRA2", "SRA3", "SRA4", "SRA1+", "SRA2+", "SRA3+", "SRA3++",
|
||||||
|
|
||||||
|
// American
|
||||||
|
"SuperB", "Tabloid", "Legal", "GovLegal", "Letter", "GovLetter", "Executive", "HalfLetter", "JuniorLegal", "Photo",
|
||||||
|
|
||||||
|
// ANSI/ASME Y14.1
|
||||||
|
"ANSIA", "ANSIB", "ANSIC", "ANSID", "ANSIE", "ANSIF",
|
||||||
|
|
||||||
|
// ANSI/ASME Y14.1 Architectural series
|
||||||
|
"ARCHA", "ARCHB", "ARCHC", "ARCHD", "ARCHE", "ARCHE1", "ARCHE2", "ARCHE3",
|
||||||
|
|
||||||
|
// American uncut
|
||||||
|
"Bond", "Book", "Cover", "Index", "NewsPrint", "Offset",
|
||||||
|
|
||||||
|
// English uncut
|
||||||
|
"Crown", "DoubleCrown", "Quad", "Demy", "DoubleDemy", "Medium", "Royal", "SuperRoyal",
|
||||||
|
"DoublePott", "DoublePost", "Foolscap", "DoubleFoolscap",
|
||||||
|
|
||||||
|
// F4
|
||||||
|
|
||||||
|
// China GB/T 148-1997 D Series
|
||||||
|
"D0", "D1", "D2", "D3", "D4", "D5", "D6",
|
||||||
|
"RD0", "RD1", "RD2", "RD3", "RD4", "RD5", "RD6",
|
||||||
|
|
||||||
|
// Japan
|
||||||
|
"JIS-B0", "JIS-B1", "JIS-B2", "JIS-B3", "JIS-B4", "JIS-B5", "JIS-B6",
|
||||||
|
"JIS-B7", "JIS-B8", "JIS-B9", "JIS-B10", "JIS-B11", "JIS-B12",
|
||||||
|
"Shirokuban4", "Shirokuban5", "Shirokuban6", "Kiku4", "Kiku5", "AB", "B40", "Shikisen"
|
||||||
|
].flatMap(size => [size, size + "P", size + "L"]))
|
||||||
|
.required()
|
||||||
|
.label(translationObject.operators.nup.values.format.friendlyName).description(translationObject.operators.nup.values.format.description)
|
||||||
|
.example("A4").example("A3L")
|
||||||
|
});
|
||||||
|
static outputSchema = JoiPDFFileSchema.label(translationObject.outputs.pdfFile.name).description(translationObject.outputs.pdfFile.description);
|
||||||
|
|
||||||
|
static schema = Joi.object({
|
||||||
|
input: Impose.inputSchema.required(),
|
||||||
|
values: Impose.valueSchema.required(),
|
||||||
|
output: Impose.outputSchema.optional()
|
||||||
|
}).label(translationObject.operators.nup.friendlyName).description(translationObject.operators.nup.description);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logic
|
||||||
|
*/
|
||||||
|
|
||||||
/** PDF-Imposition, PDF-N-Up: Put multiple pages of the input document into a single page of the output document. - see: {@link https://en.wikipedia.org/wiki/N-up} */
|
/** PDF-Imposition, PDF-N-Up: Put multiple pages of the input document into a single page of the output document. - see: {@link https://en.wikipedia.org/wiki/N-up} */
|
||||||
async run(input: PdfFile[], progressCallback: (state: Progress) => void): Promise<PdfFile[]> {
|
async run(input: PdfFile[], progressCallback: (state: Progress) => void): Promise<PdfFile[]> {
|
||||||
return this.oneToOne<PdfFile, PdfFile>(input, async (input, index, max) => {
|
return oneToOne<PdfFile, PdfFile>(input, async (input, index, max) => {
|
||||||
//TODO: Support custom Page Sizes
|
//TODO: Support custom Page Sizes
|
||||||
// https://pdfcpu.io/generate/nup.html
|
// https://pdfcpu.io/generate/nup.html
|
||||||
const uint8Array = await pdfcpuWrapper.oneToOne(
|
const uint8Array = await pdfcpuWrapper.oneToOne(
|
||||||
@ -57,67 +147,13 @@ export class Impose extends Operator {
|
|||||||
if(!baseValidationResults.valid)
|
if(!baseValidationResults.valid)
|
||||||
return baseValidationResults;
|
return baseValidationResults;
|
||||||
|
|
||||||
|
// TODO: Fully integrate joi in the base and remove this func
|
||||||
|
|
||||||
// TODO: This should be ported to SaudF's RecordValidator
|
this.actionValues = Impose.valueSchema.validate(this.actionValues);
|
||||||
if(!this.actionValues.nup) {
|
|
||||||
return { valid: false, reason: "nup is not defined" }
|
|
||||||
}
|
|
||||||
if(!(ImposeParamConstraints.record["nup"].type as number[]).includes(parseInt(this.actionValues.nup))) {
|
|
||||||
return { valid: false, reason: "NUp accepted values are 2, 3, 4, 8, 9, 12, 16 - see: https://pdfcpu.io/generate/nup.html#n-up-value"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.actionValues.format) {
|
if(this.actionValues.error)
|
||||||
return { valid: false, reason: "format is not defined" }
|
return { valid: false, reason: this.actionValues }
|
||||||
}
|
|
||||||
if(!(ImposeParamConstraints.record["format"].type as string[]).includes(this.actionValues.format)) {
|
|
||||||
return { valid: false, reason: "invalid fromat provided - see: https://pdfcpu.io/paper.html"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: true }
|
return { valid: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImposeParamConstraints = new RecordConstraint({
|
|
||||||
file: new FieldConstraint("display.key", "file.pdf", true, "hint.key"),
|
|
||||||
nup: new FieldConstraint("display.key", [2, 3, 4, 8, 9, 12, 16], true, "hint.key"),
|
|
||||||
format: new FieldConstraint("display.key", [
|
|
||||||
// ISO 216:1975 A
|
|
||||||
"4A0", "2A0", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10",
|
|
||||||
|
|
||||||
// ISO 216:1975 B
|
|
||||||
"B0+", "B0", "B1+", "B1", "B2+", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10",
|
|
||||||
|
|
||||||
// ISO 269:1985 C
|
|
||||||
"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
|
|
||||||
|
|
||||||
// ISO 217:2013 untrimmed
|
|
||||||
"RA0", "RA1", "RA2", "RA3", "RA4", "SRA0", "SRA1", "SRA2", "SRA3", "SRA4", "SRA1+", "SRA2+", "SRA3+", "SRA3++",
|
|
||||||
|
|
||||||
// American
|
|
||||||
"SuperB", "Tabloid", "Legal", "GovLegal", "Letter", "GovLetter", "Executive", "HalfLetter", "JuniorLegal", "Photo",
|
|
||||||
|
|
||||||
// ANSI/ASME Y14.1
|
|
||||||
"ANSIA", "ANSIB", "ANSIC", "ANSID", "ANSIE", "ANSIF",
|
|
||||||
|
|
||||||
// ANSI/ASME Y14.1 Architectural series
|
|
||||||
"ARCHA", "ARCHB", "ARCHC", "ARCHD", "ARCHE", "ARCHE1", "ARCHE2", "ARCHE3",
|
|
||||||
|
|
||||||
// American uncut
|
|
||||||
"Bond", "Book", "Cover", "Index", "NewsPrint", "Offset",
|
|
||||||
|
|
||||||
// English uncut
|
|
||||||
"Crown", "DoubleCrown", "Quad", "Demy", "DoubleDemy", "Medium", "Royal", "SuperRoyal",
|
|
||||||
"DoublePott", "DoublePost", "Foolscap", "DoubleFoolscap",
|
|
||||||
|
|
||||||
// F4
|
|
||||||
|
|
||||||
// China GB/T 148-1997 D Series
|
|
||||||
"D0", "D1", "D2", "D3", "D4", "D5", "D6",
|
|
||||||
"RD0", "RD1", "RD2", "RD3", "RD4", "RD5", "RD6",
|
|
||||||
|
|
||||||
// Japan
|
|
||||||
"JIS-B0", "JIS-B1", "JIS-B2", "JIS-B3", "JIS-B4", "JIS-B5", "JIS-B6",
|
|
||||||
"JIS-B7", "JIS-B8", "JIS-B9", "JIS-B10", "JIS-B11", "JIS-B12",
|
|
||||||
"Shirokuban4", "Shirokuban5", "Shirokuban6", "Kiku4", "Kiku5", "AB", "B40", "Shikisen"
|
|
||||||
].flatMap(size => [size, size + "P", size + "L"]), true, "hint.key"),
|
|
||||||
})
|
|
@ -1,8 +1,5 @@
|
|||||||
import { Action } from "../../declarations/Action";
|
import { Action } from "../../declarations/Action";
|
||||||
|
import Joi from "joi";
|
||||||
export enum IOType {
|
|
||||||
PDF, Image, Text // TODO: Extend with Document File Types
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ValidationResult {
|
export interface ValidationResult {
|
||||||
valid: boolean,
|
valid: boolean,
|
||||||
@ -17,12 +14,14 @@ export interface Progress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Operator {
|
export class Operator {
|
||||||
/** The type of the operator in camelCase (impose, merge, etc.) */
|
/** The internal name of the operator in camelCase (impose, merge, etc.) */
|
||||||
static type: string;
|
static type: string;
|
||||||
|
|
||||||
// This will most likely be needed in the node Editor
|
/** The Joi validators & decorators */
|
||||||
static mayInput: IOType;
|
static inputSchema: Joi.Schema;
|
||||||
static willOutput: IOType;
|
static valueSchema: Joi.Schema;
|
||||||
|
static outputSchema: Joi.Schema;
|
||||||
|
static schema: Joi.Schema;
|
||||||
|
|
||||||
actionValues: any;
|
actionValues: any;
|
||||||
|
|
||||||
@ -40,26 +39,25 @@ export class Operator {
|
|||||||
}
|
}
|
||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** This function should be used if the Operation may take multiple files as inputs and only outputs one file */
|
/** This function should be used if the Operation may take multiple files as inputs and only outputs one file */
|
||||||
protected async nToOne <I, O>(inputs: I[], callback: (input: I[]) => Promise<O>): Promise<O[]> {
|
export async function nToOne <I, O>(inputs: I[], callback: (input: I[]) => Promise<O>): Promise<O[]> {
|
||||||
return [await callback(inputs)];
|
return [await callback(inputs)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This function should be used if the Operation takes one file as input and may output multiple files */
|
||||||
|
export async function oneToN <I, O>(inputs: I[], callback: (input: I, index: number, max: number) => Promise<O[]>): Promise<O[]> {
|
||||||
|
let output: O[] = []
|
||||||
|
for (let i = 0; i < inputs.length; i++) {
|
||||||
|
output = output.concat(await callback(inputs[i], i, inputs.length));
|
||||||
}
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
/** This function should be used if the Operation takes one file as input and may output multiple files */
|
/** This function should be used if the Operation takes one file as input and outputs only one file */
|
||||||
protected async oneToN <I, O>(inputs: I[], callback: (input: I, index: number, max: number) => Promise<O[]>): Promise<O[]> {
|
export async function oneToOne <I, O>(inputs: I[], callback: (input: I, index: number, max: number) => Promise<O>): Promise<O[]> {
|
||||||
let output: O[] = []
|
return oneToN(inputs, async (input, index, max) => {
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
return [await callback(input, index, max)]
|
||||||
output = output.concat(await callback(inputs[i], i, inputs.length));
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** This function should be used if the Operation takes one file as input and outputs only one file */
|
|
||||||
protected async oneToOne <I, O>(inputs: I[], callback: (input: I, index: number, max: number) => Promise<O>): Promise<O[]> {
|
|
||||||
return this.oneToN(inputs, async (input, index, max) => {
|
|
||||||
return [await callback(input, index, max)]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
import { PDFPage } from 'pdf-lib';
|
import { PDFPage } from 'pdf-lib';
|
||||||
import { PdfFile, RepresentationType, PdfFileSchema } from '../wrappers/PdfFile';
|
import { PdfFile, RepresentationType, JoiPdfFileSchema } from '../wrappers/PdfFile';
|
||||||
|
|
||||||
const whSchema = Joi.string().custom((value, helpers) => {
|
const whSchema = Joi.string().custom((value, helpers) => {
|
||||||
console.log("value.pageSize", typeof value)
|
console.log("value.pageSize", typeof value)
|
||||||
@ -23,7 +23,7 @@ const whSchema = Joi.string().custom((value, helpers) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const ScalePageSchema = Joi.object({
|
export const ScalePageSchema = Joi.object({
|
||||||
file: PdfFileSchema.required(),
|
file: JoiPdfFileSchema.required(),
|
||||||
pageSize: Joi.alternatives().try(whSchema, Joi.array().items(whSchema)).required(),
|
pageSize: Joi.alternatives().try(whSchema, Joi.array().items(whSchema)).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ export class PdfFile {
|
|||||||
|
|
||||||
this.filename = filename ? filename : originalFilename;
|
this.filename = filename ? filename : originalFilename;
|
||||||
if (this.filename.toLowerCase().endsWith(".pdf"))
|
if (this.filename.toLowerCase().endsWith(".pdf"))
|
||||||
this.filename = this.filename.slice(0, -4);
|
this.filename = this.filename.slice(0, -4);
|
||||||
|
|
||||||
this.representation = representation;
|
this.representation = representation;
|
||||||
this.representationType = representationType;
|
this.representationType = representationType;
|
||||||
@ -99,6 +99,7 @@ export class PdfFile {
|
|||||||
|
|
||||||
static fromMulterFile(value: Express.Multer.File): PdfFile {
|
static fromMulterFile(value: Express.Multer.File): PdfFile {
|
||||||
return new PdfFile(value.originalname, value.buffer as Uint8Array, RepresentationType.Uint8Array);
|
return new PdfFile(value.originalname, value.buffer as Uint8Array, RepresentationType.Uint8Array);
|
||||||
|
|
||||||
}
|
}
|
||||||
static fromMulterFiles(values: Express.Multer.File[]): PdfFile[] {
|
static fromMulterFiles(values: Express.Multer.File[]): PdfFile[] {
|
||||||
return values.map(v => PdfFile.fromMulterFile(v));
|
return values.map(v => PdfFile.fromMulterFile(v));
|
||||||
@ -128,11 +129,4 @@ export class PdfFile {
|
|||||||
}));
|
}));
|
||||||
return docCache;
|
return docCache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PdfFileSchema = Joi.any().custom((value) => {
|
|
||||||
if (!(value instanceof PdfFile)) {
|
|
||||||
throw new Error('value is not a PdfFile');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}, "PdfFile validation");
|
|
14
shared-operations/src/wrappers/PdfFileJoi.ts
Normal file
14
shared-operations/src/wrappers/PdfFileJoi.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Joi from "joi";
|
||||||
|
import { PdfFile } from "./PdfFile";
|
||||||
|
|
||||||
|
export const JoiPDFFileSchema = Joi.binary().custom((value: Express.Multer.File[] | PdfFile, helpers) => {
|
||||||
|
if (!(value instanceof PdfFile)) {
|
||||||
|
try {
|
||||||
|
return PdfFile.fromMulterFiles(value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw new Error('value is not of type PdfFile');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}, "pdffile validation");
|
Loading…
x
Reference in New Issue
Block a user