mirror of
https://github.com/alam00000/bentopdf.git
synced 2026-11-05 20:02:47 +01:00
feat(workflow): add altenate merge tool and translate workflow node and engine errors across locales
This commit is contained in:
@@ -7,6 +7,7 @@ import { applyColorAdjustments } from '../../utils/image-effects';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import type { AdjustColorsSettings } from '../../types/adjust-colors-type';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class AdjustColorsNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -89,7 +90,7 @@ export class AdjustColorsNode extends BaseWorkflowNode {
|
||||
renderCanvas.height = viewport.height;
|
||||
const renderCtx = renderCanvas.getContext('2d');
|
||||
if (!renderCtx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({
|
||||
canvasContext: renderCtx,
|
||||
viewport,
|
||||
@@ -109,7 +110,8 @@ export class AdjustColorsNode extends BaseWorkflowNode {
|
||||
outputCanvas.toBlob(resolve, 'image/png')
|
||||
);
|
||||
|
||||
if (!pngBlob) throw new Error(`Failed to render page ${i} to image`);
|
||||
if (!pngBlob)
|
||||
throw new Error(wfError('failedToRenderPageToImage', { page: i }));
|
||||
|
||||
const pngBytes = await pngBlob.arrayBuffer();
|
||||
const pngImage = await newPdfDoc.embedPng(pngBytes);
|
||||
@@ -127,7 +129,7 @@ export class AdjustColorsNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (newPdfDoc.getPageCount() === 0)
|
||||
throw new Error('No pages were processed');
|
||||
throw new Error(wfError('noPagesProcessed'));
|
||||
const pdfBytes = await newPdfDoc.save();
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { extractAllPdfs } from '../types';
|
||||
import { interleavePdfs } from '../../utils/alternate-merge.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class AlternateMergeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
readonly icon = 'ph-shuffle';
|
||||
readonly description = 'Interleave pages from multiple PDFs';
|
||||
|
||||
constructor() {
|
||||
super('Alternate Merge');
|
||||
this.addInput('pdf', new ClassicPreset.Input(pdfSocket, 'PDFs', true));
|
||||
this.addOutput(
|
||||
'pdf',
|
||||
new ClassicPreset.Output(pdfSocket, 'Interleaved PDF')
|
||||
);
|
||||
}
|
||||
|
||||
async data(
|
||||
inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
const allInputs = Object.values(inputs).flat();
|
||||
const allPdfs = extractAllPdfs(allInputs);
|
||||
if (allPdfs.length < 2) {
|
||||
throw new Error(wfError('alternateMergeNeedsTwo'));
|
||||
}
|
||||
|
||||
const filesToMerge = allPdfs.map((p) => ({
|
||||
name: p.filename,
|
||||
data: p.bytes.slice().buffer as ArrayBuffer,
|
||||
}));
|
||||
|
||||
const mergedBytes = await interleavePdfs(filesToMerge);
|
||||
const document = await loadPdfDocument(mergedBytes);
|
||||
|
||||
return {
|
||||
pdf: {
|
||||
type: 'pdf',
|
||||
document,
|
||||
bytes: mergedBytes,
|
||||
filename: 'alternated-mixed.pdf',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import type { NodeCategory, NodeMeta, SocketData } from '../types';
|
||||
export abstract class BaseWorkflowNode extends ClassicPreset.Node {
|
||||
abstract readonly category: NodeCategory;
|
||||
abstract readonly icon: string;
|
||||
abstract readonly description: string;
|
||||
abstract description: string;
|
||||
|
||||
width = 280;
|
||||
height = 140;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class CbzToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -47,8 +48,7 @@ export class CbzToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No comic archives uploaded in CBZ Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noCbzUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class CombineSinglePageNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -112,13 +113,14 @@ export class CombineSinglePageNode extends BaseWorkflowNode {
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const pngBlob = await new Promise<Blob | null>((resolve) =>
|
||||
canvas.toBlob(resolve, 'image/png')
|
||||
);
|
||||
if (!pngBlob) throw new Error(`Failed to render page ${i} to image`);
|
||||
if (!pngBlob)
|
||||
throw new Error(wfError('failedToRenderPageToImage', { page: i }));
|
||||
|
||||
const pngBytes = await pngBlob.arrayBuffer();
|
||||
const pngImage = await newDoc.embedPng(pngBytes);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class CropNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -59,9 +60,7 @@ export class CropNode extends BaseWorkflowNode {
|
||||
const cropWidth = width - left - right;
|
||||
const cropHeight = height - top - bottom;
|
||||
if (cropWidth <= 0 || cropHeight <= 0) {
|
||||
throw new Error(
|
||||
'Crop margins exceed page dimensions. Reduce crop values.'
|
||||
);
|
||||
throw new Error(wfError('cropMarginsExceed'));
|
||||
}
|
||||
page.setCropBox(left, bottom, cropWidth, cropHeight);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '../../logic/digital-sign-pdf.js';
|
||||
import type { CertificateData } from '@/types';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class DigitalSignNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -89,8 +90,7 @@ export class DigitalSignNode extends BaseWorkflowNode {
|
||||
inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
const pdfInputs = requirePdfInput(inputs, 'Digital Sign');
|
||||
if (!this.certData)
|
||||
throw new Error('No certificate loaded in Digital Sign node');
|
||||
if (!this.certData) throw new Error(wfError('digitalSignNoCertificate'));
|
||||
|
||||
const reasonCtrl = this.controls['reason'] as
|
||||
| ClassicPreset.InputControl<'text'>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class DownloadNode extends BaseWorkflowNode {
|
||||
readonly category = 'Output' as const;
|
||||
@@ -27,7 +28,7 @@ export class DownloadNode extends BaseWorkflowNode {
|
||||
const allInputs = Object.values(inputs).flat();
|
||||
const allPdfs = extractAllPdfs(allInputs);
|
||||
if (allPdfs.length === 0)
|
||||
throw new Error('No PDFs connected to Download node');
|
||||
throw new Error(wfError('noPdfsConnected', { node: 'Download' }));
|
||||
|
||||
const filenameControl = this.controls['filename'] as
|
||||
| ClassicPreset.InputControl<'text'>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class DownloadZipNode extends BaseWorkflowNode {
|
||||
readonly category = 'Output' as const;
|
||||
@@ -25,7 +26,7 @@ export class DownloadZipNode extends BaseWorkflowNode {
|
||||
const allInputs = Object.values(inputs).flat();
|
||||
const allPdfs = extractAllPdfs(allInputs);
|
||||
if (allPdfs.length === 0)
|
||||
throw new Error('No PDFs connected to Download ZIP node');
|
||||
throw new Error(wfError('noPdfsConnected', { node: 'Download ZIP' }));
|
||||
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { parseEmailFile, renderEmailToHtml } from '../../logic/email-to-pdf.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class EmailToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -60,8 +61,7 @@ export class EmailToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No email files uploaded in Email Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noEmailUploaded'));
|
||||
|
||||
const pageSizeCtrl = this.controls['pageSize'] as
|
||||
| ClassicPreset.InputControl<'text'>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { initializeQpdf } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class EncryptNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -39,8 +40,7 @@ export class EncryptNode extends BaseWorkflowNode {
|
||||
|
||||
const userPassword = getText('userPassword');
|
||||
const ownerPassword = getText('ownerPassword') || userPassword;
|
||||
if (!userPassword)
|
||||
throw new Error('User password is required for encryption');
|
||||
if (!userPassword) throw new Error(wfError('encryptUserPasswordRequired'));
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class EpubToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class EpubToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No EPUB files uploaded in EPUB Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noEpubUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class ExcelToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -52,8 +53,7 @@ export class ExcelToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No spreadsheets uploaded in Excel Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noExcelUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class ExtractImagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Output' as const;
|
||||
@@ -59,7 +60,7 @@ export class ExtractImagesNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (totalImages === 0) {
|
||||
throw new Error('No images found in any of the connected PDFs');
|
||||
throw new Error(wfError('extractImagesNoneFound'));
|
||||
}
|
||||
|
||||
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class Fb2ToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class Fb2ToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No FB2 files uploaded in FB2 Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noFb2Uploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requirePdfInput, processBatch } from '../types';
|
||||
import { applyGreyscale } from '../../utils/image-effects';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class GreyscaleNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -38,7 +39,7 @@ export class GreyscaleNode extends BaseWorkflowNode {
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
@@ -49,7 +50,8 @@ export class GreyscaleNode extends BaseWorkflowNode {
|
||||
canvas.toBlob(resolve, 'image/jpeg', 0.9)
|
||||
);
|
||||
|
||||
if (!jpegBlob) throw new Error(`Failed to render page ${i} to image`);
|
||||
if (!jpegBlob)
|
||||
throw new Error(wfError('failedToRenderPageToImage', { page: i }));
|
||||
const jpegBytes = await jpegBlob.arrayBuffer();
|
||||
const jpegImage = await newPdfDoc.embedJpg(jpegBytes);
|
||||
const newPage = newPdfDoc.addPage([viewport.width, viewport.height]);
|
||||
@@ -62,7 +64,7 @@ export class GreyscaleNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (newPdfDoc.getPageCount() === 0)
|
||||
throw new Error('No pages were processed');
|
||||
throw new Error(wfError('noPagesProcessed'));
|
||||
const pdfBytes = await newPdfDoc.save();
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class ImageInputNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -51,7 +52,7 @@ export class ImageInputNode extends BaseWorkflowNode {
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0) {
|
||||
throw new Error('No images uploaded in Image Input node');
|
||||
throw new Error(wfError('noImagesUploaded'));
|
||||
}
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requirePdfInput, processBatch } from '../types';
|
||||
import { applyInvertColors } from '../../utils/image-effects';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class InvertColorsNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -38,7 +39,7 @@ export class InvertColorsNode extends BaseWorkflowNode {
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
@@ -48,14 +49,14 @@ export class InvertColorsNode extends BaseWorkflowNode {
|
||||
const pngBytes = await new Promise<Uint8Array>((resolve, reject) =>
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error(`Failed to render page ${i}`));
|
||||
reject(new Error(wfError('failedToRenderPage', { page: i })));
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () =>
|
||||
resolve(new Uint8Array(reader.result as ArrayBuffer));
|
||||
reader.onerror = () =>
|
||||
reject(new Error('Failed to read image data'));
|
||||
reject(new Error(wfError('invertColorsFailedReadImage')));
|
||||
reader.readAsArrayBuffer(blob);
|
||||
}, 'image/png')
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class JsonToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class JsonToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No JSON files uploaded in JSON Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noJsonUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
|
||||
|
||||
@@ -50,8 +51,7 @@ export class MarkdownToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No Markdown files uploaded in Markdown Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noMarkdownUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { extractAllPdfs } from '../types';
|
||||
import { mergePdfs } from '../../utils/pdf-operations';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class MergeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -23,7 +24,7 @@ export class MergeNode extends BaseWorkflowNode {
|
||||
const allInputs = Object.values(inputs).flat();
|
||||
const allPdfs = extractAllPdfs(allInputs);
|
||||
if (allPdfs.length === 0)
|
||||
throw new Error('No PDFs connected to Merge node');
|
||||
throw new Error(wfError('noPdfsConnected', { node: 'Merge' }));
|
||||
|
||||
const mergedBytes = await mergePdfs(allPdfs.map((p) => p.bytes));
|
||||
const mergedDoc = await loadPdfDocument(mergedBytes);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class MobiToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class MobiToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No MOBI files uploaded in MOBI Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noMobiUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class OdgToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class OdgToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No ODG files uploaded in ODG Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noOdgUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, extractSinglePdf } from '../types';
|
||||
import { initializeQpdf } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class OverlayNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -28,7 +29,7 @@ export class OverlayNode extends BaseWorkflowNode {
|
||||
const baseInputs = requirePdfInput(inputs, 'Overlay');
|
||||
const overlayInputs = inputs['overlay'];
|
||||
if (!overlayInputs || overlayInputs.length === 0) {
|
||||
throw new Error('Overlay node requires an overlay PDF input.');
|
||||
throw new Error(wfError('overlayRequiresPdf'));
|
||||
}
|
||||
|
||||
const basePdf = extractSinglePdf(baseInputs[0]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class PagesToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class PagesToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No Pages files uploaded in Pages Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noPagesUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { readFileAsArrayBuffer } from '../../utils/helpers.js';
|
||||
import { decryptPdfBytes } from '../../utils/pdf-decrypt.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class EncryptedPDFError extends Error {
|
||||
constructor(public readonly filename: string) {
|
||||
@@ -40,9 +41,7 @@ export class PDFInputNode extends BaseWorkflowNode {
|
||||
try {
|
||||
await loadPdfDocument(bytes);
|
||||
} catch {
|
||||
throw new Error(
|
||||
`Failed to load "${file.name}" - file may be corrupted`
|
||||
);
|
||||
throw new Error(wfError('failedToLoadFile', { file: file.name }));
|
||||
}
|
||||
throw new EncryptedPDFError(file.name);
|
||||
}
|
||||
@@ -107,7 +106,7 @@ export class PDFInputNode extends BaseWorkflowNode {
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0) {
|
||||
throw new Error('No PDF files uploaded in PDF Input node');
|
||||
throw new Error(wfError('noPdfFilesUploaded'));
|
||||
}
|
||||
|
||||
if (this.files.length === 1) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
function tableToCsv(rows: (string | null)[][]): string {
|
||||
return rows
|
||||
@@ -63,7 +64,7 @@ export class PdfToCsvNode extends BaseWorkflowNode {
|
||||
if (allPdfs.length === 1) {
|
||||
const allRows = await this.extractTables(allPdfs[0].bytes);
|
||||
if (allRows.length === 0) {
|
||||
throw new Error('No tables found in PDF');
|
||||
throw new Error(wfError('pdfToCsvNoTables'));
|
||||
}
|
||||
const csv = tableToCsv(allRows);
|
||||
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class PdfToXlsxNode extends BaseWorkflowNode {
|
||||
readonly category = 'Output' as const;
|
||||
@@ -45,7 +46,7 @@ export class PdfToXlsxNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (allTables.length === 0) {
|
||||
throw new Error(`No tables found in ${filename}`);
|
||||
throw new Error(wfError('pdfToXlsxNoTables', { file: filename }));
|
||||
}
|
||||
|
||||
const XLSX = await import('xlsx');
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class PosterizeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -57,7 +58,7 @@ export class PosterizeNode extends BaseWorkflowNode {
|
||||
canvas.height = viewport.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({ canvasContext: ctx, viewport, canvas }).promise;
|
||||
|
||||
const tileW = viewport.width / cols;
|
||||
@@ -70,7 +71,7 @@ export class PosterizeNode extends BaseWorkflowNode {
|
||||
tileCanvas.height = tileH;
|
||||
const tileCtx = tileCanvas.getContext('2d');
|
||||
if (!tileCtx)
|
||||
throw new Error('Failed to get tile canvas context');
|
||||
throw new Error(wfError('posterizeFailedTileCanvas'));
|
||||
tileCtx.drawImage(
|
||||
canvas,
|
||||
c * tileW,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class PowerPointToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -52,7 +53,7 @@ export class PowerPointToPdfNode extends BaseWorkflowNode {
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No presentations uploaded in PowerPoint Input node');
|
||||
throw new Error(wfError('noPowerPointUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class PubToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class PubToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No PUB files uploaded in PUB Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noPubUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requirePdfInput, processBatch } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class RedactNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -70,7 +71,7 @@ export class RedactNode extends BaseWorkflowNode {
|
||||
const fill = hexToRgb(this.getText('fillColor', '#000000'));
|
||||
|
||||
if (mode === 'text' && !searchText) {
|
||||
throw new Error('Redact: No text specified to redact');
|
||||
throw new Error(wfError('redactNoText'));
|
||||
}
|
||||
|
||||
const areaRect = {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { BaseWorkflowNode } from './base-node';
|
||||
import type { NodeCategory } from '../types';
|
||||
import { translateNodeLabel, translateNodeDescription } from '../i18n';
|
||||
import { PDFInputNode } from './pdf-input-node';
|
||||
import { ImageInputNode } from './image-input-node';
|
||||
import { DownloadNode } from './download-node';
|
||||
import { PdfToImagesNode } from './pdf-to-images-node';
|
||||
import { MergeNode } from './merge-node';
|
||||
import { AlternateMergeNode } from './alternate-merge-node';
|
||||
import { SplitNode } from './split-node';
|
||||
import { ExtractPagesNode } from './extract-pages-node';
|
||||
import { RotateNode } from './rotate-node';
|
||||
@@ -268,6 +270,15 @@ export const nodeRegistry: Record<string, NodeRegistryEntry> = {
|
||||
factory: () => new MergeNode(),
|
||||
toolPageId: 'merge-pdf',
|
||||
},
|
||||
AlternateMergeNode: {
|
||||
label: 'Alternate Merge',
|
||||
category: 'Organize & Manage',
|
||||
icon: 'ph-shuffle',
|
||||
description:
|
||||
'Interleave pages from multiple PDFs (e.g. odd + reversed even from a single-sided scan)',
|
||||
factory: () => new AlternateMergeNode(),
|
||||
toolPageId: 'alternate-merge',
|
||||
},
|
||||
SplitNode: {
|
||||
label: 'Split PDF',
|
||||
category: 'Organize & Manage',
|
||||
@@ -691,6 +702,8 @@ export function createNodeByType(type: string): BaseWorkflowNode | null {
|
||||
if (!entry) return null;
|
||||
const node = entry.factory();
|
||||
node.nodeType = type;
|
||||
node.label = translateNodeLabel(type, entry);
|
||||
node.description = translateNodeDescription(type, entry);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class RemoveBlankPagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -76,7 +77,7 @@ export class RemoveBlankPagesNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (nonBlankIndices.length === 0) {
|
||||
throw new Error('All pages are blank');
|
||||
throw new Error(wfError('removeBlankPagesAllBlank'));
|
||||
}
|
||||
|
||||
const newDoc = await PDFDocument.create();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { applyScannerEffect } from '../../utils/image-effects';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import type { ScanSettings } from '../../types/scanner-effect-type';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class ScannerEffectNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -107,7 +108,7 @@ export class ScannerEffectNode extends BaseWorkflowNode {
|
||||
renderCanvas.height = viewport.height;
|
||||
const renderCtx = renderCanvas.getContext('2d');
|
||||
if (!renderCtx)
|
||||
throw new Error(`Failed to get canvas context for page ${i}`);
|
||||
throw new Error(wfError('failedToGetCanvasContext', { page: i }));
|
||||
await page.render({
|
||||
canvasContext: renderCtx,
|
||||
viewport,
|
||||
@@ -133,7 +134,8 @@ export class ScannerEffectNode extends BaseWorkflowNode {
|
||||
outputCanvas.toBlob(resolve, 'image/jpeg', 0.85)
|
||||
);
|
||||
|
||||
if (!jpegBlob) throw new Error(`Failed to render page ${i} to image`);
|
||||
if (!jpegBlob)
|
||||
throw new Error(wfError('failedToRenderPageToImage', { page: i }));
|
||||
|
||||
const jpegBytes = await jpegBlob.arrayBuffer();
|
||||
const jpegImage = await newPdfDoc.embedJpg(jpegBytes);
|
||||
@@ -150,7 +152,7 @@ export class ScannerEffectNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
if (newPdfDoc.getPageCount() === 0)
|
||||
throw new Error('No pages were processed');
|
||||
throw new Error(wfError('noPagesProcessed'));
|
||||
const pdfBytes = await newPdfDoc.save();
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class SvgToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -56,7 +57,7 @@ export class SvgToPdfNode extends BaseWorkflowNode {
|
||||
URL.revokeObjectURL(url);
|
||||
canvas.toBlob(async (blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error('Failed to convert SVG'));
|
||||
reject(new Error(wfError('svgFailedToConvert')));
|
||||
return;
|
||||
}
|
||||
resolve(new Uint8Array(await blob.arrayBuffer()));
|
||||
@@ -64,7 +65,7 @@ export class SvgToPdfNode extends BaseWorkflowNode {
|
||||
};
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
reject(new Error('Failed to load SVG'));
|
||||
reject(new Error(wfError('svgFailedToLoad')));
|
||||
};
|
||||
img.src = url;
|
||||
});
|
||||
@@ -73,8 +74,7 @@ export class SvgToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No SVG files uploaded in SVG Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noSvgUploaded'));
|
||||
|
||||
const doc = await PDFDocument.create();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { WasmProvider } from '../../utils/wasm-provider.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class TableOfContentsNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -57,10 +58,7 @@ export class TableOfContentsNode extends BaseWorkflowNode {
|
||||
const addBookmark = (addBookmarkCtrl?.value ?? 'true') === 'true';
|
||||
|
||||
const cpdfUrl = WasmProvider.getUrl('cpdf');
|
||||
if (!cpdfUrl)
|
||||
throw new Error(
|
||||
'CoherentPDF is not configured. Please configure it in Advanced Settings.'
|
||||
);
|
||||
if (!cpdfUrl) throw new Error(wfError('cpdfNotConfigured'));
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
@@ -84,7 +82,7 @@ export class TableOfContentsNode extends BaseWorkflowNode {
|
||||
|
||||
worker.onerror = (err) => {
|
||||
worker.terminate();
|
||||
reject(new Error('Worker error: ' + err.message));
|
||||
reject(new Error(wfError('workerError', { message: err.message })));
|
||||
};
|
||||
|
||||
const arrayBuffer = input.bytes.buffer.slice(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class TextToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -58,8 +59,7 @@ export class TextToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No text files uploaded in Text Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noTextUploaded'));
|
||||
|
||||
const fontSizeCtrl = this.controls['fontSize'] as
|
||||
| ClassicPreset.InputControl<'number'>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { wfError } from '../errors';
|
||||
import { TIMESTAMP_TSA_PRESETS } from '../../config/timestamp-tsa.js';
|
||||
import { timestampPdf } from '../../logic/digital-sign-pdf.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
@@ -47,8 +48,9 @@ export class TimestampNode extends BaseWorkflowNode {
|
||||
try {
|
||||
bytes = await timestampPdf(input.bytes, tsaUrl);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`Failed to timestamp using TSA ${tsaUrl}: ${err instanceof Error ? err.message : err}`,
|
||||
wfError('timestampFailed', { tsa: tsaUrl, message }),
|
||||
{ cause: err }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class VsdToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -47,8 +48,7 @@ export class VsdToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No Visio files uploaded in VSD Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noVsdUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -8,6 +8,7 @@ import { PDFDocument } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class WatermarkNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -142,7 +143,9 @@ export class WatermarkNode extends BaseWorkflowNode {
|
||||
(blob) =>
|
||||
blob
|
||||
? blob.arrayBuffer().then(resolve)
|
||||
: reject(new Error(`Failed to rasterize page ${i}`)),
|
||||
: reject(
|
||||
new Error(wfError('failedToRenderPage', { page: i }))
|
||||
),
|
||||
'image/jpeg',
|
||||
0.92
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class WordToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -52,8 +53,7 @@ export class WordToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No documents uploaded in Word Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noWordDocsUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class WpdToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class WpdToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No WPD files uploaded in WPD Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noWpdUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class WpsToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class WpsToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No WPS files uploaded in WPS Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noWpsUploaded'));
|
||||
|
||||
const converter = getLibreOfficeConverter();
|
||||
await converter.initialize();
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class XmlToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -46,8 +47,7 @@ export class XmlToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No XML files uploaded in XML Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noXmlUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
import { wfError } from '../errors';
|
||||
|
||||
export class XpsToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -47,8 +48,7 @@ export class XpsToPdfNode extends BaseWorkflowNode {
|
||||
async data(
|
||||
_inputs: Record<string, SocketData[]>
|
||||
): Promise<Record<string, SocketData>> {
|
||||
if (this.files.length === 0)
|
||||
throw new Error('No XPS files uploaded in XPS Input node');
|
||||
if (this.files.length === 0) throw new Error(wfError('noXpsUploaded'));
|
||||
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const results: PDFData[] = [];
|
||||
|
||||
Reference in New Issue
Block a user