created OperatorConstraints and impelemented it in impose

This commit is contained in:
Saud Fatayerji 2023-11-19 17:22:17 +03:00
parent a7545b017f
commit 7df48d43d6
7 changed files with 142 additions and 56 deletions

View File

@ -1,8 +1,9 @@
import Operations from '../../utils/pdf-operations'; import Operations from '../../utils/pdf-operations';
import { respondWithPdfFile, respondWithPdfFiles, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils'; import { respondWithPdfFile, respondWithPdfFiles, response_mustHaveExactlyOneFile } from '../../utils/endpoint-utils';
import { PdfFile, PdfFileSchema } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile' import { PdfFile, /*PdfFileSchema*/ } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile'
import { ScalePageSchema } from '@stirling-pdf/shared-operations/src/functions/scalePage' //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'; import express, { Request, Response, RequestHandler } from 'express';
const router = express.Router(); const router = express.Router();
@ -13,8 +14,7 @@ import Joi from 'joi';
function registerEndpoint(endpoint: string, function registerEndpoint(endpoint: string,
nameToAppend: string, nameToAppend: string,
fileHandler: RequestHandler, fileHandler: RequestHandler,
operationFunction: (params: any) => Promise<PdfFile|PdfFile[]>, operator: OperatorType
joiSchema: Joi.ObjectSchema<any>
): void { ): void {
router.post(endpoint, fileHandler, async function(req: Request, res: Response) { router.post(endpoint, fileHandler, async function(req: Request, res: Response) {
const body = req.body; const body = req.body;
@ -31,13 +31,13 @@ function registerEndpoint(endpoint: string,
} }
console.log(req.body) console.log(req.body)
const { error, value } = joiSchema.validate(req.body); const { error, value } = operator.spec.toJoiSchema().validate(req.body);
if (error) { if (error) {
res.status(400).send(error.details); res.status(400).send(error.details);
return; return;
} }
const processed = await operationFunction(value) const processed = await operator.exec(value)
if (body.files && Array.isArray(processed)) { // MIMO if (body.files && Array.isArray(processed)) { // MIMO
respondWithPdfFiles(res, processed, nameToAppend); respondWithPdfFiles(res, processed, nameToAppend);
@ -55,7 +55,7 @@ function registerEndpoint(endpoint: string,
///////////////////// /////////////////////
// Page Operations // // 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(), files: Joi.array().items(PdfFileSchema).required(),
}).required()); }).required());
@ -78,13 +78,9 @@ registerEndpoint("/remove-pages", "", upload.single("file"), Operations.removePa
file: PdfFileSchema.required(), file: PdfFileSchema.required(),
pageSelector: Joi.string().required(), pageSelector: Joi.string().required(),
}).required()); }).required());
*/
registerEndpoint("/impose", "", upload.single("file"), Operations.impose, Joi.object({ registerEndpoint("/impose", "", upload.single("file"), Operations.Impose);
file: PdfFileSchema.required(), /*
nup: Joi.number().valid(2, 3, 4, 8, 9, 12, 16).required(),
format: Joi.string().required(),
}).required());
registerEndpoint("/scale-pages", "", upload.single("file"), Operations.scalePage, ScalePageSchema.required()); registerEndpoint("/scale-pages", "", upload.single("file"), Operations.scalePage, ScalePageSchema.required());
//Auto Split Pages //Auto Split Pages
@ -159,5 +155,5 @@ registerEndpoint("/update-metadata", "", upload.single("file"), Operations.updat
//Auto Rename //Auto Rename
//Get info //Get info
//Show JS //Show JS
*/
export default router; export default router;

View File

@ -1,17 +1,16 @@
import SharedOperations, { OperationsType } from "@stirling-pdf/shared-operations/src" import SharedOperations, { OperatorsType, OperatorParametersType } 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 { PdfFile } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile"
// Import injected libraries here! // Import injected libraries here!
import * as pdfcpuWrapper from "@stirling-pdf/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper-node.js"; import * as pdfcpuWrapper from "@stirling-pdf/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper-node.js";
async function impose(params: ImposeParamsType): Promise<PdfFile> { async function impose(params: OperatorParametersType["Impose"]): Promise<PdfFile> {
return SharedOperations.impose(params, pdfcpuWrapper); return SharedOperations.Impose.exec(params, pdfcpuWrapper);
} }
const toExport: OperationsType = { const toExport: OperatorsType = {
...SharedOperations, ...SharedOperations,
impose, Impose: {exec: impose, spec: SharedOperations.Impose.spec},
} }
export default toExport; export default toExport;

View File

@ -0,0 +1,85 @@
import Joi from 'joi';
import { PdfFileSchema } from '../wrappers/PdfFile';
export class RecordConstraint {
record: Record<string, FieldConstraint>;
constructor(record: Record<string, FieldConstraint>) {
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;
}
}

View File

@ -1,13 +1,20 @@
import { PdfFile, RepresentationType } from "../wrappers/PdfFile"; import { PdfFile, RepresentationType } from "../wrappers/PdfFile";
import { FieldConstraint, RecordConstraint } from '../dynamic-ui/OperatorConstraints'
export type ImposeParamsType = { export type ImposeParamsType = {
file: PdfFile; file: PdfFile;
/** Accepted values are 2, 3, 4, 8, 9, 12, 16 - see: {@link https://pdfcpu.io/generate/nup.html#n-up-value} */ /** 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} */ /** A0-A10, other formats available - see: {@link https://pdfcpu.io/paper.html} */
format: string; 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} */ /** 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<PdfFile> { export async function impose(params: ImposeParamsType, pdfcpuWrapper: any): Promise<PdfFile> {
// https://pdfcpu.io/generate/nup.html // https://pdfcpu.io/generate/nup.html
@ -34,4 +41,4 @@ export async function impose(params: ImposeParamsType, pdfcpuWrapper: any): Prom
console.log("ImposeResult: ", result); console.log("ImposeResult: ", result);
return result; return result;
} }

View File

@ -1,7 +1,7 @@
import { arrangePages, ArrangePagesParamsType } from './functions/arrangePages' import { arrangePages, ArrangePagesParamsType } from './functions/arrangePages'
import { extractPages, ExtractPagesParamsType } from "./functions/extractPages"; 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 { mergePDFs, MergeParamsType } from './functions/mergePDFs';
import { removeBlankPages, RemoveBlankPagesParamsType } from "./functions/removeBlankPages"; import { removeBlankPages, RemoveBlankPagesParamsType } from "./functions/removeBlankPages";
import { removePages, RemovePagesParamsType } from "./functions/removePages"; import { removePages, RemovePagesParamsType } from "./functions/removePages";
@ -11,17 +11,18 @@ import { scalePage, ScalePageParamsType } from './functions/scalePage';
import { splitPagesByPreset, SplitPageByPresetParamsType } from './functions/splitPagesByPreset'; import { splitPagesByPreset, SplitPageByPresetParamsType } from './functions/splitPagesByPreset';
import { splitPdfByIndex, SplitPdfByIndexParamsType } from './functions/splitPdfByIndex'; import { splitPdfByIndex, SplitPdfByIndexParamsType } from './functions/splitPdfByIndex';
import { updateMetadata, UpdateMetadataParams } from "./functions/updateMetadata"; import { updateMetadata, UpdateMetadataParams } from "./functions/updateMetadata";
import { FieldConstraint, RecordConstraint } from '@stirling-pdf/shared-operations/src/dynamic-ui/OperatorConstraints'
import { PdfFile } from "./wrappers/PdfFile"; import { PdfFile } from "./wrappers/PdfFile";
import { Override } from '../declarations/TypeScriptUtils' import { Override, ValuesType } from '../declarations/TypeScriptUtils'
// Import injected libraries here! // Import injected libraries here!
const toExport = { const toExport = {
arrangePages, /*arrangePages,
extractPages, extractPages,*/
impose, Impose: {exec: impose, spec: ImposeParamConstraints},
mergePDFs, /*mergePDFs,
removeBlankPages, removeBlankPages,
removePages, removePages,
rotatePages, rotatePages,
@ -29,15 +30,25 @@ const toExport = {
scalePage, scalePage,
splitPagesByPreset, splitPagesByPreset,
splitPdfByIndex, splitPdfByIndex,
updateMetadata, updateMetadata,*/
} }
export default toExport; export default toExport;
export type OperationsParametersBaseType = { type OperatorsBaseType = typeof toExport;
arrangePages: ArrangePagesParamsType export type OperatorsType = Override<OperatorsBaseType, {
extractPages: ExtractPagesParamsType; Impose: {
impose: ImposeParamsType; exec: (params: ImposeParamsType) => Promise<PdfFile>;
mergePDFs: MergeParamsType; spec: RecordConstraint;
};
}>;
export type OperatorType = ValuesType<OperatorsType>;
export type OperatorParametersType = {
/*arrangePages: ArrangePagesParamsType
extractPages: ExtractPagesParamsType;*/
Impose: ImposeParamsType;
/*mergePDFs: MergeParamsType;
removeBlankPages: RemoveBlankPagesParamsType; removeBlankPages: RemoveBlankPagesParamsType;
removePages: RemovePagesParamsType; removePages: RemovePagesParamsType;
rotatePages: RotateParamsType; rotatePages: RotateParamsType;
@ -45,16 +56,5 @@ export type OperationsParametersBaseType = {
scalePage: ScalePageParamsType; scalePage: ScalePageParamsType;
splitPagesByPreset: SplitPageByPresetParamsType; splitPagesByPreset: SplitPageByPresetParamsType;
splitPdfByIndex: SplitPdfByIndexParamsType; 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<OperationsBaseType, {
impose: (params: ImposeParamsType) => Promise<PdfFile>;
}>;
export type OperationsParametersType = Override<OperationsParametersBaseType, {
impose: ImposeParamsType;
}>;

View File

@ -1,9 +1,9 @@
import { organizeWaitOperations } from "./organizeWaitOperations"; import { organizeWaitOperations } from "./organizeWaitOperations";
import { Action, WaitAction } from "../../declarations/Action"; import { Action, WaitAction } from "../../declarations/Action";
import { OperationsType } from "../../src/index"; import { OperatorsType } from "../../src/index";
import { PdfFile } from "../wrappers/PdfFile"; import { PdfFile } from "../wrappers/PdfFile";
export async function * traverseOperations(operations: Action[], input: PdfFile[] | PdfFile, Operations: OperationsType): AsyncGenerator<string, PdfFile[], void> { export async function * traverseOperations(operations: Action[], input: PdfFile[] | PdfFile, Operators: OperatorsType): AsyncGenerator<string, PdfFile[], void> {
const waitOperations = organizeWaitOperations(operations); const waitOperations = organizeWaitOperations(operations);
let results: PdfFile[] = []; let results: PdfFile[] = [];
yield* nextOperation(operations, input); yield* nextOperation(operations, input);
@ -52,19 +52,19 @@ export async function * traverseOperations(operations: Action[], input: PdfFile[
yield* nextOperation(waitOperation.doneOperation.actions, waitOperation.input); yield* nextOperation(waitOperation.doneOperation.actions, waitOperation.input);
} }
break; break;
case "extract": /*case "extract":
yield* nToN(input, action, async (input) => { yield* nToN(input, action, async (input) => {
const newPdf = await Operations.extractPages({file: input, pageIndexes: action.values["pageIndexes"]}); const newPdf = await Operations.extractPages({file: input, pageIndexes: action.values["pageIndexes"]});
return newPdf; return newPdf;
}); });
break; break;*/
case "impose": case "impose":
yield* nToN(input, action, async (input) => { 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; return newPdf;
}); });
break; break;
case "merge": /*case "merge":
yield* nToOne(input, action, async (inputs) => { yield* nToOne(input, action, async (inputs) => {
const newPdf = await Operations.mergePDFs({files: inputs}); const newPdf = await Operations.mergePDFs({files: inputs});
return newPdf; return newPdf;
@ -112,7 +112,7 @@ export async function * traverseOperations(operations: Action[], input: PdfFile[
const newPdf = await Operations.updateMetadata({file: input, ...action.values["metadata"]}); const newPdf = await Operations.updateMetadata({file: input, ...action.values["metadata"]});
return newPdf; return newPdf;
}); });
break; break;*/
default: default:
throw new Error(`${action.type} not implemented yet.`); throw new Error(`${action.type} not implemented yet.`);
} }

View File

@ -1,7 +1,6 @@
import * as PDFJS from 'pdfjs-dist'; import * as PDFJS from 'pdfjs-dist';
import type { PDFDocumentProxy as PDFJSDocument } from 'pdfjs-dist/types/src/display/api'; import type { PDFDocumentProxy as PDFJSDocument } from 'pdfjs-dist/types/src/display/api';
import { PDFDocument as PDFLibDocument } from 'pdf-lib'; import { PDFDocument as PDFLibDocument } from 'pdf-lib';
import Joi from 'joi'; import Joi from 'joi';
export enum RepresentationType { export enum RepresentationType {
@ -136,4 +135,4 @@ export const PdfFileSchema = Joi.any().custom((value) => {
throw new Error('value is not a PdfFile'); throw new Error('value is not a PdfFile');
} }
return value; return value;
}, "PdfFile validation"); }, "PdfFile validation");