From 7df48d43d640d1e4a2b68601ee4941a851fc285e Mon Sep 17 00:00:00 2001 From: Saud Fatayerji Date: Sun, 19 Nov 2023 17:22:17 +0300 Subject: [PATCH] created OperatorConstraints and impelemented it in impose --- .../src/routes/api/operations-controller.ts | 26 +++--- server-node/src/utils/pdf-operations.ts | 11 ++- .../src/dynamic-ui/OperatorConstraints.ts | 85 +++++++++++++++++++ shared-operations/src/functions/impose.ts | 11 ++- shared-operations/src/index.ts | 48 +++++------ .../src/workflow/traverseOperations.ts | 14 +-- shared-operations/src/wrappers/PdfFile.ts | 3 +- 7 files changed, 142 insertions(+), 56 deletions(-) create mode 100644 shared-operations/src/dynamic-ui/OperatorConstraints.ts diff --git a/server-node/src/routes/api/operations-controller.ts b/server-node/src/routes/api/operations-controller.ts index 1538babd2..cceda0722 100644 --- a/server-node/src/routes/api/operations-controller.ts +++ b/server-node/src/routes/api/operations-controller.ts @@ -1,8 +1,9 @@ import Operations from '../../utils/pdf-operations'; import { respondWithPdfFile, respondWithPdfFiles, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils'; -import { PdfFile, PdfFileSchema } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile' -import { ScalePageSchema } from '@stirling-pdf/shared-operations/src/functions/scalePage' +import { PdfFile, /*PdfFileSchema*/ } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile' +//import { ScalePageSchema } from '@stirling-pdf/shared-operations/src/functions/scalePage' +import { OperatorType } from '@stirling-pdf/shared-operations/src'; import express, { Request, Response, RequestHandler } from 'express'; const router = express.Router(); @@ -13,8 +14,7 @@ import Joi from 'joi'; function registerEndpoint(endpoint: string, nameToAppend: string, fileHandler: RequestHandler, - operationFunction: (params: any) => Promise, - joiSchema: Joi.ObjectSchema + operator: OperatorType ): void { router.post(endpoint, fileHandler, async function(req: Request, res: Response) { const body = req.body; @@ -31,13 +31,13 @@ function registerEndpoint(endpoint: string, } console.log(req.body) - const { error, value } = joiSchema.validate(req.body); + const { error, value } = operator.spec.toJoiSchema().validate(req.body); if (error) { res.status(400).send(error.details); return; } - const processed = await operationFunction(value) + const processed = await operator.exec(value) if (body.files && Array.isArray(processed)) { // MIMO respondWithPdfFiles(res, processed, nameToAppend); @@ -55,7 +55,7 @@ function registerEndpoint(endpoint: string, ///////////////////// // Page Operations // ///////////////////// -registerEndpoint("/merge-pdfs", "", upload.any(), Operations.mergePDFs, Joi.object({ +/*registerEndpoint("/merge-pdfs", "", upload.any(), Operations.mergePDFs, Joi.object({ files: Joi.array().items(PdfFileSchema).required(), }).required()); @@ -78,13 +78,9 @@ registerEndpoint("/remove-pages", "", upload.single("file"), Operations.removePa file: PdfFileSchema.required(), pageSelector: Joi.string().required(), }).required()); - -registerEndpoint("/impose", "", upload.single("file"), Operations.impose, Joi.object({ - file: PdfFileSchema.required(), - nup: Joi.number().valid(2, 3, 4, 8, 9, 12, 16).required(), - format: Joi.string().required(), -}).required()); - +*/ +registerEndpoint("/impose", "", upload.single("file"), Operations.Impose); +/* registerEndpoint("/scale-pages", "", upload.single("file"), Operations.scalePage, ScalePageSchema.required()); //Auto Split Pages @@ -159,5 +155,5 @@ registerEndpoint("/update-metadata", "", upload.single("file"), Operations.updat //Auto Rename //Get info //Show JS - +*/ export default router; \ No newline at end of file diff --git a/server-node/src/utils/pdf-operations.ts b/server-node/src/utils/pdf-operations.ts index 48f58d10d..3ff6f9574 100644 --- a/server-node/src/utils/pdf-operations.ts +++ b/server-node/src/utils/pdf-operations.ts @@ -1,17 +1,16 @@ -import SharedOperations, { OperationsType } from "@stirling-pdf/shared-operations/src" -import { ImposeParamsType } from '@stirling-pdf/shared-operations/src/functions/impose' +import SharedOperations, { OperatorsType, OperatorParametersType } from "@stirling-pdf/shared-operations/src" import { PdfFile } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile" // Import injected libraries here! import * as pdfcpuWrapper from "@stirling-pdf/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper-node.js"; -async function impose(params: ImposeParamsType): Promise { - return SharedOperations.impose(params, pdfcpuWrapper); +async function impose(params: OperatorParametersType["Impose"]): Promise { + return SharedOperations.Impose.exec(params, pdfcpuWrapper); } -const toExport: OperationsType = { +const toExport: OperatorsType = { ...SharedOperations, - impose, + Impose: {exec: impose, spec: SharedOperations.Impose.spec}, } export default toExport; diff --git a/shared-operations/src/dynamic-ui/OperatorConstraints.ts b/shared-operations/src/dynamic-ui/OperatorConstraints.ts new file mode 100644 index 000000000..7455ff7b1 --- /dev/null +++ b/shared-operations/src/dynamic-ui/OperatorConstraints.ts @@ -0,0 +1,85 @@ + +import Joi from 'joi'; +import { PdfFileSchema } from '../wrappers/PdfFile'; + +export class RecordConstraint { + + record: Record; + + constructor(record: Record) { + this.record = record; + } + + toJoiSchema() { + const newSchemaObj: any = {}; + Object.keys(this.record).forEach(key => { + newSchemaObj[key] = this.record[key].toJoiSchema(); + }); + return Joi.object(newSchemaObj); + } + +}; + +export class FieldConstraint { + + displayNameKey: string; + type: "file.pdf" | "files.pdf" | "string" | "number" | number[] | string[] | RecordConstraint; + required: boolean; + hintKey?: string; + customSchema?: Joi.Schema; + + constructor(displayNameKey: string, + type: "file.pdf" | "files.pdf" | "string" | "number" | number[] | string[] | RecordConstraint, + required: boolean, + hintKey?: string, + customSchema?: Joi.Schema) { + this.displayNameKey = displayNameKey; + this.type = type; + this.required = required; + this.hintKey = hintKey; + this.customSchema = customSchema; + } + + toJoiSchema(): Joi.Schema { + if (this.customSchema) return this.customSchema; + + var schema: Joi.Schema; + if (Array.isArray(this.type)) { + if (this.type.every(e => typeof e == 'string')) { + schema = Joi.string().valid(...this.type); + } else if (this.type.every(e => typeof e == 'number')) { + schema = Joi.number().valid(...this.type); + } else { + schema = Joi.any().valid(this.type); + } + } else if (typeof this.type == 'string') { + switch (this.type) { + case "file.pdf": + schema = PdfFileSchema; + break; + case "files.pdf": + schema = Joi.array().items(PdfFileSchema); + break; + case "string": + schema = Joi.string(); + break; + case "number": + schema = Joi.number(); + break; + default: + throw new Error(`UiConf type '${this.type}' not supported`) + } + } else if (this.type instanceof FieldConstraint) { + schema = this.type.toJoiSchema() + } else { + throw new Error(`UiConf type '${this.type}' not supported`) + } + + if (this.required) { + schema = schema.required(); + } + + return schema; + } + +} diff --git a/shared-operations/src/functions/impose.ts b/shared-operations/src/functions/impose.ts index 7bf3d25cb..be3d752f2 100644 --- a/shared-operations/src/functions/impose.ts +++ b/shared-operations/src/functions/impose.ts @@ -1,13 +1,20 @@ import { PdfFile, RepresentationType } from "../wrappers/PdfFile"; +import { FieldConstraint, RecordConstraint } from '../dynamic-ui/OperatorConstraints' export type ImposeParamsType = { file: PdfFile; /** Accepted values are 2, 3, 4, 8, 9, 12, 16 - see: {@link https://pdfcpu.io/generate/nup.html#n-up-value} */ - nup: number; + nup: 2 | 3 | 4 | 8 | 9 | 12 | 16; /** A0-A10, other formats available - see: {@link https://pdfcpu.io/paper.html} */ format: string; } +export const ImposeParamConstraints = new RecordConstraint({ + file: new FieldConstraint("display", "file.pdf", true, "hint"), + nup: new FieldConstraint("display", [2, 3, 4, 8, 9, 12, 16], true, "hint"), + format: new FieldConstraint("display", "string", true, "hint"), +}) + /** 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} */ export async function impose(params: ImposeParamsType, pdfcpuWrapper: any): Promise { // https://pdfcpu.io/generate/nup.html @@ -34,4 +41,4 @@ export async function impose(params: ImposeParamsType, pdfcpuWrapper: any): Prom console.log("ImposeResult: ", result); return result; -} \ No newline at end of file +} diff --git a/shared-operations/src/index.ts b/shared-operations/src/index.ts index 100e590b6..6d55983a9 100644 --- a/shared-operations/src/index.ts +++ b/shared-operations/src/index.ts @@ -1,7 +1,7 @@ import { arrangePages, ArrangePagesParamsType } from './functions/arrangePages' import { extractPages, ExtractPagesParamsType } from "./functions/extractPages"; -import { impose, ImposeParamsType } from "./functions/impose"; +import { impose, ImposeParamConstraints, ImposeParamsType } from "./functions/impose"; import { mergePDFs, MergeParamsType } from './functions/mergePDFs'; import { removeBlankPages, RemoveBlankPagesParamsType } from "./functions/removeBlankPages"; import { removePages, RemovePagesParamsType } from "./functions/removePages"; @@ -11,17 +11,18 @@ import { scalePage, ScalePageParamsType } from './functions/scalePage'; import { splitPagesByPreset, SplitPageByPresetParamsType } from './functions/splitPagesByPreset'; import { splitPdfByIndex, SplitPdfByIndexParamsType } from './functions/splitPdfByIndex'; import { updateMetadata, UpdateMetadataParams } from "./functions/updateMetadata"; +import { FieldConstraint, RecordConstraint } from '@stirling-pdf/shared-operations/src/dynamic-ui/OperatorConstraints' import { PdfFile } from "./wrappers/PdfFile"; -import { Override } from '../declarations/TypeScriptUtils' +import { Override, ValuesType } from '../declarations/TypeScriptUtils' // Import injected libraries here! const toExport = { - arrangePages, - extractPages, - impose, - mergePDFs, + /*arrangePages, + extractPages,*/ + Impose: {exec: impose, spec: ImposeParamConstraints}, + /*mergePDFs, removeBlankPages, removePages, rotatePages, @@ -29,15 +30,25 @@ const toExport = { scalePage, splitPagesByPreset, splitPdfByIndex, - updateMetadata, + updateMetadata,*/ } export default toExport; -export type OperationsParametersBaseType = { - arrangePages: ArrangePagesParamsType - extractPages: ExtractPagesParamsType; - impose: ImposeParamsType; - mergePDFs: MergeParamsType; +type OperatorsBaseType = typeof toExport; +export type OperatorsType = Override Promise; + spec: RecordConstraint; + }; +}>; + +export type OperatorType = ValuesType; + +export type OperatorParametersType = { + /*arrangePages: ArrangePagesParamsType + extractPages: ExtractPagesParamsType;*/ + Impose: ImposeParamsType; + /*mergePDFs: MergeParamsType; removeBlankPages: RemoveBlankPagesParamsType; removePages: RemovePagesParamsType; rotatePages: RotateParamsType; @@ -45,16 +56,5 @@ export type OperationsParametersBaseType = { scalePage: ScalePageParamsType; splitPagesByPreset: SplitPageByPresetParamsType; splitPdfByIndex: SplitPdfByIndexParamsType; - updateMetadata: UpdateMetadataParams; + 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! -export type OperationsType = Override Promise; -}>; - -export type OperationsParametersType = Override; diff --git a/shared-operations/src/workflow/traverseOperations.ts b/shared-operations/src/workflow/traverseOperations.ts index 5e7f579a5..82b192c6f 100644 --- a/shared-operations/src/workflow/traverseOperations.ts +++ b/shared-operations/src/workflow/traverseOperations.ts @@ -1,9 +1,9 @@ import { organizeWaitOperations } from "./organizeWaitOperations"; import { Action, WaitAction } from "../../declarations/Action"; -import { OperationsType } from "../../src/index"; +import { OperatorsType } from "../../src/index"; import { PdfFile } from "../wrappers/PdfFile"; -export async function * traverseOperations(operations: Action[], input: PdfFile[] | PdfFile, Operations: OperationsType): AsyncGenerator { +export async function * traverseOperations(operations: Action[], input: PdfFile[] | PdfFile, Operators: OperatorsType): AsyncGenerator { const waitOperations = organizeWaitOperations(operations); let results: PdfFile[] = []; yield* nextOperation(operations, input); @@ -52,19 +52,19 @@ export async function * traverseOperations(operations: Action[], input: PdfFile[ yield* nextOperation(waitOperation.doneOperation.actions, waitOperation.input); } break; - case "extract": + /*case "extract": yield* nToN(input, action, async (input) => { const newPdf = await Operations.extractPages({file: input, pageIndexes: action.values["pageIndexes"]}); return newPdf; }); - break; + break;*/ case "impose": yield* nToN(input, action, async (input) => { - const newPdf = await Operations.impose({file: input, nup: action.values["nup"], format: action.values["format"]}); + const newPdf = await Operators.Impose.exec({file: input, nup: action.values["nup"], format: action.values["format"]}); return newPdf; }); break; - case "merge": + /*case "merge": yield* nToOne(input, action, async (inputs) => { const newPdf = await Operations.mergePDFs({files: inputs}); return newPdf; @@ -112,7 +112,7 @@ export async function * traverseOperations(operations: Action[], input: PdfFile[ const newPdf = await Operations.updateMetadata({file: input, ...action.values["metadata"]}); return newPdf; }); - break; + break;*/ default: throw new Error(`${action.type} not implemented yet.`); } diff --git a/shared-operations/src/wrappers/PdfFile.ts b/shared-operations/src/wrappers/PdfFile.ts index f8a2bd50e..ae31b342b 100644 --- a/shared-operations/src/wrappers/PdfFile.ts +++ b/shared-operations/src/wrappers/PdfFile.ts @@ -1,7 +1,6 @@ import * as PDFJS from 'pdfjs-dist'; import type { PDFDocumentProxy as PDFJSDocument } from 'pdfjs-dist/types/src/display/api'; import { PDFDocument as PDFLibDocument } from 'pdf-lib'; - import Joi from 'joi'; export enum RepresentationType { @@ -136,4 +135,4 @@ export const PdfFileSchema = Joi.any().custom((value) => { throw new Error('value is not a PdfFile'); } return value; -}, "PdfFile validation"); \ No newline at end of file +}, "PdfFile validation");