- Refactor PDF loading across workflow nodes to use loadPdfDocument utility
- Replaced direct calls to PDFDocument.load with loadPdfDocument in multiple nodes to standardize PDF loading process.
This commit is contained in:
@@ -653,7 +653,9 @@ export async function extractPageModel(
|
||||
fontNameMap.set(internalName, fontObj.name);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn(`Failed to resolve font name for "${internalName}"`, e);
|
||||
}
|
||||
}
|
||||
|
||||
const rawItems = sortCompareTextItems(
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { downloadFile } from '../../utils/helpers.ts';
|
||||
import { computeComparisonForPair } from '../../logic/compare-render.ts';
|
||||
import { LRUCache } from '../lru-cache.ts';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
const HIGHLIGHT_COLORS: Record<
|
||||
string,
|
||||
@@ -112,8 +113,8 @@ export async function exportComparePdf(
|
||||
]);
|
||||
|
||||
const [libDoc1, libDoc2] = await Promise.all([
|
||||
bytes1 ? PDFDocument.load(bytes1, { ignoreEncryption: true }) : null,
|
||||
bytes2 ? PDFDocument.load(bytes2, { ignoreEncryption: true }) : null,
|
||||
bytes1 ? loadPdfDocument(bytes1, { ignoreEncryption: true }) : null,
|
||||
bytes2 ? loadPdfDocument(bytes2, { ignoreEncryption: true }) : null,
|
||||
]);
|
||||
|
||||
const includeChange = options?.includeChange ?? (() => true);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
import { setupCanvasEditor } from '../canvasEditor.js';
|
||||
import { toolLogic } from '../logic/index.js';
|
||||
import { renderDuplicateOrganizeThumbnails } from '../logic/duplicate-organize.js';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import Sortable from 'sortablejs';
|
||||
import { makeUniqueFileKey } from '../utils/deduplicate-filename.js';
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
singlePdfLoadTools,
|
||||
} from '../config/pdf-tools.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -71,9 +71,7 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
|
||||
}
|
||||
|
||||
const pdfBytes = await readFileAsArrayBuffer(file);
|
||||
state.pdfDoc = await PDFLibDocument.load(pdfBytes as ArrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
state.pdfDoc = await loadPdfDocument(pdfBytes as ArrayBuffer);
|
||||
hideLoader();
|
||||
|
||||
if (
|
||||
@@ -88,7 +86,7 @@ async function handleSinglePdfUpload(toolId: string, file: File) {
|
||||
return;
|
||||
}
|
||||
const decryptedBytes = await readFileAsArrayBuffer(decryptedFile);
|
||||
state.pdfDoc = await PDFLibDocument.load(decryptedBytes as ArrayBuffer);
|
||||
state.pdfDoc = await loadPdfDocument(decryptedBytes as ArrayBuffer);
|
||||
state.files = [decryptedFile];
|
||||
}
|
||||
|
||||
@@ -603,9 +601,7 @@ async function handleMultiFileUpload(toolId: string) {
|
||||
const pdfFilesLoaded = await Promise.all(
|
||||
pdfFilesUnloaded.map(async (file) => {
|
||||
const pdfBytes = await readFileAsArrayBuffer(file);
|
||||
const pdfDoc = await PDFLibDocument.load(pdfBytes as ArrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes as ArrayBuffer);
|
||||
|
||||
return {
|
||||
file,
|
||||
|
||||
@@ -2,13 +2,13 @@ import { AddAttachmentState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { isCpdfAvailable } from '../utils/cpdf-helper.js';
|
||||
import {
|
||||
showWasmRequiredDialog,
|
||||
WasmProvider,
|
||||
} from '../utils/wasm-provider.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const worker = new Worker(
|
||||
import.meta.env.BASE_URL + 'workers/add-attachments.worker.js'
|
||||
@@ -139,9 +139,7 @@ async function updateUI() {
|
||||
pageState.file = result.file;
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
|
||||
const pageCount = pageState.pdfDoc.getPageCount();
|
||||
metaSpan.textContent = `${formatBytes(pageState.file.size)} • ${pageCount} pages`;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { AddBlankPageState } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: AddBlankPageState = {
|
||||
file: null,
|
||||
@@ -84,8 +85,7 @@ async function updateUI() {
|
||||
}
|
||||
showLoader('Loading PDF...');
|
||||
pageState.file = result.file;
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
result.pdf.destroy();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import type {
|
||||
AddPageLabelsState,
|
||||
LabelRule,
|
||||
@@ -19,6 +18,7 @@ import {
|
||||
normalizePageLabelStartValue,
|
||||
resolvePageLabelStyle,
|
||||
} from '../utils/page-labels.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
type AddPageLabelsCpdf = {
|
||||
setSlow?: () => void;
|
||||
@@ -174,8 +174,7 @@ async function handleFiles(files: FileList) {
|
||||
showLoader(translate('tools:addPageLabels.loadingPdf', 'Loading PDF...'));
|
||||
try {
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const pdfDoc = await PDFDocument.load(arrayBuffer as ArrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer as ArrayBuffer, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -175,7 +175,9 @@ async function loadPdfInViewer(file: File) {
|
||||
enablePermissions: false,
|
||||
};
|
||||
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('Failed to update pdfjs.preferences in localStorage', e);
|
||||
}
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.className = 'w-full h-full border-0';
|
||||
@@ -221,7 +223,12 @@ function setupAnnotationViewer(iframe: HTMLIFrameElement) {
|
||||
'editorStampButton'
|
||||
) as HTMLButtonElement | null;
|
||||
stampBtn?.click();
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'Failed to auto-click stamp button in annotation editor',
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { AddWatermarkState, PageWatermarkConfig } from '@/types';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -121,7 +122,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
const pdfBytes = new Uint8Array(result.bytes);
|
||||
pageState.pdfDoc = await PDFLibDocument.load(pdfBytes);
|
||||
pageState.pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
pageState.file = result.file;
|
||||
pageState.pdfBytes = pdfBytes;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { downloadFile, hexToRgb, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb } from 'pdf-lib';
|
||||
import { BackgroundColorState } from '@/types';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: BackgroundColorState = { file: null, pdfDoc: null };
|
||||
|
||||
@@ -63,9 +64,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
updateFileDisplay();
|
||||
document.getElementById('options-panel')?.classList.remove('hidden');
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { showAlert, showLoader, hideLoader } from '../ui.js';
|
||||
import { downloadFile, hexToRgb, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { StandardFonts, rgb } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import JSZip from 'jszip';
|
||||
import Sortable from 'sortablejs';
|
||||
import { FileEntry, Position, StylePreset } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const FONT_MAP: Record<string, keyof typeof StandardFonts> = {
|
||||
Helvetica: 'Helvetica',
|
||||
@@ -184,9 +185,7 @@ async function handleFiles(fileList: FileList) {
|
||||
if (!result) continue;
|
||||
showLoader('Loading PDFs...');
|
||||
result.pdf.destroy();
|
||||
const pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
const pdfDoc = await loadPdfDocument(result.bytes);
|
||||
files.push({ file: result.file, pageCount: pdfDoc.getPageCount() });
|
||||
}
|
||||
|
||||
@@ -475,7 +474,7 @@ async function applyBatesNumbers() {
|
||||
|
||||
for (const entry of files) {
|
||||
const arrayBuffer = await entry.file.arrayBuffer();
|
||||
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer);
|
||||
const font = await pdfDoc.embedFont(StandardFonts[fontName]);
|
||||
const pages = pdfDoc.getPages();
|
||||
const fileName = entry.file.name.replace(/\.pdf$/i, '');
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
hexToRgb,
|
||||
} from '../utils/helpers.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
import {
|
||||
BookmarkNode,
|
||||
BookmarkTree,
|
||||
@@ -1240,7 +1241,7 @@ async function loadPDF(e?: Event): Promise<void> {
|
||||
selectedBookmarks.clear();
|
||||
collapsedNodes.clear();
|
||||
|
||||
pdfLibDoc = await PDFDocument.load(result.bytes, { ignoreEncryption: true });
|
||||
pdfLibDoc = await loadPdfDocument(result.bytes, { ignoreEncryption: true });
|
||||
pdfJsDoc = result.pdf;
|
||||
|
||||
if (gotoPageInput) gotoPageInput.max = String(pdfJsDoc.numPages);
|
||||
|
||||
@@ -1,283 +1,330 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import {
|
||||
downloadFile,
|
||||
formatBytes,
|
||||
initializeQpdf,
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { ChangePermissionsState } from '@/types';
|
||||
|
||||
const pageState: ChangePermissionsState = {
|
||||
file: null,
|
||||
file: null,
|
||||
};
|
||||
|
||||
function resetState() {
|
||||
pageState.file = null;
|
||||
pageState.file = null;
|
||||
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
|
||||
const currentPassword = document.getElementById('current-password') as HTMLInputElement;
|
||||
if (currentPassword) currentPassword.value = '';
|
||||
const currentPassword = document.getElementById(
|
||||
'current-password'
|
||||
) as HTMLInputElement;
|
||||
if (currentPassword) currentPassword.value = '';
|
||||
|
||||
const newUserPassword = document.getElementById('new-user-password') as HTMLInputElement;
|
||||
if (newUserPassword) newUserPassword.value = '';
|
||||
const newUserPassword = document.getElementById(
|
||||
'new-user-password'
|
||||
) as HTMLInputElement;
|
||||
if (newUserPassword) newUserPassword.value = '';
|
||||
|
||||
const newOwnerPassword = document.getElementById('new-owner-password') as HTMLInputElement;
|
||||
if (newOwnerPassword) newOwnerPassword.value = '';
|
||||
const newOwnerPassword = document.getElementById(
|
||||
'new-owner-password'
|
||||
) as HTMLInputElement;
|
||||
if (newOwnerPassword) newOwnerPassword.value = '';
|
||||
}
|
||||
|
||||
async function updateUI() {
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
|
||||
if (!fileDisplayArea) return;
|
||||
if (!fileDisplayArea) return;
|
||||
|
||||
fileDisplayArea.innerHTML = '';
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(files: FileList | null) {
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (
|
||||
file.type === 'application/pdf' ||
|
||||
file.name.toLowerCase().endsWith('.pdf')
|
||||
) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function changePermissions() {
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentPassword =
|
||||
(document.getElementById('current-password') as HTMLInputElement)?.value ||
|
||||
'';
|
||||
const newUserPassword =
|
||||
(document.getElementById('new-user-password') as HTMLInputElement)?.value ||
|
||||
'';
|
||||
const newOwnerPassword =
|
||||
(document.getElementById('new-owner-password') as HTMLInputElement)
|
||||
?.value || '';
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing...';
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Processing PDF permissions...';
|
||||
|
||||
const args = [inputPath];
|
||||
|
||||
if (currentPassword) {
|
||||
args.push('--password=' + currentPassword);
|
||||
}
|
||||
|
||||
const currentPassword = (document.getElementById('current-password') as HTMLInputElement)?.value || '';
|
||||
const newUserPassword = (document.getElementById('new-user-password') as HTMLInputElement)?.value || '';
|
||||
const newOwnerPassword = (document.getElementById('new-owner-password') as HTMLInputElement)?.value || '';
|
||||
const shouldEncrypt = newUserPassword || newOwnerPassword;
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
if (shouldEncrypt) {
|
||||
const finalUserPassword = newUserPassword;
|
||||
const finalOwnerPassword = newOwnerPassword;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
args.push('--encrypt', finalUserPassword, finalOwnerPassword, '256');
|
||||
|
||||
const allowPrinting = (
|
||||
document.getElementById('allow-printing') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowCopying = (
|
||||
document.getElementById('allow-copying') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowModifying = (
|
||||
document.getElementById('allow-modifying') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowAnnotating = (
|
||||
document.getElementById('allow-annotating') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowFillingForms = (
|
||||
document.getElementById('allow-filling-forms') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowDocumentAssembly = (
|
||||
document.getElementById('allow-document-assembly') as HTMLInputElement
|
||||
)?.checked;
|
||||
const allowPageExtraction = (
|
||||
document.getElementById('allow-page-extraction') as HTMLInputElement
|
||||
)?.checked;
|
||||
|
||||
if (finalOwnerPassword) {
|
||||
if (!allowModifying) args.push('--modify=none');
|
||||
if (!allowCopying) args.push('--extract=n');
|
||||
if (!allowPrinting) args.push('--print=none');
|
||||
if (!allowAnnotating) args.push('--annotate=n');
|
||||
if (!allowDocumentAssembly) args.push('--assemble=n');
|
||||
if (!allowFillingForms) args.push('--form=n');
|
||||
if (!allowPageExtraction) args.push('--extract=n');
|
||||
if (!allowModifying) args.push('--modify-other=n');
|
||||
} else if (finalUserPassword) {
|
||||
args.push('--allow-insecure');
|
||||
}
|
||||
} else {
|
||||
args.push('--decrypt');
|
||||
}
|
||||
|
||||
args.push('--', outputPath);
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing...';
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
const errorMsg = qpdfError.message || '';
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
if (
|
||||
errorMsg.includes('invalid password') ||
|
||||
errorMsg.includes('incorrect password') ||
|
||||
errorMsg.includes('password')
|
||||
) {
|
||||
throw new Error('INVALID_PASSWORD', { cause: qpdfError });
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Processing PDF permissions...';
|
||||
if (
|
||||
errorMsg.includes('encrypted') ||
|
||||
errorMsg.includes('password required')
|
||||
) {
|
||||
throw new Error('PASSWORD_REQUIRED', { cause: qpdfError });
|
||||
}
|
||||
|
||||
const args = [inputPath];
|
||||
|
||||
if (currentPassword) {
|
||||
args.push('--password=' + currentPassword);
|
||||
}
|
||||
|
||||
const shouldEncrypt = newUserPassword || newOwnerPassword;
|
||||
|
||||
if (shouldEncrypt) {
|
||||
const finalUserPassword = newUserPassword;
|
||||
const finalOwnerPassword = newOwnerPassword;
|
||||
|
||||
args.push('--encrypt', finalUserPassword, finalOwnerPassword, '256');
|
||||
|
||||
const allowPrinting = (document.getElementById('allow-printing') as HTMLInputElement)?.checked;
|
||||
const allowCopying = (document.getElementById('allow-copying') as HTMLInputElement)?.checked;
|
||||
const allowModifying = (document.getElementById('allow-modifying') as HTMLInputElement)?.checked;
|
||||
const allowAnnotating = (document.getElementById('allow-annotating') as HTMLInputElement)?.checked;
|
||||
const allowFillingForms = (document.getElementById('allow-filling-forms') as HTMLInputElement)?.checked;
|
||||
const allowDocumentAssembly = (document.getElementById('allow-document-assembly') as HTMLInputElement)?.checked;
|
||||
const allowPageExtraction = (document.getElementById('allow-page-extraction') as HTMLInputElement)?.checked;
|
||||
|
||||
if (finalOwnerPassword) {
|
||||
if (!allowModifying) args.push('--modify=none');
|
||||
if (!allowCopying) args.push('--extract=n');
|
||||
if (!allowPrinting) args.push('--print=none');
|
||||
if (!allowAnnotating) args.push('--annotate=n');
|
||||
if (!allowDocumentAssembly) args.push('--assemble=n');
|
||||
if (!allowFillingForms) args.push('--form=n');
|
||||
if (!allowPageExtraction) args.push('--extract=n');
|
||||
if (!allowModifying) args.push('--modify-other=n');
|
||||
} else if (finalUserPassword) {
|
||||
args.push('--allow-insecure');
|
||||
}
|
||||
} else {
|
||||
args.push('--decrypt');
|
||||
}
|
||||
|
||||
args.push('--', outputPath);
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
|
||||
const errorMsg = qpdfError.message || '';
|
||||
|
||||
if (
|
||||
errorMsg.includes('invalid password') ||
|
||||
errorMsg.includes('incorrect password') ||
|
||||
errorMsg.includes('password')
|
||||
) {
|
||||
throw new Error('INVALID_PASSWORD');
|
||||
}
|
||||
|
||||
if (
|
||||
errorMsg.includes('encrypted') ||
|
||||
errorMsg.includes('password required')
|
||||
) {
|
||||
throw new Error('PASSWORD_REQUIRED');
|
||||
}
|
||||
|
||||
throw new Error('Processing failed: ' + errorMsg || 'Unknown error');
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Processing resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `permissions-changed-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
let successMessage = 'PDF permissions changed successfully!';
|
||||
if (!shouldEncrypt) {
|
||||
successMessage = 'PDF decrypted successfully! All encryption and restrictions removed.';
|
||||
}
|
||||
|
||||
showAlert('Success', successMessage, 'success', () => { resetState(); });
|
||||
} catch (error: any) {
|
||||
console.error('Error during PDF permission change:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
if (error.message === 'INVALID_PASSWORD') {
|
||||
showAlert(
|
||||
'Incorrect Password',
|
||||
'The current password you entered is incorrect. Please try again.'
|
||||
);
|
||||
} else if (error.message === 'PASSWORD_REQUIRED') {
|
||||
showAlert(
|
||||
'Password Required',
|
||||
'This PDF is password-protected. Please enter the current password to proceed.'
|
||||
);
|
||||
} else {
|
||||
showAlert(
|
||||
'Processing Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password protected.'}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) { }
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) { }
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
throw new Error('Processing failed: ' + errorMsg || 'Unknown error', {
|
||||
cause: qpdfError,
|
||||
});
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Processing resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `permissions-changed-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
let successMessage = 'PDF permissions changed successfully!';
|
||||
if (!shouldEncrypt) {
|
||||
successMessage =
|
||||
'PDF decrypted successfully! All encryption and restrictions removed.';
|
||||
}
|
||||
|
||||
showAlert('Success', successMessage, 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error during PDF permission change:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
if (error.message === 'INVALID_PASSWORD') {
|
||||
showAlert(
|
||||
'Incorrect Password',
|
||||
'The current password you entered is incorrect. Please try again.'
|
||||
);
|
||||
} else if (error.message === 'PASSWORD_REQUIRED') {
|
||||
showAlert(
|
||||
'Password Required',
|
||||
'This PDF is password-protected. Please enter the current password to proceed.'
|
||||
);
|
||||
} else {
|
||||
showAlert(
|
||||
'Processing Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password protected.'}`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink input file from WASM FS', e);
|
||||
}
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink output file from WASM FS', e);
|
||||
}
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return (
|
||||
f.type === 'application/pdf' ||
|
||||
f.name.toLowerCase().endsWith('.pdf')
|
||||
);
|
||||
});
|
||||
}
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf');
|
||||
});
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', changePermissions);
|
||||
}
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', changePermissions);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PDFDocument as PDFLibDocument, rgb } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { CombineSinglePageState } from '@/types';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -81,8 +82,7 @@ async function updateUI() {
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.file = result.file;
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
hideLoader();
|
||||
|
||||
@@ -6,6 +6,7 @@ import Cropper from 'cropperjs';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { CropperState } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -305,10 +306,9 @@ async function performCrop() {
|
||||
async function performMetadataCrop(
|
||||
cropData: Record<number, any>
|
||||
): Promise<Uint8Array> {
|
||||
const pdfToModify = await PDFLibDocument.load(
|
||||
cropperState.originalPdfBytes!,
|
||||
{ throwOnInvalidObject: false }
|
||||
);
|
||||
const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
for (const pageNum in cropData) {
|
||||
const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum));
|
||||
@@ -349,7 +349,7 @@ async function performFlatteningCrop(
|
||||
cropData: Record<number, any>
|
||||
): Promise<Uint8Array> {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
const sourcePdfDocForCopying = await PDFLibDocument.load(
|
||||
const sourcePdfDocForCopying = await loadPdfDocument(
|
||||
cropperState.originalPdfBytes!,
|
||||
{ throwOnInvalidObject: false }
|
||||
);
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, readFileAsArrayBuffer, getPDFDocument } from '../utils/helpers.js';
|
||||
import {
|
||||
downloadFile,
|
||||
readFileAsArrayBuffer,
|
||||
getPDFDocument,
|
||||
} from '../utils/helpers.js';
|
||||
import { state } from '../state.js';
|
||||
import Cropper from 'cropperjs';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
// --- Global State for the Cropper Tool ---
|
||||
const cropperState = {
|
||||
@@ -145,21 +153,21 @@ async function performMetadataCrop(pdfToModify: any, cropData: any) {
|
||||
|
||||
// Define the 4 corners of the crop rectangle in visual coordinates (Top-Left origin)
|
||||
const visualCorners = [
|
||||
{ x: cropX, y: cropY }, // TL
|
||||
{ x: cropX + cropW, y: cropY }, // TR
|
||||
{ x: cropX + cropW, y: cropY + cropH }, // BR
|
||||
{ x: cropX, y: cropY + cropH }, // BL
|
||||
{ x: cropX, y: cropY }, // TL
|
||||
{ x: cropX + cropW, y: cropY }, // TR
|
||||
{ x: cropX + cropW, y: cropY + cropH }, // BR
|
||||
{ x: cropX, y: cropY + cropH }, // BL
|
||||
];
|
||||
|
||||
// This handles rotation, media box offsets, and coordinate system flips automatically
|
||||
const pdfCorners = visualCorners.map(p => {
|
||||
const pdfCorners = visualCorners.map((p) => {
|
||||
return viewport.convertToPdfPoint(p.x, p.y);
|
||||
});
|
||||
|
||||
// Find the bounding box of the converted points in PDF coordinates
|
||||
// convertToPdfPoint returns [x, y] arrays
|
||||
const pdfXs = pdfCorners.map(p => p[0]);
|
||||
const pdfYs = pdfCorners.map(p => p[1]);
|
||||
const pdfXs = pdfCorners.map((p) => p[0]);
|
||||
const pdfYs = pdfCorners.map((p) => p[1]);
|
||||
|
||||
const minX = Math.min(...pdfXs);
|
||||
const maxX = Math.max(...pdfXs);
|
||||
@@ -179,9 +187,9 @@ async function performFlatteningCrop(cropData: any) {
|
||||
const newPdfDoc = await PDFLibDocument.create();
|
||||
|
||||
// Load the original PDF with pdf-lib to copy un-cropped pages from
|
||||
const sourcePdfDocForCopying = await PDFLibDocument.load(
|
||||
cropperState.originalPdfBytes,
|
||||
{ignoreEncryption: true, throwOnInvalidObject: false}
|
||||
const sourcePdfDocForCopying = await loadPdfDocument(
|
||||
cropperState.originalPdfBytes,
|
||||
{ ignoreEncryption: true, throwOnInvalidObject: false }
|
||||
);
|
||||
const totalPages = cropperState.pdfDoc.numPages;
|
||||
|
||||
@@ -224,7 +232,11 @@ async function performFlatteningCrop(cropData: any) {
|
||||
const jpegQuality = 0.9;
|
||||
|
||||
const jpegBytes = await new Promise((res) =>
|
||||
finalCanvas.toBlob((blob) => blob.arrayBuffer().then(res), 'image/jpeg', jpegQuality)
|
||||
finalCanvas.toBlob(
|
||||
(blob) => blob.arrayBuffer().then(res),
|
||||
'image/jpeg',
|
||||
jpegQuality
|
||||
)
|
||||
);
|
||||
const embeddedImage = await newPdfDoc.embedJpg(jpegBytes as ArrayBuffer);
|
||||
const newPage = newPdfDoc.addPage([finalWidth, finalHeight]);
|
||||
@@ -273,80 +285,73 @@ export async function setupCropperTool() {
|
||||
.getElementById('next-page')
|
||||
.addEventListener('click', () => changePage(1));
|
||||
|
||||
document
|
||||
.getElementById('crop-button')
|
||||
.addEventListener('click', async () => {
|
||||
// Get the last known crop from the active page before processing
|
||||
saveCurrentCrop();
|
||||
document.getElementById('crop-button').addEventListener('click', async () => {
|
||||
// Get the last known crop from the active page before processing
|
||||
saveCurrentCrop();
|
||||
|
||||
const isDestructive = (
|
||||
document.getElementById('destructive-crop-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
const isApplyToAll = (
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
const isDestructive = (
|
||||
document.getElementById('destructive-crop-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
const isApplyToAll = (
|
||||
document.getElementById('apply-to-all-toggle') as HTMLInputElement
|
||||
).checked;
|
||||
|
||||
let finalCropData = {};
|
||||
if (isApplyToAll) {
|
||||
const currentCrop =
|
||||
cropperState.pageCrops[cropperState.currentPageNum];
|
||||
if (!currentCrop) {
|
||||
showAlert('No Crop Area', 'Please select an area to crop first.');
|
||||
return;
|
||||
}
|
||||
// Apply the active page's crop to all pages
|
||||
for (let i = 1; i <= cropperState.pdfDoc.numPages; i++) {
|
||||
finalCropData[i] = currentCrop;
|
||||
}
|
||||
} else {
|
||||
// If not applying to all, only process pages with saved crops
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce(
|
||||
(obj, key) => {
|
||||
obj[key] = cropperState.pageCrops[key];
|
||||
return obj;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(finalCropData).length === 0) {
|
||||
showAlert(
|
||||
'No Crop Area',
|
||||
'Please select an area on at least one page to crop.'
|
||||
);
|
||||
let finalCropData = {};
|
||||
if (isApplyToAll) {
|
||||
const currentCrop = cropperState.pageCrops[cropperState.currentPageNum];
|
||||
if (!currentCrop) {
|
||||
showAlert('No Crop Area', 'Please select an area to crop first.');
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Applying crop...');
|
||||
|
||||
try {
|
||||
let finalPdfBytes;
|
||||
if (isDestructive) {
|
||||
const newPdfDoc = await performFlatteningCrop(finalCropData);
|
||||
finalPdfBytes = await newPdfDoc.save();
|
||||
} else {
|
||||
const pdfToModify = await PDFLibDocument.load(
|
||||
cropperState.originalPdfBytes,
|
||||
{ignoreEncryption: true, throwOnInvalidObject: false}
|
||||
);
|
||||
await performMetadataCrop(pdfToModify, finalCropData);
|
||||
finalPdfBytes = await pdfToModify.save();
|
||||
}
|
||||
|
||||
const fileName = isDestructive
|
||||
? 'flattened_crop.pdf'
|
||||
: 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert('Success', 'Crop complete! Your download has started.');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert('Error', 'An error occurred during cropping.');
|
||||
} finally {
|
||||
hideLoader();
|
||||
// Apply the active page's crop to all pages
|
||||
for (let i = 1; i <= cropperState.pdfDoc.numPages; i++) {
|
||||
finalCropData[i] = currentCrop;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If not applying to all, only process pages with saved crops
|
||||
finalCropData = Object.keys(cropperState.pageCrops).reduce((obj, key) => {
|
||||
obj[key] = cropperState.pageCrops[key];
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
if (Object.keys(finalCropData).length === 0) {
|
||||
showAlert(
|
||||
'No Crop Area',
|
||||
'Please select an area on at least one page to crop.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
showLoader('Applying crop...');
|
||||
|
||||
try {
|
||||
let finalPdfBytes;
|
||||
if (isDestructive) {
|
||||
const newPdfDoc = await performFlatteningCrop(finalCropData);
|
||||
finalPdfBytes = await newPdfDoc.save();
|
||||
} else {
|
||||
const pdfToModify = await loadPdfDocument(
|
||||
cropperState.originalPdfBytes,
|
||||
{ ignoreEncryption: true, throwOnInvalidObject: false }
|
||||
);
|
||||
await performMetadataCrop(pdfToModify, finalCropData);
|
||||
finalPdfBytes = await pdfToModify.save();
|
||||
}
|
||||
|
||||
const fileName = isDestructive
|
||||
? 'flattened_crop.pdf'
|
||||
: 'standard_crop.pdf';
|
||||
downloadFile(
|
||||
new Blob([finalPdfBytes], { type: 'application/pdf' }),
|
||||
fileName
|
||||
);
|
||||
showAlert('Success', 'Crop complete! Your download has started.');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
showAlert('Error', 'An error occurred during cropping.');
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
downloadFile,
|
||||
parsePageRanges,
|
||||
} from '../utils/helpers.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { deletePdfPages } from '../utils/pdf-operations.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { DeletePagesState } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -94,8 +94,7 @@ async function handleFile(file: File) {
|
||||
}
|
||||
showLoader('Loading PDF...');
|
||||
deleteState.file = result.file;
|
||||
deleteState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
deleteState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
deleteState.pdfJsDoc = result.pdf;
|
||||
@@ -272,7 +271,7 @@ async function deletePages() {
|
||||
);
|
||||
const baseName = deleteState.file?.name.replace('.pdf', '') || 'document';
|
||||
downloadFile(
|
||||
new Blob([resultBytes as unknown as BlobPart], {
|
||||
new Blob([new Uint8Array(resultBytes)], {
|
||||
type: 'application/pdf',
|
||||
}),
|
||||
`${baseName}_pages_removed.pdf`
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: DividePagesState = {
|
||||
file: null,
|
||||
@@ -87,9 +88,7 @@ async function updateUI() {
|
||||
pageState.file = result.file;
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.totalPages = pageState.pdfDoc.getPageCount();
|
||||
hideLoader();
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import { EditMetadataState } from '@/types';
|
||||
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument, PDFName, PDFString } from 'pdf-lib';
|
||||
import { PDFName, PDFString } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: EditMetadataState = {
|
||||
file: null,
|
||||
@@ -228,8 +229,7 @@ async function updateUI() {
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.file = result.file;
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
hideLoader();
|
||||
|
||||
@@ -207,7 +207,7 @@ export function renderEmailToHtml(
|
||||
): string {
|
||||
const { includeCcBcc = true, includeAttachments = true } = options;
|
||||
|
||||
let processedHtml = '';
|
||||
let processedHtml: string;
|
||||
if (email.htmlBody) {
|
||||
const sanitizedHtml = sanitizeEmailHtml(email.htmlBody);
|
||||
processedHtml = processInlineImages(sanitizedHtml, email.attachments);
|
||||
|
||||
@@ -1,243 +1,267 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import {
|
||||
downloadFile,
|
||||
formatBytes,
|
||||
initializeQpdf,
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { EncryptPdfState } from '@/types';
|
||||
|
||||
const pageState: EncryptPdfState = {
|
||||
file: null,
|
||||
file: null,
|
||||
};
|
||||
|
||||
function resetState() {
|
||||
pageState.file = null;
|
||||
pageState.file = null;
|
||||
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
|
||||
const userPasswordInput = document.getElementById('user-password-input') as HTMLInputElement;
|
||||
if (userPasswordInput) userPasswordInput.value = '';
|
||||
const userPasswordInput = document.getElementById(
|
||||
'user-password-input'
|
||||
) as HTMLInputElement;
|
||||
if (userPasswordInput) userPasswordInput.value = '';
|
||||
|
||||
const ownerPasswordInput = document.getElementById('owner-password-input') as HTMLInputElement;
|
||||
if (ownerPasswordInput) ownerPasswordInput.value = '';
|
||||
const ownerPasswordInput = document.getElementById(
|
||||
'owner-password-input'
|
||||
) as HTMLInputElement;
|
||||
if (ownerPasswordInput) ownerPasswordInput.value = '';
|
||||
}
|
||||
|
||||
async function updateUI() {
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
|
||||
if (!fileDisplayArea) return;
|
||||
if (!fileDisplayArea) return;
|
||||
|
||||
fileDisplayArea.innerHTML = '';
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(files: FileList | null) {
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (
|
||||
file.type === 'application/pdf' ||
|
||||
file.name.toLowerCase().endsWith('.pdf')
|
||||
) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function encryptPdf() {
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const userPassword =
|
||||
(document.getElementById('user-password-input') as HTMLInputElement)
|
||||
?.value || '';
|
||||
const ownerPasswordInput =
|
||||
(document.getElementById('owner-password-input') as HTMLInputElement)
|
||||
?.value || '';
|
||||
|
||||
if (!userPassword) {
|
||||
showAlert('Input Required', 'Please enter a user password.');
|
||||
return;
|
||||
}
|
||||
|
||||
const ownerPassword = ownerPasswordInput || userPassword;
|
||||
const hasDistinctOwnerPassword = ownerPasswordInput !== '';
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing encryption...';
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
|
||||
if (loaderText)
|
||||
loaderText.textContent = 'Encrypting PDF with 256-bit AES...';
|
||||
|
||||
const args = [inputPath, '--encrypt', userPassword, ownerPassword, '256'];
|
||||
|
||||
// Only add restrictions if a distinct owner password was provided
|
||||
if (hasDistinctOwnerPassword) {
|
||||
args.push(
|
||||
'--modify=none',
|
||||
'--extract=n',
|
||||
'--print=none',
|
||||
'--accessibility=n',
|
||||
'--annotate=n',
|
||||
'--assemble=n',
|
||||
'--form=n',
|
||||
'--modify-other=n'
|
||||
);
|
||||
}
|
||||
|
||||
const userPassword = (document.getElementById('user-password-input') as HTMLInputElement)?.value || '';
|
||||
const ownerPasswordInput = (document.getElementById('owner-password-input') as HTMLInputElement)?.value || '';
|
||||
|
||||
if (!userPassword) {
|
||||
showAlert('Input Required', 'Please enter a user password.');
|
||||
return;
|
||||
}
|
||||
|
||||
const ownerPassword = ownerPasswordInput || userPassword;
|
||||
const hasDistinctOwnerPassword = ownerPasswordInput !== '';
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
args.push('--', outputPath);
|
||||
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing encryption...';
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Encrypting PDF with 256-bit AES...';
|
||||
|
||||
const args = [inputPath, '--encrypt', userPassword, ownerPassword, '256'];
|
||||
|
||||
// Only add restrictions if a distinct owner password was provided
|
||||
if (hasDistinctOwnerPassword) {
|
||||
args.push(
|
||||
'--modify=none',
|
||||
'--extract=n',
|
||||
'--print=none',
|
||||
'--accessibility=n',
|
||||
'--annotate=n',
|
||||
'--assemble=n',
|
||||
'--form=n',
|
||||
'--modify-other=n'
|
||||
);
|
||||
}
|
||||
|
||||
args.push('--', outputPath);
|
||||
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
throw new Error(
|
||||
'Encryption failed: ' + (qpdfError.message || 'Unknown error')
|
||||
);
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Encryption resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `encrypted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
let successMessage = 'PDF encrypted successfully with 256-bit AES!';
|
||||
if (!hasDistinctOwnerPassword) {
|
||||
successMessage +=
|
||||
' Note: Without a separate owner password, the PDF has no usage restrictions.';
|
||||
}
|
||||
|
||||
showAlert('Success', successMessage, 'success', () => { resetState(); });
|
||||
} catch (error: any) {
|
||||
console.error('Error during PDF encryption:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Encryption Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink input file:', e);
|
||||
}
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink output file:', e);
|
||||
}
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
throw new Error(
|
||||
'Encryption failed: ' + (qpdfError.message || 'Unknown error'),
|
||||
{ cause: qpdfError }
|
||||
);
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Encryption resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `encrypted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
let successMessage = 'PDF encrypted successfully with 256-bit AES!';
|
||||
if (!hasDistinctOwnerPassword) {
|
||||
successMessage +=
|
||||
' Note: Without a separate owner password, the PDF has no usage restrictions.';
|
||||
}
|
||||
|
||||
showAlert('Success', successMessage, 'success', () => {
|
||||
resetState();
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Error during PDF encryption:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Encryption Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink input file:', e);
|
||||
}
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink output file:', e);
|
||||
}
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return (
|
||||
f.type === 'application/pdf' ||
|
||||
f.name.toLowerCase().endsWith('.pdf')
|
||||
);
|
||||
});
|
||||
}
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf');
|
||||
});
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', encryptPdf);
|
||||
}
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', encryptPdf);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import JSZip from 'jszip';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface ExtractState {
|
||||
file: File | null;
|
||||
@@ -99,8 +100,7 @@ async function handleFile(file: File) {
|
||||
showLoader('Loading PDF...');
|
||||
extractState.file = result.file;
|
||||
result.pdf.destroy();
|
||||
extractState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
extractState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
extractState.totalPages = extractState.pdfDoc.getPageCount();
|
||||
|
||||
@@ -7,6 +7,7 @@ import { icons, createIcons } from 'lucide';
|
||||
import JSZip from 'jszip';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
import { FlattenPdfState } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: FlattenPdfState = {
|
||||
files: [],
|
||||
@@ -115,7 +116,7 @@ async function flattenPdf() {
|
||||
|
||||
const file = pageState.files[0];
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer);
|
||||
|
||||
try {
|
||||
flattenFormsInDoc(pdfDoc);
|
||||
@@ -154,7 +155,7 @@ async function flattenPdf() {
|
||||
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer);
|
||||
|
||||
try {
|
||||
flattenFormsInDoc(pdfDoc);
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
PageData,
|
||||
} from '@/types';
|
||||
import { extractExistingFields as extractExistingPdfFields } from './form-creator-extraction.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
let fields: FormField[] = [];
|
||||
let selectedField: FormField | null = null;
|
||||
@@ -3140,9 +3141,7 @@ async function handlePdfUpload(file: File) {
|
||||
if (!result) return;
|
||||
const arrayBuffer = result.bytes;
|
||||
uploadedPdfjsDoc = result.pdf;
|
||||
uploadedPdfDoc = await PDFDocument.load(arrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
uploadedPdfDoc = await loadPdfDocument(arrayBuffer);
|
||||
|
||||
// Check for existing fields and update counter
|
||||
existingFieldNames.clear();
|
||||
|
||||
@@ -6,9 +6,10 @@ import {
|
||||
formatBytes,
|
||||
parsePageRanges,
|
||||
} from '../utils/helpers.js';
|
||||
import { PDFDocument as PDFLibDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||
import { rgb, StandardFonts } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { HeaderFooterState } from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: HeaderFooterState = { file: null, pdfDoc: null };
|
||||
|
||||
@@ -68,9 +69,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
result.pdf.destroy();
|
||||
|
||||
@@ -253,9 +252,12 @@ async function addHeaderFooter() {
|
||||
resetState();
|
||||
}
|
||||
);
|
||||
} catch (e: any) {
|
||||
} catch (e: unknown) {
|
||||
console.error(e);
|
||||
showAlert('Error', e.message || 'Could not add header or footer.');
|
||||
showAlert(
|
||||
'Error',
|
||||
e instanceof Error ? e.message : 'Could not add header or footer.'
|
||||
);
|
||||
} finally {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
@@ -203,7 +203,9 @@ async function preprocessFile(file: File): Promise<File> {
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to convert HEIC: ${file.name}`, e);
|
||||
throw new Error(`Failed to process HEIC file: ${file.name}`);
|
||||
throw new Error(`Failed to process HEIC file: ${file.name}`, {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +249,9 @@ async function preprocessFile(file: File): Promise<File> {
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Failed to convert WebP: ${file.name}`, e);
|
||||
throw new Error(`Failed to process WebP file: ${file.name}`);
|
||||
throw new Error(`Failed to process WebP file: ${file.name}`, {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { applyInvertColors } from '../utils/image-effects.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { InvertColorsState } from '@/types';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -70,9 +71,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
updateFileDisplay();
|
||||
document.getElementById('options-panel')?.classList.remove('hidden');
|
||||
|
||||
@@ -3,6 +3,7 @@ import { downloadFile, formatBytes, hexToRgb } from '../utils/helpers.js';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument, rgb, PageSizes } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface NUpState {
|
||||
file: File | null;
|
||||
@@ -74,8 +75,7 @@ async function updateUI() {
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.file = result.file;
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
hideLoader();
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import Sortable from 'sortablejs';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -176,8 +177,7 @@ async function handleFile(file: File) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
organizeState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
organizeState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
organizeState.pdfJsDoc = result.pdf;
|
||||
|
||||
@@ -5,16 +5,20 @@ import {
|
||||
convertPoints,
|
||||
} from '../utils/helpers.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { PageDimensionsState } from '@/types';
|
||||
import {
|
||||
PageDimensionsState,
|
||||
AnalyzedPageData,
|
||||
UniqueSizeEntry,
|
||||
} from '@/types';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
const pageState: PageDimensionsState = {
|
||||
file: null,
|
||||
pdfDoc: null,
|
||||
};
|
||||
|
||||
let analyzedPagesData: any[] = [];
|
||||
let analyzedPagesData: AnalyzedPageData[] = [];
|
||||
|
||||
function calculateAspectRatio(width: number, height: number): string {
|
||||
const ratio = width / height;
|
||||
@@ -35,11 +39,12 @@ function calculateArea(width: number, height: number, unit: string): string {
|
||||
convertedArea = (areaInPoints / (72 * 72)) * (25.4 * 25.4);
|
||||
unitSuffix = 'mm²';
|
||||
break;
|
||||
case 'px':
|
||||
case 'px': {
|
||||
const pxPerPoint = 96 / 72;
|
||||
convertedArea = areaInPoints * (pxPerPoint * pxPerPoint);
|
||||
unitSuffix = 'px²';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
convertedArea = areaInPoints;
|
||||
unitSuffix = 'pt²';
|
||||
@@ -52,8 +57,8 @@ function calculateArea(width: number, height: number, unit: string): string {
|
||||
function getSummaryStats() {
|
||||
const totalPages = analyzedPagesData.length;
|
||||
|
||||
const uniqueSizes = new Map();
|
||||
analyzedPagesData.forEach((pageData: any) => {
|
||||
const uniqueSizes = new Map<string, UniqueSizeEntry>();
|
||||
analyzedPagesData.forEach((pageData) => {
|
||||
const key = `${pageData.width.toFixed(2)}x${pageData.height.toFixed(2)}`;
|
||||
const label = `${pageData.standardSize} (${pageData.orientation})`;
|
||||
uniqueSizes.set(key, {
|
||||
@@ -110,7 +115,7 @@ function renderSummary() {
|
||||
<ul class="space-y-1 text-sm text-gray-300">
|
||||
${stats.uniqueSizes
|
||||
.map(
|
||||
(size: any) => `
|
||||
(size: UniqueSizeEntry) => `
|
||||
<li>• ${size.label}: ${size.count} page${size.count > 1 ? 's' : ''}</li>
|
||||
`
|
||||
)
|
||||
@@ -145,7 +150,7 @@ function renderTable(unit: string) {
|
||||
|
||||
const pageNumCell = document.createElement('td');
|
||||
pageNumCell.className = 'px-4 py-3 text-white';
|
||||
pageNumCell.textContent = pageData.pageNum;
|
||||
pageNumCell.textContent = String(pageData.pageNum);
|
||||
|
||||
const dimensionsCell = document.createElement('td');
|
||||
dimensionsCell.className = 'px-4 py-3 text-gray-300';
|
||||
@@ -202,7 +207,7 @@ function exportToCSV() {
|
||||
];
|
||||
const csvRows = [headers.join(',')];
|
||||
|
||||
analyzedPagesData.forEach((pageData: any) => {
|
||||
analyzedPagesData.forEach((pageData) => {
|
||||
const width = convertPoints(pageData.width, unit);
|
||||
const height = convertPoints(pageData.height, unit);
|
||||
const aspectRatio = calculateAspectRatio(pageData.width, pageData.height);
|
||||
@@ -239,7 +244,7 @@ function analyzeAndDisplayDimensions() {
|
||||
analyzedPagesData = [];
|
||||
const pages = pageState.pdfDoc.getPages();
|
||||
|
||||
pages.forEach((page: any, index: number) => {
|
||||
pages.forEach((page, index) => {
|
||||
const { width, height } = page.getSize();
|
||||
const rotation = page.getRotation().angle || 0;
|
||||
|
||||
@@ -341,9 +346,7 @@ async function handleFileSelect(files: FileList | null) {
|
||||
result.pdf.destroy();
|
||||
|
||||
pageState.file = result.file;
|
||||
pageState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
updateUI();
|
||||
analyzeAndDisplayDimensions();
|
||||
} catch (e) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type PageNumberPosition,
|
||||
type PageNumberFormat,
|
||||
} from '../utils/pdf-operations.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
@@ -89,9 +90,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
result.pdf.destroy();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createIcons, icons } from 'lucide';
|
||||
import { PDFDocument as PDFLibDocument, degrees, PageSizes } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.mjs',
|
||||
@@ -98,7 +99,7 @@ async function updateUI() {
|
||||
pageState.pdfBytes = new Uint8Array(result.bytes);
|
||||
pageState.pdfjsDoc = result.pdf;
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(pageState.pdfBytes, {
|
||||
pageState.pdfDoc = await loadPdfDocument(pageState.pdfBytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
@@ -430,7 +431,7 @@ async function createBooklet() {
|
||||
showLoader('Creating Booklet...');
|
||||
|
||||
try {
|
||||
const sourceDoc = await PDFLibDocument.load(pageState.pdfBytes.slice());
|
||||
const sourceDoc = await loadPdfDocument(pageState.pdfBytes.slice());
|
||||
const rotationMode =
|
||||
(
|
||||
document.querySelector(
|
||||
|
||||
@@ -20,6 +20,7 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
).toString();
|
||||
|
||||
import { t } from '../i18n/i18n';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface PageData {
|
||||
id: string; // Unique ID for DOM reconciliation
|
||||
@@ -435,8 +436,7 @@ async function loadPdfs(files: File[]) {
|
||||
pwResult.pdf.destroy();
|
||||
arrayBuffer = pwResult.bytes as ArrayBuffer;
|
||||
|
||||
const pdfDoc = await PDFLibDocument.load(arrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
currentPdfDocs.push(pdfDoc);
|
||||
@@ -859,8 +859,7 @@ async function handleInsertPdf(e: Event) {
|
||||
if (!pwResult) return;
|
||||
pwResult.pdf.destroy();
|
||||
|
||||
const pdfDoc = await PDFLibDocument.load(pwResult.bytes, {
|
||||
ignoreEncryption: true,
|
||||
const pdfDoc = await loadPdfDocument(pwResult.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
currentPdfDocs.push(pdfDoc);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
// State management
|
||||
const pageState: { pdfDoc: PDFDocument | null; file: File | null } = {
|
||||
@@ -108,9 +109,7 @@ async function handleFileUpload(file: File) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
updateFileDisplay();
|
||||
document.getElementById('options-panel')?.classList.remove('hidden');
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { createIcons, icons } from 'lucide';
|
||||
import { initPagePreview } from '../utils/page-preview.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -122,9 +123,7 @@ async function handleFileUpload(file: File) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
pageState.detectedBlankPages = [];
|
||||
updateFileDisplay();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { downloadFile, formatBytes } from '../utils/helpers.js';
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface PageState {
|
||||
file: File | null;
|
||||
@@ -150,9 +151,7 @@ async function removeMetadata() {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Removing all metadata...';
|
||||
result.pdf.destroy();
|
||||
const pdfDoc = await PDFDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
const pdfDoc = await loadPdfDocument(result.bytes);
|
||||
|
||||
removeMetadataFromDoc(pdfDoc);
|
||||
|
||||
|
||||
@@ -1,230 +1,250 @@
|
||||
import { showAlert } from '../ui.js';
|
||||
import { downloadFile, formatBytes, initializeQpdf, readFileAsArrayBuffer } from '../utils/helpers.js';
|
||||
import {
|
||||
downloadFile,
|
||||
formatBytes,
|
||||
initializeQpdf,
|
||||
readFileAsArrayBuffer,
|
||||
} from '../utils/helpers.js';
|
||||
import { icons, createIcons } from 'lucide';
|
||||
import { RemoveRestrictionsState } from '@/types';
|
||||
|
||||
const pageState: RemoveRestrictionsState = {
|
||||
file: null,
|
||||
file: null,
|
||||
};
|
||||
|
||||
function resetState() {
|
||||
pageState.file = null;
|
||||
pageState.file = null;
|
||||
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
||||
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
|
||||
const passwordInput = document.getElementById('owner-password-remove') as HTMLInputElement;
|
||||
if (passwordInput) passwordInput.value = '';
|
||||
const passwordInput = document.getElementById(
|
||||
'owner-password-remove'
|
||||
) as HTMLInputElement;
|
||||
if (passwordInput) passwordInput.value = '';
|
||||
}
|
||||
|
||||
async function updateUI() {
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
const fileDisplayArea = document.getElementById('file-display-area');
|
||||
const toolOptions = document.getElementById('tool-options');
|
||||
|
||||
if (!fileDisplayArea) return;
|
||||
if (!fileDisplayArea) return;
|
||||
|
||||
fileDisplayArea.innerHTML = '';
|
||||
fileDisplayArea.innerHTML = '';
|
||||
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className = 'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
if (pageState.file) {
|
||||
const fileDiv = document.createElement('div');
|
||||
fileDiv.className =
|
||||
'flex items-center justify-between bg-gray-700 p-3 rounded-lg text-sm';
|
||||
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'flex flex-col overflow-hidden';
|
||||
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
const nameSpan = document.createElement('div');
|
||||
nameSpan.className = 'truncate font-medium text-gray-200 text-sm mb-1';
|
||||
nameSpan.textContent = pageState.file.name;
|
||||
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
const metaSpan = document.createElement('div');
|
||||
metaSpan.className = 'text-xs text-gray-400';
|
||||
metaSpan.textContent = formatBytes(pageState.file.size);
|
||||
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
infoContainer.append(nameSpan, metaSpan);
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = function () {
|
||||
resetState();
|
||||
};
|
||||
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
fileDiv.append(infoContainer, removeBtn);
|
||||
fileDisplayArea.appendChild(fileDiv);
|
||||
createIcons({ icons });
|
||||
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
if (toolOptions) toolOptions.classList.remove('hidden');
|
||||
} else {
|
||||
if (toolOptions) toolOptions.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(files: FileList | null) {
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
if (
|
||||
file.type === 'application/pdf' ||
|
||||
file.name.toLowerCase().endsWith('.pdf')
|
||||
) {
|
||||
pageState.file = file;
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function removeRestrictions() {
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
if (!pageState.file) {
|
||||
showAlert('No File', 'Please upload a PDF file first.');
|
||||
return;
|
||||
}
|
||||
|
||||
const password =
|
||||
(document.getElementById('owner-password-remove') as HTMLInputElement)
|
||||
?.value || '';
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing...';
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Removing restrictions...';
|
||||
|
||||
const args = [inputPath];
|
||||
|
||||
if (password) {
|
||||
args.push(`--password=${password}`);
|
||||
}
|
||||
|
||||
const password = (document.getElementById('owner-password-remove') as HTMLInputElement)?.value || '';
|
||||
|
||||
const inputPath = '/input.pdf';
|
||||
const outputPath = '/output.pdf';
|
||||
let qpdf: any;
|
||||
|
||||
const loaderModal = document.getElementById('loader-modal');
|
||||
const loaderText = document.getElementById('loader-text');
|
||||
args.push('--decrypt', '--remove-restrictions', '--', outputPath);
|
||||
|
||||
try {
|
||||
if (loaderModal) loaderModal.classList.remove('hidden');
|
||||
if (loaderText) loaderText.textContent = 'Initializing...';
|
||||
|
||||
qpdf = await initializeQpdf();
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Reading PDF...';
|
||||
const fileBuffer = await readFileAsArrayBuffer(pageState.file);
|
||||
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
||||
|
||||
qpdf.FS.writeFile(inputPath, uint8Array);
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Removing restrictions...';
|
||||
|
||||
const args = [inputPath];
|
||||
|
||||
if (password) {
|
||||
args.push(`--password=${password}`);
|
||||
}
|
||||
|
||||
args.push('--decrypt', '--remove-restrictions', '--', outputPath);
|
||||
|
||||
try {
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
if (
|
||||
qpdfError.message?.includes('password') ||
|
||||
qpdfError.message?.includes('encrypt')
|
||||
) {
|
||||
throw new Error(
|
||||
'Failed to remove restrictions. The PDF may require the correct owner password.'
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Failed to remove restrictions: ' +
|
||||
(qpdfError.message || 'Unknown error')
|
||||
);
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Operation resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `unrestricted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
showAlert(
|
||||
'Success',
|
||||
'PDF restrictions removed successfully! The file is now fully editable and printable.',
|
||||
'success',
|
||||
() => { resetState(); }
|
||||
qpdf.callMain(args);
|
||||
} catch (qpdfError: any) {
|
||||
console.error('qpdf execution error:', qpdfError);
|
||||
if (
|
||||
qpdfError.message?.includes('password') ||
|
||||
qpdfError.message?.includes('encrypt')
|
||||
) {
|
||||
throw new Error(
|
||||
'Failed to remove restrictions. The PDF may require the correct owner password.',
|
||||
{ cause: qpdfError }
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error during restriction removal:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Operation Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password-protected.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink input file:', e);
|
||||
}
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink output file:', e);
|
||||
}
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Failed to remove restrictions: ' +
|
||||
(qpdfError.message || 'Unknown error'),
|
||||
{ cause: qpdfError }
|
||||
);
|
||||
}
|
||||
|
||||
if (loaderText) loaderText.textContent = 'Preparing download...';
|
||||
const outputFile = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
||||
|
||||
if (!outputFile || outputFile.length === 0) {
|
||||
throw new Error('Operation resulted in an empty file.');
|
||||
}
|
||||
|
||||
const blob = new Blob([outputFile], { type: 'application/pdf' });
|
||||
downloadFile(blob, `unrestricted-${pageState.file.name}`);
|
||||
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
|
||||
showAlert(
|
||||
'Success',
|
||||
'PDF restrictions removed successfully! The file is now fully editable and printable.',
|
||||
'success',
|
||||
() => {
|
||||
resetState();
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error during restriction removal:', error);
|
||||
if (loaderModal) loaderModal.classList.add('hidden');
|
||||
showAlert(
|
||||
'Operation Failed',
|
||||
`An error occurred: ${error.message || 'The PDF might be corrupted or password-protected.'}`
|
||||
);
|
||||
} finally {
|
||||
try {
|
||||
if (qpdf?.FS) {
|
||||
try {
|
||||
qpdf.FS.unlink(inputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink input file:', e);
|
||||
}
|
||||
try {
|
||||
qpdf.FS.unlink(outputPath);
|
||||
} catch (e) {
|
||||
console.warn('Failed to unlink output file:', e);
|
||||
}
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to cleanup WASM FS:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
||||
const dropZone = document.getElementById('drop-zone');
|
||||
const processBtn = document.getElementById('process-btn');
|
||||
const backBtn = document.getElementById('back-to-tools');
|
||||
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function () {
|
||||
window.location.href = import.meta.env.BASE_URL;
|
||||
});
|
||||
}
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return (
|
||||
f.type === 'application/pdf' ||
|
||||
f.name.toLowerCase().endsWith('.pdf')
|
||||
);
|
||||
});
|
||||
}
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (fileInput && dropZone) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
handleFileSelect((e.target as HTMLInputElement).files);
|
||||
});
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
dropZone.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove('bg-gray-700');
|
||||
const files = e.dataTransfer?.files;
|
||||
if (files && files.length > 0) {
|
||||
const pdfFiles = Array.from(files).filter(function (f) {
|
||||
return f.type === 'application/pdf' || f.name.toLowerCase().endsWith('.pdf');
|
||||
});
|
||||
if (pdfFiles.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(pdfFiles[0]);
|
||||
handleFileSelect(dataTransfer.files);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fileInput.addEventListener('click', function () {
|
||||
fileInput.value = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', removeRestrictions);
|
||||
}
|
||||
if (processBtn) {
|
||||
processBtn.addEventListener('click', removeRestrictions);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import JSZip from 'jszip';
|
||||
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
||||
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
interface ReverseState {
|
||||
files: File[];
|
||||
@@ -79,7 +80,7 @@ function updateUI() {
|
||||
|
||||
async function reverseSingleFile(file: File): Promise<Uint8Array> {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const pdfDoc = await PDFLibDocument.load(arrayBuffer);
|
||||
const pdfDoc = await loadPdfDocument(arrayBuffer);
|
||||
|
||||
const newPdf = await PDFLibDocument.create();
|
||||
const pageCount = pdfDoc.getPageCount();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../utils/render-utils.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -235,8 +236,7 @@ async function updateUI() {
|
||||
}
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { rotatePdfPages } from '../utils/pdf-operations.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -207,8 +208,7 @@ async function updateUI() {
|
||||
}
|
||||
showLoader('Loading PDF...');
|
||||
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,16 +6,9 @@ import {
|
||||
downloadFile,
|
||||
} from '../utils/helpers.js';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { t } from '../i18n/i18n';
|
||||
|
||||
interface SignState {
|
||||
file: File | null;
|
||||
pdfDoc: any;
|
||||
viewerIframe: HTMLIFrameElement | null;
|
||||
viewerReady: boolean;
|
||||
blobUrl: string | null;
|
||||
}
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
import type { SignState, PDFViewerWindow } from '@/types';
|
||||
|
||||
const signState: SignState = {
|
||||
file: null,
|
||||
@@ -124,7 +117,7 @@ async function updateFileDisplay() {
|
||||
|
||||
const removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
||||
removeBtn.innerHTML = '<i data-lucide=\"trash-2\" class=\"w-4 h-4\"></i>';
|
||||
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
||||
removeBtn.onclick = () => {
|
||||
signState.file = null;
|
||||
signState.pdfDoc = null;
|
||||
@@ -180,20 +173,26 @@ async function setupSignTool() {
|
||||
signState.viewerIframe = iframe;
|
||||
|
||||
const pdfBytes = await readFileAsArrayBuffer(signState.file);
|
||||
const blob = new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
|
||||
const blob = new Blob([new Uint8Array(pdfBytes as ArrayBuffer)], {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
signState.blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
try {
|
||||
const existingPrefsRaw = localStorage.getItem('pdfjs.preferences');
|
||||
const existingPrefs = existingPrefsRaw ? JSON.parse(existingPrefsRaw) : {};
|
||||
delete (existingPrefs as any).annotationEditorMode;
|
||||
const existingPrefs: Record<string, unknown> = existingPrefsRaw
|
||||
? JSON.parse(existingPrefsRaw)
|
||||
: {};
|
||||
delete existingPrefs.annotationEditorMode;
|
||||
const newPrefs = {
|
||||
...existingPrefs,
|
||||
enableSignatureEditor: true,
|
||||
enablePermissions: false,
|
||||
};
|
||||
localStorage.setItem('pdfjs.preferences', JSON.stringify(newPrefs));
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('Failed to update pdfjs.preferences in localStorage', e);
|
||||
}
|
||||
|
||||
const viewerUrl = new URL(
|
||||
`${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html`,
|
||||
@@ -206,7 +205,7 @@ async function setupSignTool() {
|
||||
hideLoader();
|
||||
signState.viewerReady = true;
|
||||
try {
|
||||
const viewerWindow: any = iframe.contentWindow;
|
||||
const viewerWindow = iframe.contentWindow as PDFViewerWindow | null;
|
||||
if (viewerWindow && viewerWindow.PDFViewerApplication) {
|
||||
const app = viewerWindow.PDFViewerApplication;
|
||||
const doc = viewerWindow.document;
|
||||
@@ -235,7 +234,12 @@ async function setupSignTool() {
|
||||
'editorHighlightButton'
|
||||
) as HTMLButtonElement | null;
|
||||
highlightBtn?.click();
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'Failed to auto-click highlight button in PDF viewer',
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -258,7 +262,8 @@ async function applyAndSaveSignatures() {
|
||||
}
|
||||
|
||||
try {
|
||||
const viewerWindow: any = signState.viewerIframe.contentWindow;
|
||||
const viewerWindow = signState.viewerIframe
|
||||
.contentWindow as PDFViewerWindow | null;
|
||||
if (!viewerWindow || !viewerWindow.PDFViewerApplication) {
|
||||
showAlert('Viewer not ready', 'The PDF viewer is still initializing.');
|
||||
return;
|
||||
@@ -277,11 +282,11 @@ async function applyAndSaveSignatures() {
|
||||
app.pdfDocument.annotationStorage
|
||||
);
|
||||
const pdfBytes = new Uint8Array(rawPdfBytes);
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
pdfDoc.getForm().flatten();
|
||||
const flattenedPdfBytes = await pdfDoc.save();
|
||||
|
||||
const blob = new Blob([flattenedPdfBytes as BlobPart], {
|
||||
const blob = new Blob([new Uint8Array(flattenedPdfBytes)], {
|
||||
type: 'application/pdf',
|
||||
});
|
||||
downloadFile(
|
||||
|
||||
@@ -14,6 +14,7 @@ import { isCpdfAvailable } from '../utils/cpdf-helper.js';
|
||||
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
|
||||
import JSZip from 'jszip';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
// @ts-ignore
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
@@ -98,9 +99,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
result.pdf.destroy();
|
||||
state.files[0] = result.file;
|
||||
state.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
state.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
}
|
||||
// Update page count
|
||||
metaSpan.textContent = `${formatBytes(file.size)} • ${state.pdfDoc.getPageCount()} pages`;
|
||||
@@ -148,9 +147,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
result.pdf.destroy();
|
||||
state.files[0] = result.file;
|
||||
state.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
state.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
showLoader('Rendering page previews...');
|
||||
} else {
|
||||
throw new Error('No PDF document loaded');
|
||||
|
||||
@@ -247,7 +247,8 @@ async function convertToPdf() {
|
||||
} catch (error) {
|
||||
console.error(`Failed to process ${file.name}:`, error);
|
||||
throw new Error(
|
||||
`Could not process "${file.name}". The file may be corrupted.`
|
||||
`Could not process "${file.name}". The file may be corrupted.`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import { TextColorState } from '@/types';
|
||||
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
|
||||
import { loadPdfDocument } from '../utils/load-pdf-document.js';
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
@@ -75,9 +76,7 @@ async function handleFiles(files: FileList) {
|
||||
if (!result) return;
|
||||
showLoader('Loading PDF...');
|
||||
result.pdf.destroy();
|
||||
pageState.pdfDoc = await PDFLibDocument.load(result.bytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
pageState.pdfDoc = await loadPdfDocument(result.bytes);
|
||||
pageState.file = result.file;
|
||||
updateFileDisplay();
|
||||
document.getElementById('options-panel')?.classList.remove('hidden');
|
||||
|
||||
@@ -1,238 +1,260 @@
|
||||
import forge from 'node-forge';
|
||||
import { ExtractedSignature, SignatureValidationResult } from '@/types';
|
||||
|
||||
|
||||
export function extractSignatures(pdfBytes: Uint8Array): ExtractedSignature[] {
|
||||
const signatures: ExtractedSignature[] = [];
|
||||
const pdfString = new TextDecoder('latin1').decode(pdfBytes);
|
||||
const signatures: ExtractedSignature[] = [];
|
||||
const pdfString = new TextDecoder('latin1').decode(pdfBytes);
|
||||
|
||||
// Find all signature objects for /Type /Sig
|
||||
const sigRegex = /\/Type\s*\/Sig\b/g;
|
||||
let sigMatch;
|
||||
let sigIndex = 0;
|
||||
// Find all signature objects for /Type /Sig
|
||||
const sigRegex = /\/Type\s*\/Sig\b/g;
|
||||
let sigMatch;
|
||||
let sigIndex = 0;
|
||||
|
||||
while ((sigMatch = sigRegex.exec(pdfString)) !== null) {
|
||||
try {
|
||||
const searchStart = Math.max(0, sigMatch.index - 5000);
|
||||
const searchEnd = Math.min(pdfString.length, sigMatch.index + 10000);
|
||||
const context = pdfString.substring(searchStart, searchEnd);
|
||||
const byteRangeMatch = context.match(/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/);
|
||||
if (!byteRangeMatch) continue;
|
||||
while ((sigMatch = sigRegex.exec(pdfString)) !== null) {
|
||||
try {
|
||||
const searchStart = Math.max(0, sigMatch.index - 5000);
|
||||
const searchEnd = Math.min(pdfString.length, sigMatch.index + 10000);
|
||||
const context = pdfString.substring(searchStart, searchEnd);
|
||||
const byteRangeMatch = context.match(
|
||||
/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/
|
||||
);
|
||||
if (!byteRangeMatch) continue;
|
||||
|
||||
const byteRange = [
|
||||
parseInt(byteRangeMatch[1], 10),
|
||||
parseInt(byteRangeMatch[2], 10),
|
||||
parseInt(byteRangeMatch[3], 10),
|
||||
parseInt(byteRangeMatch[4], 10),
|
||||
];
|
||||
const byteRange = [
|
||||
parseInt(byteRangeMatch[1], 10),
|
||||
parseInt(byteRangeMatch[2], 10),
|
||||
parseInt(byteRangeMatch[3], 10),
|
||||
parseInt(byteRangeMatch[4], 10),
|
||||
];
|
||||
|
||||
const contentsMatch = context.match(/\/Contents\s*<([0-9A-Fa-f]+)>/);
|
||||
if (!contentsMatch) continue;
|
||||
const contentsMatch = context.match(/\/Contents\s*<([0-9A-Fa-f]+)>/);
|
||||
if (!contentsMatch) continue;
|
||||
|
||||
const hexContents = contentsMatch[1];
|
||||
const contentsBytes = hexToBytes(hexContents);
|
||||
const hexContents = contentsMatch[1];
|
||||
const contentsBytes = hexToBytes(hexContents);
|
||||
|
||||
const reasonMatch = context.match(/\/Reason\s*\(([^)]*)\)/);
|
||||
const locationMatch = context.match(/\/Location\s*\(([^)]*)\)/);
|
||||
const contactMatch = context.match(/\/ContactInfo\s*\(([^)]*)\)/);
|
||||
const nameMatch = context.match(/\/Name\s*\(([^)]*)\)/);
|
||||
const timeMatch = context.match(/\/M\s*\(D:(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
|
||||
const reasonMatch = context.match(/\/Reason\s*\(([^)]*)\)/);
|
||||
const locationMatch = context.match(/\/Location\s*\(([^)]*)\)/);
|
||||
const contactMatch = context.match(/\/ContactInfo\s*\(([^)]*)\)/);
|
||||
const nameMatch = context.match(/\/Name\s*\(([^)]*)\)/);
|
||||
const timeMatch = context.match(
|
||||
/\/M\s*\(D:(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/
|
||||
);
|
||||
|
||||
let signingTime: string | undefined;
|
||||
if (timeMatch) {
|
||||
signingTime = `${timeMatch[1]}-${timeMatch[2]}-${timeMatch[3]}T${timeMatch[4]}:${timeMatch[5]}:${timeMatch[6]}`;
|
||||
}
|
||||
let signingTime: string | undefined;
|
||||
if (timeMatch) {
|
||||
signingTime = `${timeMatch[1]}-${timeMatch[2]}-${timeMatch[3]}T${timeMatch[4]}:${timeMatch[5]}:${timeMatch[6]}`;
|
||||
}
|
||||
|
||||
signatures.push({
|
||||
index: sigIndex++,
|
||||
contents: contentsBytes,
|
||||
byteRange,
|
||||
reason: reasonMatch ? decodeURIComponent(escape(reasonMatch[1])) : undefined,
|
||||
location: locationMatch ? decodeURIComponent(escape(locationMatch[1])) : undefined,
|
||||
contactInfo: contactMatch ? decodeURIComponent(escape(contactMatch[1])) : undefined,
|
||||
name: nameMatch ? decodeURIComponent(escape(nameMatch[1])) : undefined,
|
||||
signingTime,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error extracting signature at index', sigIndex, e);
|
||||
}
|
||||
signatures.push({
|
||||
index: sigIndex++,
|
||||
contents: contentsBytes,
|
||||
byteRange,
|
||||
reason: reasonMatch
|
||||
? decodeURIComponent(escape(reasonMatch[1]))
|
||||
: undefined,
|
||||
location: locationMatch
|
||||
? decodeURIComponent(escape(locationMatch[1]))
|
||||
: undefined,
|
||||
contactInfo: contactMatch
|
||||
? decodeURIComponent(escape(contactMatch[1]))
|
||||
: undefined,
|
||||
name: nameMatch ? decodeURIComponent(escape(nameMatch[1])) : undefined,
|
||||
signingTime,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Error extracting signature at index', sigIndex, e);
|
||||
}
|
||||
}
|
||||
|
||||
return signatures;
|
||||
return signatures;
|
||||
}
|
||||
|
||||
export function validateSignature(
|
||||
signature: ExtractedSignature,
|
||||
pdfBytes: Uint8Array,
|
||||
trustedCert?: forge.pki.Certificate
|
||||
signature: ExtractedSignature,
|
||||
pdfBytes: Uint8Array,
|
||||
trustedCert?: forge.pki.Certificate
|
||||
): SignatureValidationResult {
|
||||
const result: SignatureValidationResult = {
|
||||
signatureIndex: signature.index,
|
||||
isValid: false,
|
||||
signerName: 'Unknown',
|
||||
issuer: 'Unknown',
|
||||
validFrom: new Date(0),
|
||||
validTo: new Date(0),
|
||||
isExpired: false,
|
||||
isSelfSigned: false,
|
||||
isTrusted: false,
|
||||
algorithms: { digest: 'Unknown', signature: 'Unknown' },
|
||||
serialNumber: '',
|
||||
byteRange: signature.byteRange,
|
||||
coverageStatus: 'unknown',
|
||||
reason: signature.reason,
|
||||
location: signature.location,
|
||||
contactInfo: signature.contactInfo,
|
||||
};
|
||||
const result: SignatureValidationResult = {
|
||||
signatureIndex: signature.index,
|
||||
isValid: false,
|
||||
signerName: 'Unknown',
|
||||
issuer: 'Unknown',
|
||||
validFrom: new Date(0),
|
||||
validTo: new Date(0),
|
||||
isExpired: false,
|
||||
isSelfSigned: false,
|
||||
isTrusted: false,
|
||||
algorithms: { digest: 'Unknown', signature: 'Unknown' },
|
||||
serialNumber: '',
|
||||
byteRange: signature.byteRange,
|
||||
coverageStatus: 'unknown',
|
||||
reason: signature.reason,
|
||||
location: signature.location,
|
||||
contactInfo: signature.contactInfo,
|
||||
};
|
||||
|
||||
try {
|
||||
const binaryString = String.fromCharCode.apply(null, Array.from(signature.contents));
|
||||
const asn1 = forge.asn1.fromDer(binaryString);
|
||||
const p7 = forge.pkcs7.messageFromAsn1(asn1) as any;
|
||||
try {
|
||||
const binaryString = String.fromCharCode.apply(
|
||||
null,
|
||||
Array.from(signature.contents)
|
||||
);
|
||||
const asn1 = forge.asn1.fromDer(binaryString);
|
||||
const p7 = forge.pkcs7.messageFromAsn1(asn1) as any;
|
||||
|
||||
if (!p7.certificates || p7.certificates.length === 0) {
|
||||
result.errorMessage = 'No certificates found in signature';
|
||||
return result;
|
||||
}
|
||||
|
||||
const signerCert = p7.certificates[0] as forge.pki.Certificate;
|
||||
|
||||
const subjectCN = signerCert.subject.getField('CN');
|
||||
const subjectO = signerCert.subject.getField('O');
|
||||
const subjectE = signerCert.subject.getField('E') || signerCert.subject.getField('emailAddress');
|
||||
const issuerCN = signerCert.issuer.getField('CN');
|
||||
const issuerO = signerCert.issuer.getField('O');
|
||||
|
||||
result.signerName = (subjectCN?.value as string) ?? 'Unknown';
|
||||
result.signerOrg = subjectO?.value as string | undefined;
|
||||
result.signerEmail = subjectE?.value as string | undefined;
|
||||
result.issuer = (issuerCN?.value as string) ?? 'Unknown';
|
||||
result.issuerOrg = issuerO?.value as string | undefined;
|
||||
result.validFrom = signerCert.validity.notBefore;
|
||||
result.validTo = signerCert.validity.notAfter;
|
||||
result.serialNumber = signerCert.serialNumber;
|
||||
|
||||
const now = new Date();
|
||||
result.isExpired = now > result.validTo || now < result.validFrom;
|
||||
|
||||
result.isSelfSigned = signerCert.isIssuer(signerCert);
|
||||
|
||||
// Check trust against provided certificate
|
||||
if (trustedCert) {
|
||||
try {
|
||||
const isTrustedIssuer = trustedCert.isIssuer(signerCert);
|
||||
const isSameCert = signerCert.serialNumber === trustedCert.serialNumber;
|
||||
|
||||
let chainTrusted = false;
|
||||
for (const cert of p7.certificates) {
|
||||
if (trustedCert.isIssuer(cert) ||
|
||||
(cert as forge.pki.Certificate).serialNumber === trustedCert.serialNumber) {
|
||||
chainTrusted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.isTrusted = isTrustedIssuer || isSameCert || chainTrusted;
|
||||
} catch {
|
||||
result.isTrusted = false;
|
||||
}
|
||||
}
|
||||
|
||||
result.algorithms = {
|
||||
digest: getDigestAlgorithmName(signerCert.siginfo?.algorithmOid || ''),
|
||||
signature: getSignatureAlgorithmName(signerCert.signatureOid || ''),
|
||||
};
|
||||
|
||||
// Parse signing time if available in signature
|
||||
if (signature.signingTime) {
|
||||
result.signatureDate = new Date(signature.signingTime);
|
||||
} else {
|
||||
// Try to extract from authenticated attributes
|
||||
try {
|
||||
const signedData = p7 as any;
|
||||
if (signedData.rawCapture?.authenticatedAttributes) {
|
||||
// Look for signing time attribute
|
||||
for (const attr of signedData.rawCapture.authenticatedAttributes) {
|
||||
if (attr.type === forge.pki.oids.signingTime) {
|
||||
result.signatureDate = attr.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
if (signature.byteRange && signature.byteRange.length === 4) {
|
||||
const [start1, len1, start2, len2] = signature.byteRange;
|
||||
const totalCovered = len1 + len2;
|
||||
const expectedEnd = start2 + len2;
|
||||
|
||||
if (expectedEnd === pdfBytes.length) {
|
||||
result.coverageStatus = 'full';
|
||||
} else if (expectedEnd < pdfBytes.length) {
|
||||
result.coverageStatus = 'partial';
|
||||
}
|
||||
}
|
||||
|
||||
result.isValid = true;
|
||||
|
||||
} catch (e) {
|
||||
result.errorMessage = e instanceof Error ? e.message : 'Failed to parse signature';
|
||||
if (!p7.certificates || p7.certificates.length === 0) {
|
||||
result.errorMessage = 'No certificates found in signature';
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
const signerCert = p7.certificates[0] as forge.pki.Certificate;
|
||||
|
||||
const subjectCN = signerCert.subject.getField('CN');
|
||||
const subjectO = signerCert.subject.getField('O');
|
||||
const subjectE =
|
||||
signerCert.subject.getField('E') ||
|
||||
signerCert.subject.getField('emailAddress');
|
||||
const issuerCN = signerCert.issuer.getField('CN');
|
||||
const issuerO = signerCert.issuer.getField('O');
|
||||
|
||||
result.signerName = (subjectCN?.value as string) ?? 'Unknown';
|
||||
result.signerOrg = subjectO?.value as string | undefined;
|
||||
result.signerEmail = subjectE?.value as string | undefined;
|
||||
result.issuer = (issuerCN?.value as string) ?? 'Unknown';
|
||||
result.issuerOrg = issuerO?.value as string | undefined;
|
||||
result.validFrom = signerCert.validity.notBefore;
|
||||
result.validTo = signerCert.validity.notAfter;
|
||||
result.serialNumber = signerCert.serialNumber;
|
||||
|
||||
const now = new Date();
|
||||
result.isExpired = now > result.validTo || now < result.validFrom;
|
||||
|
||||
result.isSelfSigned = signerCert.isIssuer(signerCert);
|
||||
|
||||
// Check trust against provided certificate
|
||||
if (trustedCert) {
|
||||
try {
|
||||
const isTrustedIssuer = trustedCert.isIssuer(signerCert);
|
||||
const isSameCert = signerCert.serialNumber === trustedCert.serialNumber;
|
||||
|
||||
let chainTrusted = false;
|
||||
for (const cert of p7.certificates) {
|
||||
if (
|
||||
trustedCert.isIssuer(cert) ||
|
||||
(cert as forge.pki.Certificate).serialNumber ===
|
||||
trustedCert.serialNumber
|
||||
) {
|
||||
chainTrusted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.isTrusted = isTrustedIssuer || isSameCert || chainTrusted;
|
||||
} catch {
|
||||
result.isTrusted = false;
|
||||
}
|
||||
}
|
||||
|
||||
result.algorithms = {
|
||||
digest: getDigestAlgorithmName(signerCert.siginfo?.algorithmOid || ''),
|
||||
signature: getSignatureAlgorithmName(signerCert.signatureOid || ''),
|
||||
};
|
||||
|
||||
// Parse signing time if available in signature
|
||||
if (signature.signingTime) {
|
||||
result.signatureDate = new Date(signature.signingTime);
|
||||
} else {
|
||||
// Try to extract from authenticated attributes
|
||||
try {
|
||||
const signedData = p7 as any;
|
||||
if (signedData.rawCapture?.authenticatedAttributes) {
|
||||
// Look for signing time attribute
|
||||
for (const attr of signedData.rawCapture.authenticatedAttributes) {
|
||||
if (attr.type === forge.pki.oids.signingTime) {
|
||||
result.signatureDate = attr.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'Failed to extract signing time from authenticated attributes',
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (signature.byteRange && signature.byteRange.length === 4) {
|
||||
const [start1, len1, start2, len2] = signature.byteRange;
|
||||
const totalCovered = len1 + len2;
|
||||
const expectedEnd = start2 + len2;
|
||||
|
||||
if (expectedEnd === pdfBytes.length) {
|
||||
result.coverageStatus = 'full';
|
||||
} else if (expectedEnd < pdfBytes.length) {
|
||||
result.coverageStatus = 'partial';
|
||||
}
|
||||
}
|
||||
|
||||
result.isValid = true;
|
||||
} catch (e) {
|
||||
result.errorMessage =
|
||||
e instanceof Error ? e.message : 'Failed to parse signature';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function validatePdfSignatures(
|
||||
pdfBytes: Uint8Array,
|
||||
trustedCert?: forge.pki.Certificate
|
||||
pdfBytes: Uint8Array,
|
||||
trustedCert?: forge.pki.Certificate
|
||||
): Promise<SignatureValidationResult[]> {
|
||||
const signatures = extractSignatures(pdfBytes);
|
||||
return signatures.map(sig => validateSignature(sig, pdfBytes, trustedCert));
|
||||
const signatures = extractSignatures(pdfBytes);
|
||||
return signatures.map((sig) => validateSignature(sig, pdfBytes, trustedCert));
|
||||
}
|
||||
|
||||
export function countSignatures(pdfBytes: Uint8Array): number {
|
||||
return extractSignatures(pdfBytes).length;
|
||||
return extractSignatures(pdfBytes).length;
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
const bytes = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
||||
}
|
||||
const bytes = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
|
||||
}
|
||||
|
||||
let actualLength = bytes.length;
|
||||
while (actualLength > 0 && bytes[actualLength - 1] === 0) {
|
||||
actualLength--;
|
||||
}
|
||||
let actualLength = bytes.length;
|
||||
while (actualLength > 0 && bytes[actualLength - 1] === 0) {
|
||||
actualLength--;
|
||||
}
|
||||
|
||||
return bytes.slice(0, actualLength);
|
||||
return bytes.slice(0, actualLength);
|
||||
}
|
||||
|
||||
function getDigestAlgorithmName(oid: string): string {
|
||||
const digestAlgorithms: Record<string, string> = {
|
||||
'1.2.840.113549.2.5': 'MD5',
|
||||
'1.3.14.3.2.26': 'SHA-1',
|
||||
'2.16.840.1.101.3.4.2.1': 'SHA-256',
|
||||
'2.16.840.1.101.3.4.2.2': 'SHA-384',
|
||||
'2.16.840.1.101.3.4.2.3': 'SHA-512',
|
||||
'2.16.840.1.101.3.4.2.4': 'SHA-224',
|
||||
};
|
||||
return digestAlgorithms[oid] || oid || 'Unknown';
|
||||
const digestAlgorithms: Record<string, string> = {
|
||||
'1.2.840.113549.2.5': 'MD5',
|
||||
'1.3.14.3.2.26': 'SHA-1',
|
||||
'2.16.840.1.101.3.4.2.1': 'SHA-256',
|
||||
'2.16.840.1.101.3.4.2.2': 'SHA-384',
|
||||
'2.16.840.1.101.3.4.2.3': 'SHA-512',
|
||||
'2.16.840.1.101.3.4.2.4': 'SHA-224',
|
||||
};
|
||||
return digestAlgorithms[oid] || oid || 'Unknown';
|
||||
}
|
||||
|
||||
function getSignatureAlgorithmName(oid: string): string {
|
||||
const signatureAlgorithms: Record<string, string> = {
|
||||
'1.2.840.113549.1.1.1': 'RSA',
|
||||
'1.2.840.113549.1.1.5': 'RSA with SHA-1',
|
||||
'1.2.840.113549.1.1.11': 'RSA with SHA-256',
|
||||
'1.2.840.113549.1.1.12': 'RSA with SHA-384',
|
||||
'1.2.840.113549.1.1.13': 'RSA with SHA-512',
|
||||
'1.2.840.10045.2.1': 'ECDSA',
|
||||
'1.2.840.10045.4.1': 'ECDSA with SHA-1',
|
||||
'1.2.840.10045.4.3.2': 'ECDSA with SHA-256',
|
||||
'1.2.840.10045.4.3.3': 'ECDSA with SHA-384',
|
||||
'1.2.840.10045.4.3.4': 'ECDSA with SHA-512',
|
||||
};
|
||||
return signatureAlgorithms[oid] || oid || 'Unknown';
|
||||
const signatureAlgorithms: Record<string, string> = {
|
||||
'1.2.840.113549.1.1.1': 'RSA',
|
||||
'1.2.840.113549.1.1.5': 'RSA with SHA-1',
|
||||
'1.2.840.113549.1.1.11': 'RSA with SHA-256',
|
||||
'1.2.840.113549.1.1.12': 'RSA with SHA-384',
|
||||
'1.2.840.113549.1.1.13': 'RSA with SHA-512',
|
||||
'1.2.840.10045.2.1': 'ECDSA',
|
||||
'1.2.840.10045.4.1': 'ECDSA with SHA-1',
|
||||
'1.2.840.10045.4.3.2': 'ECDSA with SHA-256',
|
||||
'1.2.840.10045.4.3.3': 'ECDSA with SHA-384',
|
||||
'1.2.840.10045.4.3.4': 'ECDSA with SHA-512',
|
||||
};
|
||||
return signatureAlgorithms[oid] || oid || 'Unknown';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
export interface PageDimensionsState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFDocument | null;
|
||||
file: File | null;
|
||||
pdfDoc: PDFDocument | null;
|
||||
}
|
||||
|
||||
export interface AnalyzedPageData {
|
||||
pageNum: number;
|
||||
width: number;
|
||||
height: number;
|
||||
orientation: string;
|
||||
standardSize: string;
|
||||
rotation: number;
|
||||
}
|
||||
|
||||
export interface UniqueSizeEntry {
|
||||
count: number;
|
||||
label: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
export interface SignPdfState {
|
||||
file: File | null;
|
||||
pdfBytes: ArrayBuffer | null;
|
||||
signatureData: string | null;
|
||||
file: File | null;
|
||||
pdfBytes: ArrayBuffer | null;
|
||||
signatureData: string | null;
|
||||
}
|
||||
|
||||
export interface PDFViewerEventBus {
|
||||
_on: (event: string, callback: () => void) => void;
|
||||
dispatch: (event: string, data: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
export interface PDFViewerApplication {
|
||||
eventBus?: PDFViewerEventBus;
|
||||
pdfDocument?: {
|
||||
saveDocument: (storage: unknown) => Promise<ArrayBuffer>;
|
||||
annotationStorage: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PDFViewerWindow extends Window {
|
||||
PDFViewerApplication?: PDFViewerApplication;
|
||||
}
|
||||
|
||||
export interface SignState {
|
||||
file: File | null;
|
||||
pdfDoc: PDFDocument | null;
|
||||
viewerIframe: HTMLIFrameElement | null;
|
||||
viewerReady: boolean;
|
||||
blobUrl: string | null;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,9 @@ export async function performCondenseCompression(
|
||||
return { ...result, usedFallback: true };
|
||||
} catch (fallbackError: any) {
|
||||
const msg = fallbackError?.message || String(fallbackError);
|
||||
throw new Error(`PDF compression failed: ${msg}`);
|
||||
throw new Error(`PDF compression failed: ${msg}`, {
|
||||
cause: fallbackError,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ export async function loadGhostscript(): Promise<GhostscriptInterface> {
|
||||
} catch (error: any) {
|
||||
loadPromise = null;
|
||||
throw new Error(
|
||||
`Failed to load Ghostscript from ${normalizedUrl}: ${error.message}`
|
||||
`Failed to load Ghostscript from ${normalizedUrl}: ${error.message}`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
* Requires user to configure Ghostscript URL in WASM Settings.
|
||||
*/
|
||||
|
||||
import {
|
||||
getWasmBaseUrl,
|
||||
fetchWasmFile,
|
||||
isWasmAvailable,
|
||||
} from '../config/wasm-cdn-config.js';
|
||||
import { PDFDocument, PDFDict, PDFName, PDFArray } from 'pdf-lib';
|
||||
import { getWasmBaseUrl, isWasmAvailable } from '../config/wasm-cdn-config.js';
|
||||
import { PDFDict, PDFName, PDFArray } from 'pdf-lib';
|
||||
import { loadPdfDocument } from './load-pdf-document.js';
|
||||
|
||||
interface GhostscriptModule {
|
||||
FS: {
|
||||
@@ -160,7 +157,9 @@ export async function convertToPdfA(
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('[Ghostscript] Failed to setup PDF/A assets:', e);
|
||||
throw new Error('Conversion failed: could not create PDF/A definition');
|
||||
throw new Error('Conversion failed: could not create PDF/A definition', {
|
||||
cause: e,
|
||||
});
|
||||
}
|
||||
|
||||
const args = [
|
||||
@@ -200,7 +199,7 @@ export async function convertToPdfA(
|
||||
exitCode = gs.callMain(args);
|
||||
} catch (e) {
|
||||
console.error('[Ghostscript] Exception:', e);
|
||||
throw new Error(`Ghostscript threw an exception: ${e}`);
|
||||
throw new Error(`Ghostscript threw an exception: ${e}`, { cause: e });
|
||||
}
|
||||
|
||||
console.log('[Ghostscript] Exit code:', exitCode);
|
||||
@@ -208,23 +207,23 @@ export async function convertToPdfA(
|
||||
if (exitCode !== 0) {
|
||||
try {
|
||||
gs.FS.unlink(inputPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(outputPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(iccPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(pdfaDefPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
throw new Error(`Ghostscript conversion failed with exit code ${exitCode}`);
|
||||
}
|
||||
@@ -237,29 +236,29 @@ export async function convertToPdfA(
|
||||
output = gs.FS.readFile(outputPath);
|
||||
} catch (e) {
|
||||
console.error('[Ghostscript] Failed to read output:', e);
|
||||
throw new Error('Ghostscript did not produce output file');
|
||||
throw new Error('Ghostscript did not produce output file', { cause: e });
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
try {
|
||||
gs.FS.unlink(inputPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(outputPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(iccPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(pdfaDefPath);
|
||||
} catch {
|
||||
/* ignore */
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
|
||||
if (level !== 'PDF/A-1b') {
|
||||
@@ -282,8 +281,7 @@ export async function convertToPdfA(
|
||||
async function addPageGroupDictionaries(
|
||||
pdfData: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
const pdfDoc = await PDFDocument.load(pdfData, {
|
||||
ignoreEncryption: true,
|
||||
const pdfDoc = await loadPdfDocument(pdfData, {
|
||||
updateMetadata: false,
|
||||
});
|
||||
|
||||
@@ -352,7 +350,7 @@ async function addPageGroupDictionaries(
|
||||
}
|
||||
|
||||
if (iccProfileRef) {
|
||||
pdfDoc.context.enumerateIndirectObjects().forEach(([ref, obj]) => {
|
||||
pdfDoc.context.enumerateIndirectObjects().forEach(([_ref, obj]) => {
|
||||
if (
|
||||
obj instanceof PDFDict ||
|
||||
(obj && typeof obj === 'object' && 'dict' in obj)
|
||||
@@ -437,17 +435,23 @@ export async function convertFontsToOutlines(
|
||||
} catch (e) {
|
||||
try {
|
||||
gs.FS.unlink(inputPath);
|
||||
} catch {}
|
||||
throw new Error(`Ghostscript threw an exception: ${e}`);
|
||||
} catch (e2) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e2);
|
||||
}
|
||||
throw new Error(`Ghostscript threw an exception: ${e}`, { cause: e });
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
try {
|
||||
gs.FS.unlink(inputPath);
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(outputPath);
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
throw new Error(`Ghostscript conversion failed with exit code ${exitCode}`);
|
||||
}
|
||||
|
||||
@@ -455,15 +459,19 @@ export async function convertFontsToOutlines(
|
||||
try {
|
||||
output = gs.FS.readFile(outputPath);
|
||||
} catch (e) {
|
||||
throw new Error('Ghostscript did not produce output file');
|
||||
throw new Error('Ghostscript did not produce output file', { cause: e });
|
||||
}
|
||||
|
||||
try {
|
||||
gs.FS.unlink(inputPath);
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
try {
|
||||
gs.FS.unlink(outputPath);
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
console.warn('[Ghostscript] Failed to clean up temp file:', e);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
14
src/js/utils/load-pdf-document.ts
Normal file
14
src/js/utils/load-pdf-document.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
|
||||
type LoadOptions = Parameters<typeof PDFDocument.load>[1];
|
||||
type PDFDocumentInstance = Awaited<ReturnType<typeof PDFDocument.load>>;
|
||||
|
||||
export async function loadPdfDocument(
|
||||
pdf: Uint8Array | ArrayBuffer,
|
||||
options?: LoadOptions
|
||||
): Promise<PDFDocumentInstance> {
|
||||
return PDFDocument.load(pdf, {
|
||||
ignoreEncryption: true,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { PDFDocument, degrees, rgb, StandardFonts, PageSizes } from 'pdf-lib';
|
||||
import { loadPdfDocument } from './load-pdf-document.js';
|
||||
|
||||
export async function mergePdfs(
|
||||
pdfBytesList: Uint8Array[]
|
||||
): Promise<Uint8Array> {
|
||||
const mergedDoc = await PDFDocument.create();
|
||||
for (const bytes of pdfBytesList) {
|
||||
const srcDoc = await PDFDocument.load(bytes);
|
||||
const srcDoc = await loadPdfDocument(bytes);
|
||||
const copiedPages = await mergedDoc.copyPages(
|
||||
srcDoc,
|
||||
srcDoc.getPageIndices()
|
||||
@@ -19,7 +20,7 @@ export async function splitPdf(
|
||||
pdfBytes: Uint8Array,
|
||||
pageIndices: number[]
|
||||
): Promise<Uint8Array> {
|
||||
const srcDoc = await PDFDocument.load(pdfBytes);
|
||||
const srcDoc = await loadPdfDocument(pdfBytes);
|
||||
const newPdf = await PDFDocument.create();
|
||||
const copiedPages = await newPdf.copyPages(srcDoc, pageIndices);
|
||||
copiedPages.forEach((page) => newPdf.addPage(page));
|
||||
@@ -30,7 +31,7 @@ export async function rotatePdfUniform(
|
||||
pdfBytes: Uint8Array,
|
||||
angle: number
|
||||
): Promise<Uint8Array> {
|
||||
const srcDoc = await PDFDocument.load(pdfBytes);
|
||||
const srcDoc = await loadPdfDocument(pdfBytes);
|
||||
const newPdfDoc = await PDFDocument.create();
|
||||
const pageCount = srcDoc.getPageCount();
|
||||
|
||||
@@ -75,7 +76,7 @@ export async function rotatePdfPages(
|
||||
pdfBytes: Uint8Array,
|
||||
rotations: number[]
|
||||
): Promise<Uint8Array> {
|
||||
const srcDoc = await PDFDocument.load(pdfBytes);
|
||||
const srcDoc = await loadPdfDocument(pdfBytes);
|
||||
const newPdfDoc = await PDFDocument.create();
|
||||
const pageCount = srcDoc.getPageCount();
|
||||
|
||||
@@ -121,7 +122,7 @@ export async function deletePdfPages(
|
||||
pdfBytes: Uint8Array,
|
||||
pagesToDelete: Set<number>
|
||||
): Promise<Uint8Array> {
|
||||
const srcDoc = await PDFDocument.load(pdfBytes);
|
||||
const srcDoc = await loadPdfDocument(pdfBytes);
|
||||
const totalPages = srcDoc.getPageCount();
|
||||
|
||||
const pagesToKeep: number[] = [];
|
||||
@@ -196,7 +197,7 @@ export async function addTextWatermark(
|
||||
pdfBytes: Uint8Array,
|
||||
options: TextWatermarkOptions
|
||||
): Promise<Uint8Array> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -274,7 +275,7 @@ export async function addImageWatermark(
|
||||
pdfBytes: Uint8Array,
|
||||
options: ImageWatermarkOptions
|
||||
): Promise<Uint8Array> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
const image =
|
||||
options.imageType === 'png'
|
||||
? await pdfDoc.embedPng(options.imageBytes)
|
||||
@@ -330,7 +331,7 @@ export async function addPageNumbers(
|
||||
pdfBytes: Uint8Array,
|
||||
options: PageNumberOptions
|
||||
): Promise<Uint8Array> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const pages = pdfDoc.getPages();
|
||||
const totalPages = pages.length;
|
||||
@@ -481,7 +482,7 @@ export async function fixPageSize(
|
||||
[targetWidth, targetHeight] = [targetHeight, targetWidth];
|
||||
}
|
||||
|
||||
const sourceDoc = await PDFDocument.load(pdfBytes);
|
||||
const sourceDoc = await loadPdfDocument(pdfBytes);
|
||||
const outputDoc = await PDFDocument.create();
|
||||
|
||||
for (const sourcePage of sourceDoc.getPages()) {
|
||||
|
||||
@@ -67,7 +67,9 @@ export async function loadPyMuPDF(): Promise<any> {
|
||||
return cachedPyMuPDF;
|
||||
} catch (error: any) {
|
||||
loadPromise = null;
|
||||
throw new Error(`Failed to load PyMuPDF from CDN: ${error.message}`);
|
||||
throw new Error(`Failed to load PyMuPDF from CDN: ${error.message}`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { loadPdfDocument } from './load-pdf-document.js';
|
||||
|
||||
export interface SanitizeOptions {
|
||||
flattenForms: boolean;
|
||||
@@ -503,7 +504,7 @@ export async function sanitizePdf(
|
||||
pdfBytes: Uint8Array,
|
||||
options: SanitizeOptions
|
||||
): Promise<{ pdfDoc: PDFDocument; bytes: Uint8Array }> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBytes);
|
||||
const pdfDoc = await loadPdfDocument(pdfBytes);
|
||||
|
||||
if (options.flattenForms) {
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class AddBlankPageNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -47,7 +47,7 @@ export class AddBlankPageNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
const firstPage = pdfDoc.getPages()[0];
|
||||
const { width, height } = firstPage
|
||||
? firstPage.getSize()
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class BackgroundColorNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -34,7 +35,7 @@ export class BackgroundColorNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const newDoc = await PDFDocument.create();
|
||||
|
||||
for (let i = 0; i < srcDoc.getPageCount(); i++) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, PageSizes } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
const paperSizeLookup: Record<string, [number, number]> = {
|
||||
Letter: PageSizes.Letter,
|
||||
@@ -88,7 +89,7 @@ export class BookletNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const sourceDoc = await PDFDocument.load(input.bytes);
|
||||
const sourceDoc = await loadPdfDocument(input.bytes);
|
||||
const totalPages = sourceDoc.getPageCount();
|
||||
const pagesPerSheet = rows * cols;
|
||||
const outputDoc = await PDFDocument.create();
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class CbzToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -55,7 +55,7 @@ export class CbzToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const blob = await pymupdf.convertToPdf(file, { filetype: 'cbz' });
|
||||
const bytes = new Uint8Array(await blob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -3,13 +3,13 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import {
|
||||
performCondenseCompression,
|
||||
performPhotonCompression,
|
||||
} from '../../utils/compress.js';
|
||||
import type { CondenseCustomSettings } from '../../utils/compress.js';
|
||||
import { isPyMuPDFAvailable } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class CompressNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -115,7 +115,7 @@ export class CompressNode extends BaseWorkflowNode {
|
||||
pdfBytes = await performPhotonCompression(arrayBuffer, level);
|
||||
}
|
||||
|
||||
const document = await PDFDocument.load(pdfBytes);
|
||||
const document = await loadPdfDocument(pdfBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class CropNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -51,7 +51,7 @@ export class CropNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
const pages = pdfDoc.getPages();
|
||||
|
||||
for (const page of pages) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { decryptPdfBytes } from '../../utils/pdf-decrypt.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class DecryptNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -37,7 +37,7 @@ export class DecryptNode extends BaseWorkflowNode {
|
||||
input.bytes,
|
||||
password
|
||||
);
|
||||
const document = await PDFDocument.load(resultBytes, {
|
||||
const document = await loadPdfDocument(resultBytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { deletePdfPages, parseDeletePages } from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class DeletePagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -32,11 +32,11 @@ export class DeletePagesNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const totalPages = srcDoc.getPageCount();
|
||||
const pagesToDelete = parseDeletePages(deleteStr, totalPages);
|
||||
const resultBytes = await deletePdfPages(input.bytes, pagesToDelete);
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
return {
|
||||
type: 'pdf',
|
||||
document: resultDoc,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class DeskewNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -51,7 +51,7 @@ export class DeskewNode extends BaseWorkflowNode {
|
||||
});
|
||||
|
||||
const bytes = new Uint8Array(await resultPdf.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,13 +3,13 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import {
|
||||
signPdf,
|
||||
parsePfxFile,
|
||||
parseCombinedPem,
|
||||
} from '../../logic/digital-sign-pdf.js';
|
||||
import type { CertificateData } from '@/types';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class DigitalSignNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -117,7 +117,7 @@ export class DigitalSignNode extends BaseWorkflowNode {
|
||||
});
|
||||
|
||||
const bytes = new Uint8Array(signedBytes);
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { parsePageRange } from '../../utils/pdf-operations';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class DividePagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -42,7 +43,7 @@ export class DividePagesNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const newDoc = await PDFDocument.create();
|
||||
const totalPages = srcDoc.getPageCount();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class EditMetadataNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -61,7 +61,7 @@ export class EditMetadataNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
|
||||
if (title) pdfDoc.setTitle(title);
|
||||
if (author) pdfDoc.setAuthor(author);
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { parseEmailFile, renderEmailToHtml } from '../../logic/email-to-pdf.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class EmailToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -87,7 +87,11 @@ export class EmailToPdfNode extends BaseWorkflowNode {
|
||||
pageSize,
|
||||
});
|
||||
|
||||
const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, {
|
||||
const pdfBlob = await (
|
||||
pymupdf as unknown as {
|
||||
htmlToPdf(html: string, options: unknown): Promise<Blob>;
|
||||
}
|
||||
).htmlToPdf(htmlContent, {
|
||||
pageSize,
|
||||
margins: { top: 50, right: 50, bottom: 50, left: 50 },
|
||||
attachments: email.attachments
|
||||
@@ -99,7 +103,7 @@ export class EmailToPdfNode extends BaseWorkflowNode {
|
||||
});
|
||||
|
||||
const bytes = new Uint8Array(await pdfBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { initializeQpdf } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class EncryptNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -90,9 +90,7 @@ export class EncryptNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
const resultBytes = new Uint8Array(encryptedData);
|
||||
const document = await PDFDocument.load(resultBytes, {
|
||||
ignoreEncryption: true,
|
||||
});
|
||||
const document = await loadPdfDocument(resultBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class EpubToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -54,7 +54,7 @@ export class EpubToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const blob = await pymupdf.convertToPdf(file, { filetype: 'epub' });
|
||||
const bytes = new Uint8Array(await blob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class ExcelToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -62,7 +62,7 @@ export class ExcelToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const resultBlob = await converter.convertToPdf(file);
|
||||
const bytes = new Uint8Array(await resultBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData, MultiPDFData } from '../types';
|
||||
import { requirePdfInput, extractAllPdfs } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { parsePageRange } from '../../utils/pdf-operations';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class ExtractPagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -37,7 +38,7 @@ export class ExtractPagesNode extends BaseWorkflowNode {
|
||||
|
||||
const allItems = [];
|
||||
for (const input of allPdfs) {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const totalPages = srcDoc.getPageCount();
|
||||
const indices = parsePageRange(rangeStr, totalPages);
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class Fb2ToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -54,7 +54,7 @@ export class Fb2ToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const blob = await pymupdf.convertToPdf(file, { filetype: 'fb2' });
|
||||
const bytes = new Uint8Array(await blob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -4,8 +4,8 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { fixPageSize } from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class FixPageSizeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -90,7 +90,7 @@ export class FixPageSizeNode extends BaseWorkflowNode {
|
||||
customUnits,
|
||||
});
|
||||
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { flattenAnnotations } from '../../utils/flatten-annotations.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class FlattenNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -24,7 +24,7 @@ export class FlattenNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
|
||||
try {
|
||||
const form = pdfDoc.getForm();
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { convertFontsToOutlines } from '../../utils/ghostscript-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class FontToOutlineNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -28,7 +28,7 @@ export class FontToOutlineNode extends BaseWorkflowNode {
|
||||
new Uint8Array(input.bytes)
|
||||
);
|
||||
const bytes = new Uint8Array(resultBytes);
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,8 +3,9 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { StandardFonts, rgb } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class HeaderFooterNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -89,7 +90,7 @@ export class HeaderFooterNode extends BaseWorkflowNode {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
if (!hasAny) return input;
|
||||
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const pages = pdfDoc.getPages();
|
||||
const totalPages = pages.length;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class ImageInputNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -57,7 +57,7 @@ export class ImageInputNode extends BaseWorkflowNode {
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
const pdfBlob = await pymupdf.imagesToPdf(this.files);
|
||||
const bytes = new Uint8Array(await pdfBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
const result: PDFData = {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class JsonToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -65,7 +65,7 @@ export class JsonToPdfNode extends BaseWorkflowNode {
|
||||
pageSize: 'a4',
|
||||
});
|
||||
const bytes = new Uint8Array(await pdfBlob.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(bytes);
|
||||
const pdfDoc = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document: pdfDoc,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { initializeQpdf } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class LinearizeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -50,7 +50,7 @@ export class LinearizeNode extends BaseWorkflowNode {
|
||||
}
|
||||
}
|
||||
|
||||
const document = await PDFDocument.load(resultBytes);
|
||||
const document = await loadPdfDocument(resultBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
|
||||
|
||||
@@ -65,11 +65,15 @@ export class MarkdownToPdfNode extends BaseWorkflowNode {
|
||||
blockquote { border-left: 3px solid #ccc; margin-left: 0; padding-left: 12px; color: #555; }
|
||||
table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ccc; padding: 6px 12px; }
|
||||
</style></head><body>${md.render(textContent)}</body></html>`;
|
||||
const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, {
|
||||
const pdfBlob = await (
|
||||
pymupdf as unknown as {
|
||||
htmlToPdf(html: string, options: unknown): Promise<Blob>;
|
||||
}
|
||||
).htmlToPdf(htmlContent, {
|
||||
pageSize: 'a4',
|
||||
});
|
||||
const bytes = new Uint8Array(await pdfBlob.arrayBuffer());
|
||||
const pdfDoc = await PDFDocument.load(bytes);
|
||||
const pdfDoc = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document: pdfDoc,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { extractAllPdfs } from '../types';
|
||||
import { mergePdfs } from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class MergeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -26,7 +26,7 @@ export class MergeNode extends BaseWorkflowNode {
|
||||
throw new Error('No PDFs connected to Merge node');
|
||||
|
||||
const mergedBytes = await mergePdfs(allPdfs.map((p) => p.bytes));
|
||||
const mergedDoc = await PDFDocument.load(mergedBytes);
|
||||
const mergedDoc = await loadPdfDocument(mergedBytes);
|
||||
|
||||
return {
|
||||
pdf: {
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class MobiToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -54,7 +54,7 @@ export class MobiToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const blob = await pymupdf.convertToPdf(file, { filetype: 'mobi' });
|
||||
const bytes = new Uint8Array(await blob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class NUpNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -73,7 +74,7 @@ export class NUpNode extends BaseWorkflowNode {
|
||||
};
|
||||
const [cols, rows] = gridDims[n];
|
||||
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const newDoc = await PDFDocument.create();
|
||||
const pageCount = srcDoc.getPageCount();
|
||||
const firstPage = srcDoc.getPages()[0];
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class OdgToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -56,7 +56,7 @@ export class OdgToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const resultBlob = await converter.convertToPdf(file);
|
||||
const bytes = new Uint8Array(await resultBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
type PageNumberPosition,
|
||||
type PageNumberFormat,
|
||||
} from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class PageNumbersNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -68,7 +68,7 @@ export class PageNumbersNode extends BaseWorkflowNode {
|
||||
color: { r: c.r, g: c.g, b: c.b },
|
||||
});
|
||||
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class PagesToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -56,7 +56,7 @@ export class PagesToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const resultBlob = await converter.convertToPdf(file);
|
||||
const bytes = new Uint8Array(await resultBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { readFileAsArrayBuffer } from '../../utils/helpers.js';
|
||||
import { decryptPdfBytes } from '../../utils/pdf-decrypt.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class EncryptedPDFError extends Error {
|
||||
constructor(public readonly filename: string) {
|
||||
@@ -31,15 +31,14 @@ export class PDFInputNode extends BaseWorkflowNode {
|
||||
|
||||
let isEncrypted = false;
|
||||
try {
|
||||
await PDFDocument.load(bytes, { throwOnInvalidObject: false });
|
||||
await loadPdfDocument(bytes, { throwOnInvalidObject: false });
|
||||
} catch {
|
||||
isEncrypted = true;
|
||||
}
|
||||
|
||||
if (isEncrypted) {
|
||||
try {
|
||||
await PDFDocument.load(bytes, {
|
||||
ignoreEncryption: true,
|
||||
await loadPdfDocument(bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
} catch {
|
||||
@@ -50,7 +49,7 @@ export class PDFInputNode extends BaseWorkflowNode {
|
||||
throw new EncryptedPDFError(file.name);
|
||||
}
|
||||
|
||||
const document = await PDFDocument.load(bytes, {
|
||||
const document = await loadPdfDocument(bytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
this.files.push({
|
||||
@@ -65,7 +64,7 @@ export class PDFInputNode extends BaseWorkflowNode {
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const bytes = new Uint8Array(arrayBuffer as ArrayBuffer);
|
||||
const { bytes: decryptedBytes } = await decryptPdfBytes(bytes, password);
|
||||
const document = await PDFDocument.load(decryptedBytes, {
|
||||
const document = await loadPdfDocument(decryptedBytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
this.files.push({
|
||||
|
||||
@@ -43,7 +43,7 @@ export class PdfToCsvNode extends BaseWorkflowNode {
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
const page = doc.getPage(i);
|
||||
const tables = page.findTables();
|
||||
tables.forEach((table: any) => {
|
||||
tables.forEach((table: { rows: (string | null)[][] }) => {
|
||||
allRows.push(...table.rows);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { SocketData, PDFData } from '../types';
|
||||
import { requirePdfInput, extractAllPdfs } from '../types';
|
||||
import { downloadFile } from '../../utils/helpers.js';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import type JSZip from 'jszip';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
|
||||
export class PdfToImagesNode extends BaseWorkflowNode {
|
||||
@@ -31,7 +32,7 @@ export class PdfToImagesNode extends BaseWorkflowNode {
|
||||
|
||||
private async addPdfPages(
|
||||
pdf: PDFData,
|
||||
zip: any,
|
||||
zip: JSZip,
|
||||
format: string,
|
||||
mimeType: string,
|
||||
quality: number,
|
||||
@@ -59,7 +60,10 @@ export class PdfToImagesNode extends BaseWorkflowNode {
|
||||
}
|
||||
}
|
||||
|
||||
private async addPdfPagesAsSvg(allPdfs: PDFData[], zip: any): Promise<void> {
|
||||
private async addPdfPagesAsSvg(
|
||||
allPdfs: PDFData[],
|
||||
zip: JSZip
|
||||
): Promise<void> {
|
||||
const pymupdf = await loadPyMuPDF();
|
||||
for (const pdf of allPdfs) {
|
||||
const blob = new Blob([new Uint8Array(pdf.bytes)], {
|
||||
|
||||
@@ -3,9 +3,9 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadGhostscript } from '../../utils/ghostscript-dynamic-loader.js';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class PdfToPdfANode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -68,7 +68,7 @@ export class PdfToPdfANode extends BaseWorkflowNode {
|
||||
);
|
||||
|
||||
const bytes = new Uint8Array(resultBuffer);
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -36,7 +36,7 @@ export class PdfToXlsxNode extends BaseWorkflowNode {
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
const page = doc.getPage(i);
|
||||
const tables = page.findTables();
|
||||
tables.forEach((table: any) => {
|
||||
tables.forEach((table: { rows: (string | null)[][] }) => {
|
||||
allTables.push({ page: i + 1, rows: table.rows });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class PowerPointToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -61,7 +61,7 @@ export class PowerPointToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const resultBlob = await converter.convertToPdf(file);
|
||||
const bytes = new Uint8Array(await resultBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ClassicPreset } from 'rete';
|
||||
import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { PDFData, SocketData, MultiPDFData } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { getLibreOfficeConverter } from '../../utils/libreoffice-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class PubToPdfNode extends BaseWorkflowNode {
|
||||
readonly category = 'Input' as const;
|
||||
@@ -56,7 +56,7 @@ export class PubToPdfNode extends BaseWorkflowNode {
|
||||
for (const file of this.files) {
|
||||
const resultBlob = await converter.convertToPdf(file);
|
||||
const bytes = new Uint8Array(await resultBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
results.push({
|
||||
type: 'pdf',
|
||||
document,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RasterizeNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -65,7 +65,7 @@ export class RasterizeNode extends BaseWorkflowNode {
|
||||
});
|
||||
|
||||
const bytes = new Uint8Array(await rasterizedBlob.arrayBuffer());
|
||||
const document = await PDFDocument.load(bytes);
|
||||
const document = await loadPdfDocument(bytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,9 +3,9 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPyMuPDF } from '../../utils/pymupdf-loader.js';
|
||||
import { hexToRgb } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RedactNode extends BaseWorkflowNode {
|
||||
readonly category = 'Secure PDF' as const;
|
||||
@@ -106,7 +106,7 @@ export class RedactNode extends BaseWorkflowNode {
|
||||
const resultBytes = new Uint8Array(doc.save());
|
||||
doc.close();
|
||||
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
|
||||
return {
|
||||
type: 'pdf',
|
||||
|
||||
@@ -3,7 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument, PDFName } from 'pdf-lib';
|
||||
import { PDFName } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RemoveAnnotationsNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -23,7 +24,7 @@ export class RemoveAnnotationsNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfDoc = await PDFDocument.load(input.bytes);
|
||||
const pdfDoc = await loadPdfDocument(input.bytes);
|
||||
const pages = pdfDoc.getPages();
|
||||
|
||||
for (const page of pages) {
|
||||
|
||||
@@ -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 { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RemoveBlankPagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Edit & Annotate' as const;
|
||||
@@ -61,7 +62,7 @@ export class RemoveBlankPagesNode extends BaseWorkflowNode {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const pdfjsDoc = await pdfjsLib.getDocument({ data: input.bytes })
|
||||
.promise;
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const nonBlankIndices: number[] = [];
|
||||
|
||||
for (let i = 1; i <= pdfjsDoc.numPages; i++) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { BaseWorkflowNode } from './base-node';
|
||||
import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { initializeQpdf } from '../../utils/helpers.js';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RepairNode extends BaseWorkflowNode {
|
||||
readonly category = 'Optimize & Repair' as const;
|
||||
@@ -49,8 +49,7 @@ export class RepairNode extends BaseWorkflowNode {
|
||||
}
|
||||
|
||||
const resultBytes = new Uint8Array(repairedData);
|
||||
const resultDoc = await PDFDocument.load(resultBytes, {
|
||||
ignoreEncryption: true,
|
||||
const resultDoc = await loadPdfDocument(resultBytes, {
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class ReversePagesNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -23,7 +24,7 @@ export class ReversePagesNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const pageCount = srcDoc.getPageCount();
|
||||
const newDoc = await PDFDocument.create();
|
||||
const reversedIndices = Array.from(
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { rotatePdfUniform } from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class RotateNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -33,7 +33,7 @@ export class RotateNode extends BaseWorkflowNode {
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const resultBytes = await rotatePdfUniform(input.bytes, angle);
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
return {
|
||||
type: 'pdf',
|
||||
document: resultDoc,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { pdfSocket } from '../sockets';
|
||||
import type { SocketData } from '../types';
|
||||
import { requirePdfInput, processBatch } from '../types';
|
||||
import { splitPdf, parsePageRange } from '../../utils/pdf-operations';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import { loadPdfDocument } from '../../utils/load-pdf-document.js';
|
||||
|
||||
export class SplitNode extends BaseWorkflowNode {
|
||||
readonly category = 'Organize & Manage' as const;
|
||||
@@ -32,11 +32,11 @@ export class SplitNode extends BaseWorkflowNode {
|
||||
|
||||
return {
|
||||
pdf: await processBatch(pdfInputs, async (input) => {
|
||||
const srcDoc = await PDFDocument.load(input.bytes);
|
||||
const srcDoc = await loadPdfDocument(input.bytes);
|
||||
const totalPages = srcDoc.getPageCount();
|
||||
const indices = parsePageRange(rangeStr, totalPages);
|
||||
const resultBytes = await splitPdf(input.bytes, indices);
|
||||
const resultDoc = await PDFDocument.load(resultBytes);
|
||||
const resultDoc = await loadPdfDocument(resultBytes);
|
||||
return {
|
||||
type: 'pdf',
|
||||
document: resultDoc,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user