diff --git a/src/js/compare/engine/extract-page-model.ts b/src/js/compare/engine/extract-page-model.ts index 434e463..787f0d0 100644 --- a/src/js/compare/engine/extract-page-model.ts +++ b/src/js/compare/engine/extract-page-model.ts @@ -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( diff --git a/src/js/compare/reporting/export-compare-pdf.ts b/src/js/compare/reporting/export-compare-pdf.ts index 25d7413..749d03c 100644 --- a/src/js/compare/reporting/export-compare-pdf.ts +++ b/src/js/compare/reporting/export-compare-pdf.ts @@ -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); diff --git a/src/js/handlers/fileHandler.ts b/src/js/handlers/fileHandler.ts index db1a75f..06628fe 100644 --- a/src/js/handlers/fileHandler.ts +++ b/src/js/handlers/fileHandler.ts @@ -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, diff --git a/src/js/logic/add-attachments-page.ts b/src/js/logic/add-attachments-page.ts index 9883e48..e78b4d3 100644 --- a/src/js/logic/add-attachments-page.ts +++ b/src/js/logic/add-attachments-page.ts @@ -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`; diff --git a/src/js/logic/add-blank-page-page.ts b/src/js/logic/add-blank-page-page.ts index d509400..c562612 100644 --- a/src/js/logic/add-blank-page-page.ts +++ b/src/js/logic/add-blank-page-page.ts @@ -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(); diff --git a/src/js/logic/add-page-labels-page.ts b/src/js/logic/add-page-labels-page.ts index e5adca9..7301501 100644 --- a/src/js/logic/add-page-labels-page.ts +++ b/src/js/logic/add-page-labels-page.ts @@ -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, }); diff --git a/src/js/logic/add-stamps.ts b/src/js/logic/add-stamps.ts index 92e15ea..e99f280 100644 --- a/src/js/logic/add-stamps.ts +++ b/src/js/logic/add-stamps.ts @@ -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 + ); + } }); } diff --git a/src/js/logic/add-watermark-page.ts b/src/js/logic/add-watermark-page.ts index 59714b0..fdb1c11 100644 --- a/src/js/logic/add-watermark-page.ts +++ b/src/js/logic/add-watermark-page.ts @@ -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; diff --git a/src/js/logic/background-color-page.ts b/src/js/logic/background-color-page.ts index c3a9686..30e0773 100644 --- a/src/js/logic/background-color-page.ts +++ b/src/js/logic/background-color-page.ts @@ -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'); diff --git a/src/js/logic/bates-numbering-page.ts b/src/js/logic/bates-numbering-page.ts index be4cdda..67204d9 100644 --- a/src/js/logic/bates-numbering-page.ts +++ b/src/js/logic/bates-numbering-page.ts @@ -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 = { 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, ''); diff --git a/src/js/logic/bookmark-pdf.ts b/src/js/logic/bookmark-pdf.ts index 5d230aa..12ef623 100644 --- a/src/js/logic/bookmark-pdf.ts +++ b/src/js/logic/bookmark-pdf.ts @@ -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 { 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); diff --git a/src/js/logic/change-permissions-page.ts b/src/js/logic/change-permissions-page.ts index b22d895..40f7a3b 100644 --- a/src/js/logic/change-permissions-page.ts +++ b/src/js/logic/change-permissions-page.ts @@ -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 = ''; - 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 = ''; + 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); + } }); diff --git a/src/js/logic/combine-single-page-page.ts b/src/js/logic/combine-single-page-page.ts index 2e6a297..3933f13 100644 --- a/src/js/logic/combine-single-page-page.ts +++ b/src/js/logic/combine-single-page-page.ts @@ -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(); diff --git a/src/js/logic/crop-pdf-page.ts b/src/js/logic/crop-pdf-page.ts index 582a373..65d98f6 100644 --- a/src/js/logic/crop-pdf-page.ts +++ b/src/js/logic/crop-pdf-page.ts @@ -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 ): Promise { - 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 ): Promise { const newPdfDoc = await PDFLibDocument.create(); - const sourcePdfDocForCopying = await PDFLibDocument.load( + const sourcePdfDocForCopying = await loadPdfDocument( cropperState.originalPdfBytes!, { throwOnInvalidObject: false } ); diff --git a/src/js/logic/cropper.ts b/src/js/logic/cropper.ts index d68110d..250e48a 100644 --- a/src/js/logic/cropper.ts +++ b/src/js/logic/cropper.ts @@ -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(); + } + }); } diff --git a/src/js/logic/delete-pages-page.ts b/src/js/logic/delete-pages-page.ts index cdea5db..b137c26 100644 --- a/src/js/logic/delete-pages-page.ts +++ b/src/js/logic/delete-pages-page.ts @@ -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` diff --git a/src/js/logic/divide-pages-page.ts b/src/js/logic/divide-pages-page.ts index fa592a4..7b2f53c 100644 --- a/src/js/logic/divide-pages-page.ts +++ b/src/js/logic/divide-pages-page.ts @@ -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(); diff --git a/src/js/logic/edit-metadata-page.ts b/src/js/logic/edit-metadata-page.ts index f6c0543..8315030 100644 --- a/src/js/logic/edit-metadata-page.ts +++ b/src/js/logic/edit-metadata-page.ts @@ -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(); diff --git a/src/js/logic/email-to-pdf.ts b/src/js/logic/email-to-pdf.ts index 40772a3..e4c7b63 100644 --- a/src/js/logic/email-to-pdf.ts +++ b/src/js/logic/email-to-pdf.ts @@ -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); diff --git a/src/js/logic/encrypt-pdf-page.ts b/src/js/logic/encrypt-pdf-page.ts index 941932a..599ea3b 100644 --- a/src/js/logic/encrypt-pdf-page.ts +++ b/src/js/logic/encrypt-pdf-page.ts @@ -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 = ''; - 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 = ''; + 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); + } }); diff --git a/src/js/logic/extract-pages-page.ts b/src/js/logic/extract-pages-page.ts index 1da72a4..7b0f15b 100644 --- a/src/js/logic/extract-pages-page.ts +++ b/src/js/logic/extract-pages-page.ts @@ -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(); diff --git a/src/js/logic/flatten-pdf-page.ts b/src/js/logic/flatten-pdf-page.ts index cb2ed65..8e128b2 100644 --- a/src/js/logic/flatten-pdf-page.ts +++ b/src/js/logic/flatten-pdf-page.ts @@ -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); diff --git a/src/js/logic/form-creator.ts b/src/js/logic/form-creator.ts index dfcc392..7ee262a 100644 --- a/src/js/logic/form-creator.ts +++ b/src/js/logic/form-creator.ts @@ -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(); diff --git a/src/js/logic/header-footer-page.ts b/src/js/logic/header-footer-page.ts index 2dc2d88..191ba11 100644 --- a/src/js/logic/header-footer-page.ts +++ b/src/js/logic/header-footer-page.ts @@ -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(); } diff --git a/src/js/logic/image-to-pdf-page.ts b/src/js/logic/image-to-pdf-page.ts index 3cef7e4..6101cb6 100644 --- a/src/js/logic/image-to-pdf-page.ts +++ b/src/js/logic/image-to-pdf-page.ts @@ -203,7 +203,9 @@ async function preprocessFile(file: File): Promise { }); } 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 { }); } 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, + }); } } diff --git a/src/js/logic/invert-colors-page.ts b/src/js/logic/invert-colors-page.ts index 9a4b87f..0cd6cf5 100644 --- a/src/js/logic/invert-colors-page.ts +++ b/src/js/logic/invert-colors-page.ts @@ -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'); diff --git a/src/js/logic/n-up-pdf-page.ts b/src/js/logic/n-up-pdf-page.ts index abe5b49..7905dcb 100644 --- a/src/js/logic/n-up-pdf-page.ts +++ b/src/js/logic/n-up-pdf-page.ts @@ -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(); diff --git a/src/js/logic/organize-pdf-page.ts b/src/js/logic/organize-pdf-page.ts index ea0803c..c320cd6 100644 --- a/src/js/logic/organize-pdf-page.ts +++ b/src/js/logic/organize-pdf-page.ts @@ -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; diff --git a/src/js/logic/page-dimensions-page.ts b/src/js/logic/page-dimensions-page.ts index 9d62e7e..a0d6929 100644 --- a/src/js/logic/page-dimensions-page.ts +++ b/src/js/logic/page-dimensions-page.ts @@ -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(); + 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() {
    ${stats.uniqueSizes .map( - (size: any) => ` + (size: UniqueSizeEntry) => `
  • • ${size.label}: ${size.count} page${size.count > 1 ? 's' : ''}
  • ` ) @@ -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) { diff --git a/src/js/logic/page-numbers-page.ts b/src/js/logic/page-numbers-page.ts index 4af659b..4d222e9 100644 --- a/src/js/logic/page-numbers-page.ts +++ b/src/js/logic/page-numbers-page.ts @@ -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(); diff --git a/src/js/logic/pdf-booklet-page.ts b/src/js/logic/pdf-booklet-page.ts index 59641b9..f540409 100644 --- a/src/js/logic/pdf-booklet-page.ts +++ b/src/js/logic/pdf-booklet-page.ts @@ -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( diff --git a/src/js/logic/pdf-multi-tool.ts b/src/js/logic/pdf-multi-tool.ts index aa5388c..2765d77 100644 --- a/src/js/logic/pdf-multi-tool.ts +++ b/src/js/logic/pdf-multi-tool.ts @@ -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); diff --git a/src/js/logic/remove-annotations-page.ts b/src/js/logic/remove-annotations-page.ts index cbea8a7..17c77c0 100644 --- a/src/js/logic/remove-annotations-page.ts +++ b/src/js/logic/remove-annotations-page.ts @@ -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'); diff --git a/src/js/logic/remove-blank-pages-page.ts b/src/js/logic/remove-blank-pages-page.ts index 36f0e70..5ece8d9 100644 --- a/src/js/logic/remove-blank-pages-page.ts +++ b/src/js/logic/remove-blank-pages-page.ts @@ -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(); diff --git a/src/js/logic/remove-metadata-page.ts b/src/js/logic/remove-metadata-page.ts index a586e45..2573a1b 100644 --- a/src/js/logic/remove-metadata-page.ts +++ b/src/js/logic/remove-metadata-page.ts @@ -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); diff --git a/src/js/logic/remove-restrictions-page.ts b/src/js/logic/remove-restrictions-page.ts index 98404e2..b8c97ff 100644 --- a/src/js/logic/remove-restrictions-page.ts +++ b/src/js/logic/remove-restrictions-page.ts @@ -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 = ''; - 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 = ''; + 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); + } }); diff --git a/src/js/logic/reverse-pages-page.ts b/src/js/logic/reverse-pages-page.ts index 4f0377b..a31744a 100644 --- a/src/js/logic/reverse-pages-page.ts +++ b/src/js/logic/reverse-pages-page.ts @@ -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 { 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(); diff --git a/src/js/logic/rotate-custom-page.ts b/src/js/logic/rotate-custom-page.ts index 6238b6b..77ff2d2 100644 --- a/src/js/logic/rotate-custom-page.ts +++ b/src/js/logic/rotate-custom-page.ts @@ -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, }); diff --git a/src/js/logic/rotate-pdf-page.ts b/src/js/logic/rotate-pdf-page.ts index 885e493..f95685f 100644 --- a/src/js/logic/rotate-pdf-page.ts +++ b/src/js/logic/rotate-pdf-page.ts @@ -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, }); diff --git a/src/js/logic/sign-pdf-page.ts b/src/js/logic/sign-pdf-page.ts index 8f9415a..3e1497b 100644 --- a/src/js/logic/sign-pdf-page.ts +++ b/src/js/logic/sign-pdf-page.ts @@ -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 = ''; + removeBtn.innerHTML = ''; 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 = 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( diff --git a/src/js/logic/split-pdf-page.ts b/src/js/logic/split-pdf-page.ts index 9b71612..d2d1a92 100644 --- a/src/js/logic/split-pdf-page.ts +++ b/src/js/logic/split-pdf-page.ts @@ -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'); diff --git a/src/js/logic/svg-to-pdf-page.ts b/src/js/logic/svg-to-pdf-page.ts index a65fe08..9073619 100644 --- a/src/js/logic/svg-to-pdf-page.ts +++ b/src/js/logic/svg-to-pdf-page.ts @@ -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 } ); } } diff --git a/src/js/logic/text-color-page.ts b/src/js/logic/text-color-page.ts index 853b104..e6a7382 100644 --- a/src/js/logic/text-color-page.ts +++ b/src/js/logic/text-color-page.ts @@ -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'); diff --git a/src/js/logic/validate-signature-pdf.ts b/src/js/logic/validate-signature-pdf.ts index 59ee5f5..ea16da5 100644 --- a/src/js/logic/validate-signature-pdf.ts +++ b/src/js/logic/validate-signature-pdf.ts @@ -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 { - 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 = { - '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 = { + '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 = { - '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 = { + '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'; } diff --git a/src/js/types/page-dimensions-type.ts b/src/js/types/page-dimensions-type.ts index 9cdccd5..2b30170 100644 --- a/src/js/types/page-dimensions-type.ts +++ b/src/js/types/page-dimensions-type.ts @@ -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; } diff --git a/src/js/types/sign-pdf-type.ts b/src/js/types/sign-pdf-type.ts index 3085770..05ef63b 100644 --- a/src/js/types/sign-pdf-type.ts +++ b/src/js/types/sign-pdf-type.ts @@ -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) => void; +} + +export interface PDFViewerApplication { + eventBus?: PDFViewerEventBus; + pdfDocument?: { + saveDocument: (storage: unknown) => Promise; + 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; } diff --git a/src/js/utils/compress.ts b/src/js/utils/compress.ts index ff4dbae..47a15bb 100644 --- a/src/js/utils/compress.ts +++ b/src/js/utils/compress.ts @@ -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, + }); } } } diff --git a/src/js/utils/ghostscript-dynamic-loader.ts b/src/js/utils/ghostscript-dynamic-loader.ts index 761b1bb..e43e3fa 100644 --- a/src/js/utils/ghostscript-dynamic-loader.ts +++ b/src/js/utils/ghostscript-dynamic-loader.ts @@ -52,7 +52,8 @@ export async function loadGhostscript(): Promise { } 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 } ); } })(); diff --git a/src/js/utils/ghostscript-loader.ts b/src/js/utils/ghostscript-loader.ts index 3ed86a0..8bd4435 100644 --- a/src/js/utils/ghostscript-loader.ts +++ b/src/js/utils/ghostscript-loader.ts @@ -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 { - 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; } diff --git a/src/js/utils/load-pdf-document.ts b/src/js/utils/load-pdf-document.ts new file mode 100644 index 0000000..adec708 --- /dev/null +++ b/src/js/utils/load-pdf-document.ts @@ -0,0 +1,14 @@ +import { PDFDocument } from 'pdf-lib'; + +type LoadOptions = Parameters[1]; +type PDFDocumentInstance = Awaited>; + +export async function loadPdfDocument( + pdf: Uint8Array | ArrayBuffer, + options?: LoadOptions +): Promise { + return PDFDocument.load(pdf, { + ignoreEncryption: true, + ...options, + }); +} diff --git a/src/js/utils/pdf-operations.ts b/src/js/utils/pdf-operations.ts index 5b9a72b..08dbdd6 100644 --- a/src/js/utils/pdf-operations.ts +++ b/src/js/utils/pdf-operations.ts @@ -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 { 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 { - 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 { - 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 { - 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 ): Promise { - 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 { - 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 { - 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 { - 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()) { diff --git a/src/js/utils/pymupdf-loader.ts b/src/js/utils/pymupdf-loader.ts index b6d39ab..58b74ba 100644 --- a/src/js/utils/pymupdf-loader.ts +++ b/src/js/utils/pymupdf-loader.ts @@ -67,7 +67,9 @@ export async function loadPyMuPDF(): Promise { 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, + }); } })(); diff --git a/src/js/utils/sanitize.ts b/src/js/utils/sanitize.ts index 4da78fa..6432b86 100644 --- a/src/js/utils/sanitize.ts +++ b/src/js/utils/sanitize.ts @@ -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 { diff --git a/src/js/workflow/nodes/add-blank-page-node.ts b/src/js/workflow/nodes/add-blank-page-node.ts index d648688..7283652 100644 --- a/src/js/workflow/nodes/add-blank-page-node.ts +++ b/src/js/workflow/nodes/add-blank-page-node.ts @@ -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() diff --git a/src/js/workflow/nodes/background-color-node.ts b/src/js/workflow/nodes/background-color-node.ts index 2d44c3e..26d17f3 100644 --- a/src/js/workflow/nodes/background-color-node.ts +++ b/src/js/workflow/nodes/background-color-node.ts @@ -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++) { diff --git a/src/js/workflow/nodes/booklet-node.ts b/src/js/workflow/nodes/booklet-node.ts index 440fc3f..d2aecbc 100644 --- a/src/js/workflow/nodes/booklet-node.ts +++ b/src/js/workflow/nodes/booklet-node.ts @@ -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 = { 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(); diff --git a/src/js/workflow/nodes/cbz-to-pdf-node.ts b/src/js/workflow/nodes/cbz-to-pdf-node.ts index ac1b641..3aa8fee 100644 --- a/src/js/workflow/nodes/cbz-to-pdf-node.ts +++ b/src/js/workflow/nodes/cbz-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/compress-node.ts b/src/js/workflow/nodes/compress-node.ts index d2e66ce..0f82b44 100644 --- a/src/js/workflow/nodes/compress-node.ts +++ b/src/js/workflow/nodes/compress-node.ts @@ -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', diff --git a/src/js/workflow/nodes/crop-node.ts b/src/js/workflow/nodes/crop-node.ts index e82a18d..0cd443e 100644 --- a/src/js/workflow/nodes/crop-node.ts +++ b/src/js/workflow/nodes/crop-node.ts @@ -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) { diff --git a/src/js/workflow/nodes/decrypt-node.ts b/src/js/workflow/nodes/decrypt-node.ts index 06fb044..ee20630 100644 --- a/src/js/workflow/nodes/decrypt-node.ts +++ b/src/js/workflow/nodes/decrypt-node.ts @@ -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, }); diff --git a/src/js/workflow/nodes/delete-pages-node.ts b/src/js/workflow/nodes/delete-pages-node.ts index 91b0fd7..858944c 100644 --- a/src/js/workflow/nodes/delete-pages-node.ts +++ b/src/js/workflow/nodes/delete-pages-node.ts @@ -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, diff --git a/src/js/workflow/nodes/deskew-node.ts b/src/js/workflow/nodes/deskew-node.ts index 16251d5..7a8a553 100644 --- a/src/js/workflow/nodes/deskew-node.ts +++ b/src/js/workflow/nodes/deskew-node.ts @@ -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', diff --git a/src/js/workflow/nodes/digital-sign-node.ts b/src/js/workflow/nodes/digital-sign-node.ts index c82fe55..3e990c0 100644 --- a/src/js/workflow/nodes/digital-sign-node.ts +++ b/src/js/workflow/nodes/digital-sign-node.ts @@ -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', diff --git a/src/js/workflow/nodes/divide-pages-node.ts b/src/js/workflow/nodes/divide-pages-node.ts index 1334697..bedcb4f 100644 --- a/src/js/workflow/nodes/divide-pages-node.ts +++ b/src/js/workflow/nodes/divide-pages-node.ts @@ -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(); diff --git a/src/js/workflow/nodes/edit-metadata-node.ts b/src/js/workflow/nodes/edit-metadata-node.ts index 36696a6..44e8a8f 100644 --- a/src/js/workflow/nodes/edit-metadata-node.ts +++ b/src/js/workflow/nodes/edit-metadata-node.ts @@ -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); diff --git a/src/js/workflow/nodes/email-to-pdf-node.ts b/src/js/workflow/nodes/email-to-pdf-node.ts index 08635ae..fe08b6f 100644 --- a/src/js/workflow/nodes/email-to-pdf-node.ts +++ b/src/js/workflow/nodes/email-to-pdf-node.ts @@ -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; + } + ).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, diff --git a/src/js/workflow/nodes/encrypt-node.ts b/src/js/workflow/nodes/encrypt-node.ts index 8f81fec..0d50218 100644 --- a/src/js/workflow/nodes/encrypt-node.ts +++ b/src/js/workflow/nodes/encrypt-node.ts @@ -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', diff --git a/src/js/workflow/nodes/epub-to-pdf-node.ts b/src/js/workflow/nodes/epub-to-pdf-node.ts index 18f7335..46483b8 100644 --- a/src/js/workflow/nodes/epub-to-pdf-node.ts +++ b/src/js/workflow/nodes/epub-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/excel-to-pdf-node.ts b/src/js/workflow/nodes/excel-to-pdf-node.ts index 8fb2479..c7261a8 100644 --- a/src/js/workflow/nodes/excel-to-pdf-node.ts +++ b/src/js/workflow/nodes/excel-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/extract-pages-node.ts b/src/js/workflow/nodes/extract-pages-node.ts index 9c38697..712b7a4 100644 --- a/src/js/workflow/nodes/extract-pages-node.ts +++ b/src/js/workflow/nodes/extract-pages-node.ts @@ -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); diff --git a/src/js/workflow/nodes/fb2-to-pdf-node.ts b/src/js/workflow/nodes/fb2-to-pdf-node.ts index 23e7099..4a21383 100644 --- a/src/js/workflow/nodes/fb2-to-pdf-node.ts +++ b/src/js/workflow/nodes/fb2-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/fix-page-size-node.ts b/src/js/workflow/nodes/fix-page-size-node.ts index 7ae8581..7bdb0c1 100644 --- a/src/js/workflow/nodes/fix-page-size-node.ts +++ b/src/js/workflow/nodes/fix-page-size-node.ts @@ -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', diff --git a/src/js/workflow/nodes/flatten-node.ts b/src/js/workflow/nodes/flatten-node.ts index f22138b..0791a5e 100644 --- a/src/js/workflow/nodes/flatten-node.ts +++ b/src/js/workflow/nodes/flatten-node.ts @@ -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(); diff --git a/src/js/workflow/nodes/font-to-outline-node.ts b/src/js/workflow/nodes/font-to-outline-node.ts index 026fa86..09da7fb 100644 --- a/src/js/workflow/nodes/font-to-outline-node.ts +++ b/src/js/workflow/nodes/font-to-outline-node.ts @@ -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', diff --git a/src/js/workflow/nodes/header-footer-node.ts b/src/js/workflow/nodes/header-footer-node.ts index 19a7654..1cf94e3 100644 --- a/src/js/workflow/nodes/header-footer-node.ts +++ b/src/js/workflow/nodes/header-footer-node.ts @@ -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; diff --git a/src/js/workflow/nodes/image-input-node.ts b/src/js/workflow/nodes/image-input-node.ts index 5a72b4a..21da5bc 100644 --- a/src/js/workflow/nodes/image-input-node.ts +++ b/src/js/workflow/nodes/image-input-node.ts @@ -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', diff --git a/src/js/workflow/nodes/json-to-pdf-node.ts b/src/js/workflow/nodes/json-to-pdf-node.ts index d59b0b7..60227f1 100644 --- a/src/js/workflow/nodes/json-to-pdf-node.ts +++ b/src/js/workflow/nodes/json-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/linearize-node.ts b/src/js/workflow/nodes/linearize-node.ts index c274622..cadb9f3 100644 --- a/src/js/workflow/nodes/linearize-node.ts +++ b/src/js/workflow/nodes/linearize-node.ts @@ -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', diff --git a/src/js/workflow/nodes/markdown-to-pdf-node.ts b/src/js/workflow/nodes/markdown-to-pdf-node.ts index 8be87c8..5effdc1 100644 --- a/src/js/workflow/nodes/markdown-to-pdf-node.ts +++ b/src/js/workflow/nodes/markdown-to-pdf-node.ts @@ -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; } ${md.render(textContent)}`; - const pdfBlob = await (pymupdf as any).htmlToPdf(htmlContent, { + const pdfBlob = await ( + pymupdf as unknown as { + htmlToPdf(html: string, options: unknown): Promise; + } + ).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, diff --git a/src/js/workflow/nodes/merge-node.ts b/src/js/workflow/nodes/merge-node.ts index 948d78b..8177403 100644 --- a/src/js/workflow/nodes/merge-node.ts +++ b/src/js/workflow/nodes/merge-node.ts @@ -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: { diff --git a/src/js/workflow/nodes/mobi-to-pdf-node.ts b/src/js/workflow/nodes/mobi-to-pdf-node.ts index 332989d..c6a4e22 100644 --- a/src/js/workflow/nodes/mobi-to-pdf-node.ts +++ b/src/js/workflow/nodes/mobi-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/n-up-node.ts b/src/js/workflow/nodes/n-up-node.ts index f1358fa..1e17558 100644 --- a/src/js/workflow/nodes/n-up-node.ts +++ b/src/js/workflow/nodes/n-up-node.ts @@ -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]; diff --git a/src/js/workflow/nodes/odg-to-pdf-node.ts b/src/js/workflow/nodes/odg-to-pdf-node.ts index 50d4df3..789b2db 100644 --- a/src/js/workflow/nodes/odg-to-pdf-node.ts +++ b/src/js/workflow/nodes/odg-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/page-numbers-node.ts b/src/js/workflow/nodes/page-numbers-node.ts index 280478c..56bf77e 100644 --- a/src/js/workflow/nodes/page-numbers-node.ts +++ b/src/js/workflow/nodes/page-numbers-node.ts @@ -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', diff --git a/src/js/workflow/nodes/pages-to-pdf-node.ts b/src/js/workflow/nodes/pages-to-pdf-node.ts index eff335e..c133a82 100644 --- a/src/js/workflow/nodes/pages-to-pdf-node.ts +++ b/src/js/workflow/nodes/pages-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/pdf-input-node.ts b/src/js/workflow/nodes/pdf-input-node.ts index 4420347..c6924ba 100644 --- a/src/js/workflow/nodes/pdf-input-node.ts +++ b/src/js/workflow/nodes/pdf-input-node.ts @@ -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({ diff --git a/src/js/workflow/nodes/pdf-to-csv-node.ts b/src/js/workflow/nodes/pdf-to-csv-node.ts index 7c9f00c..5b50c74 100644 --- a/src/js/workflow/nodes/pdf-to-csv-node.ts +++ b/src/js/workflow/nodes/pdf-to-csv-node.ts @@ -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); }); } diff --git a/src/js/workflow/nodes/pdf-to-images-node.ts b/src/js/workflow/nodes/pdf-to-images-node.ts index 184c39a..f531aa8 100644 --- a/src/js/workflow/nodes/pdf-to-images-node.ts +++ b/src/js/workflow/nodes/pdf-to-images-node.ts @@ -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 { + private async addPdfPagesAsSvg( + allPdfs: PDFData[], + zip: JSZip + ): Promise { const pymupdf = await loadPyMuPDF(); for (const pdf of allPdfs) { const blob = new Blob([new Uint8Array(pdf.bytes)], { diff --git a/src/js/workflow/nodes/pdf-to-pdfa-node.ts b/src/js/workflow/nodes/pdf-to-pdfa-node.ts index 842e137..0e85110 100644 --- a/src/js/workflow/nodes/pdf-to-pdfa-node.ts +++ b/src/js/workflow/nodes/pdf-to-pdfa-node.ts @@ -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', diff --git a/src/js/workflow/nodes/pdf-to-xlsx-node.ts b/src/js/workflow/nodes/pdf-to-xlsx-node.ts index 234c948..2294553 100644 --- a/src/js/workflow/nodes/pdf-to-xlsx-node.ts +++ b/src/js/workflow/nodes/pdf-to-xlsx-node.ts @@ -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 }); }); } diff --git a/src/js/workflow/nodes/powerpoint-to-pdf-node.ts b/src/js/workflow/nodes/powerpoint-to-pdf-node.ts index 7fb1f42..7f9c488 100644 --- a/src/js/workflow/nodes/powerpoint-to-pdf-node.ts +++ b/src/js/workflow/nodes/powerpoint-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/pub-to-pdf-node.ts b/src/js/workflow/nodes/pub-to-pdf-node.ts index 8af5d7e..09fcbe3 100644 --- a/src/js/workflow/nodes/pub-to-pdf-node.ts +++ b/src/js/workflow/nodes/pub-to-pdf-node.ts @@ -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, diff --git a/src/js/workflow/nodes/rasterize-node.ts b/src/js/workflow/nodes/rasterize-node.ts index 30e40bc..5c30de7 100644 --- a/src/js/workflow/nodes/rasterize-node.ts +++ b/src/js/workflow/nodes/rasterize-node.ts @@ -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', diff --git a/src/js/workflow/nodes/redact-node.ts b/src/js/workflow/nodes/redact-node.ts index 3997d17..793ef53 100644 --- a/src/js/workflow/nodes/redact-node.ts +++ b/src/js/workflow/nodes/redact-node.ts @@ -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', diff --git a/src/js/workflow/nodes/remove-annotations-node.ts b/src/js/workflow/nodes/remove-annotations-node.ts index b857fe3..34ae941 100644 --- a/src/js/workflow/nodes/remove-annotations-node.ts +++ b/src/js/workflow/nodes/remove-annotations-node.ts @@ -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) { diff --git a/src/js/workflow/nodes/remove-blank-pages-node.ts b/src/js/workflow/nodes/remove-blank-pages-node.ts index 692c9a9..45ec41f 100644 --- a/src/js/workflow/nodes/remove-blank-pages-node.ts +++ b/src/js/workflow/nodes/remove-blank-pages-node.ts @@ -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++) { diff --git a/src/js/workflow/nodes/repair-node.ts b/src/js/workflow/nodes/repair-node.ts index 0794316..92de112 100644 --- a/src/js/workflow/nodes/repair-node.ts +++ b/src/js/workflow/nodes/repair-node.ts @@ -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, }); diff --git a/src/js/workflow/nodes/reverse-pages-node.ts b/src/js/workflow/nodes/reverse-pages-node.ts index 8bcbe0a..f0e5d6d 100644 --- a/src/js/workflow/nodes/reverse-pages-node.ts +++ b/src/js/workflow/nodes/reverse-pages-node.ts @@ -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( diff --git a/src/js/workflow/nodes/rotate-node.ts b/src/js/workflow/nodes/rotate-node.ts index be5ce88..fff643f 100644 --- a/src/js/workflow/nodes/rotate-node.ts +++ b/src/js/workflow/nodes/rotate-node.ts @@ -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, diff --git a/src/js/workflow/nodes/split-node.ts b/src/js/workflow/nodes/split-node.ts index 225c7f3..a7da231 100644 --- a/src/js/workflow/nodes/split-node.ts +++ b/src/js/workflow/nodes/split-node.ts @@ -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, diff --git a/src/js/workflow/nodes/table-of-contents-node.ts b/src/js/workflow/nodes/table-of-contents-node.ts index ab9b21c..227507d 100644 --- a/src/js/workflow/nodes/table-of-contents-node.ts +++ b/src/js/workflow/nodes/table-of-contents-node.ts @@ -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 { WasmProvider } from '../../utils/wasm-provider.js'; +import { loadPdfDocument } from '../../utils/load-pdf-document.js'; export class TableOfContentsNode extends BaseWorkflowNode { readonly category = 'Organize & Manage' as const; @@ -107,7 +107,7 @@ export class TableOfContentsNode extends BaseWorkflowNode { }); const bytes = new Uint8Array(resultBytes); - const document = await PDFDocument.load(bytes); + const document = await loadPdfDocument(bytes); return { type: 'pdf', diff --git a/src/js/workflow/nodes/text-to-pdf-node.ts b/src/js/workflow/nodes/text-to-pdf-node.ts index f82bdd5..b464351 100644 --- a/src/js/workflow/nodes/text-to-pdf-node.ts +++ b/src/js/workflow/nodes/text-to-pdf-node.ts @@ -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 TextToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -89,7 +89,7 @@ export class TextToPdfNode 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, diff --git a/src/js/workflow/nodes/vsd-to-pdf-node.ts b/src/js/workflow/nodes/vsd-to-pdf-node.ts index fa0f86f..2f87e29 100644 --- a/src/js/workflow/nodes/vsd-to-pdf-node.ts +++ b/src/js/workflow/nodes/vsd-to-pdf-node.ts @@ -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 VsdToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -57,7 +57,7 @@ export class VsdToPdfNode 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, diff --git a/src/js/workflow/nodes/watermark-node.ts b/src/js/workflow/nodes/watermark-node.ts index d16eed1..2d6c4ff 100644 --- a/src/js/workflow/nodes/watermark-node.ts +++ b/src/js/workflow/nodes/watermark-node.ts @@ -7,6 +7,7 @@ import { addTextWatermark, parsePageRange } from '../../utils/pdf-operations'; import { PDFDocument } from 'pdf-lib'; import { hexToRgb } from '../../utils/helpers.js'; import * as pdfjsLib from 'pdfjs-dist'; +import { loadPdfDocument } from '../../utils/load-pdf-document.js'; export class WatermarkNode extends BaseWorkflowNode { readonly category = 'Edit & Annotate' as const; @@ -100,7 +101,7 @@ export class WatermarkNode 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 pageIndices = @@ -163,7 +164,7 @@ export class WatermarkNode extends BaseWorkflowNode { resultBytes = new Uint8Array(await flattenedDoc.save()); } - const resultDoc = await PDFDocument.load(resultBytes); + const resultDoc = await loadPdfDocument(resultBytes); return { type: 'pdf', diff --git a/src/js/workflow/nodes/word-to-pdf-node.ts b/src/js/workflow/nodes/word-to-pdf-node.ts index 8bb8318..898f14c 100644 --- a/src/js/workflow/nodes/word-to-pdf-node.ts +++ b/src/js/workflow/nodes/word-to-pdf-node.ts @@ -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 WordToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -62,7 +62,7 @@ export class WordToPdfNode 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, diff --git a/src/js/workflow/nodes/wpd-to-pdf-node.ts b/src/js/workflow/nodes/wpd-to-pdf-node.ts index 83fc873..08e61b5 100644 --- a/src/js/workflow/nodes/wpd-to-pdf-node.ts +++ b/src/js/workflow/nodes/wpd-to-pdf-node.ts @@ -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 WpdToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -56,7 +56,7 @@ export class WpdToPdfNode 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, diff --git a/src/js/workflow/nodes/wps-to-pdf-node.ts b/src/js/workflow/nodes/wps-to-pdf-node.ts index fbd1dc6..020526a 100644 --- a/src/js/workflow/nodes/wps-to-pdf-node.ts +++ b/src/js/workflow/nodes/wps-to-pdf-node.ts @@ -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 WpsToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -56,7 +56,7 @@ export class WpsToPdfNode 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, diff --git a/src/js/workflow/nodes/xml-to-pdf-node.ts b/src/js/workflow/nodes/xml-to-pdf-node.ts index 69e2284..5e41360 100644 --- a/src/js/workflow/nodes/xml-to-pdf-node.ts +++ b/src/js/workflow/nodes/xml-to-pdf-node.ts @@ -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 XmlToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -59,7 +59,7 @@ export class XmlToPdfNode 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, diff --git a/src/js/workflow/nodes/xps-to-pdf-node.ts b/src/js/workflow/nodes/xps-to-pdf-node.ts index 206527e..e407124 100644 --- a/src/js/workflow/nodes/xps-to-pdf-node.ts +++ b/src/js/workflow/nodes/xps-to-pdf-node.ts @@ -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 XpsToPdfNode extends BaseWorkflowNode { readonly category = 'Input' as const; @@ -55,7 +55,7 @@ export class XpsToPdfNode extends BaseWorkflowNode { for (const file of this.files) { const blob = await pymupdf.convertToPdf(file, { filetype: 'xps' }); const bytes = new Uint8Array(await blob.arrayBuffer()); - const document = await PDFDocument.load(bytes); + const document = await loadPdfDocument(bytes); results.push({ type: 'pdf', document, diff --git a/src/js/workflow/serialization.ts b/src/js/workflow/serialization.ts index c0aaba9..2b15edb 100644 --- a/src/js/workflow/serialization.ts +++ b/src/js/workflow/serialization.ts @@ -185,7 +185,8 @@ export function saveWorkflow( delete templates[name]; } throw new Error( - 'Failed to save workflow: storage quota exceeded. Try deleting old templates.' + 'Failed to save workflow: storage quota exceeded. Try deleting old templates.', + { cause: e } ); } }