diff --git a/src/js/logic/add-blank-page-page.ts b/src/js/logic/add-blank-page-page.ts index c562612..bf286ef 100644 --- a/src/js/logic/add-blank-page-page.ts +++ b/src/js/logic/add-blank-page-page.ts @@ -85,9 +85,7 @@ async function updateUI() { } showLoader('Loading PDF...'); pageState.file = result.file; - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); result.pdf.destroy(); hideLoader(); diff --git a/src/js/logic/add-page-labels-page.ts b/src/js/logic/add-page-labels-page.ts index 7301501..96002cc 100644 --- a/src/js/logic/add-page-labels-page.ts +++ b/src/js/logic/add-page-labels-page.ts @@ -174,9 +174,7 @@ async function handleFiles(files: FileList) { showLoader(translate('tools:addPageLabels.loadingPdf', 'Loading PDF...')); try { const arrayBuffer = await readFileAsArrayBuffer(file); - const pdfDoc = await loadPdfDocument(arrayBuffer as ArrayBuffer, { - throwOnInvalidObject: false, - }); + const pdfDoc = await loadPdfDocument(arrayBuffer as ArrayBuffer); if (pdfDoc.isEncrypted) { showAlert( diff --git a/src/js/logic/combine-single-page-page.ts b/src/js/logic/combine-single-page-page.ts index 3933f13..f5307ab 100644 --- a/src/js/logic/combine-single-page-page.ts +++ b/src/js/logic/combine-single-page-page.ts @@ -82,9 +82,7 @@ async function updateUI() { showLoader('Loading PDF...'); result.pdf.destroy(); pageState.file = result.file; - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); hideLoader(); const pageCount = pageState.pdfDoc.getPageCount(); diff --git a/src/js/logic/crop-pdf-page.ts b/src/js/logic/crop-pdf-page.ts index 65d98f6..71595b7 100644 --- a/src/js/logic/crop-pdf-page.ts +++ b/src/js/logic/crop-pdf-page.ts @@ -306,9 +306,7 @@ async function performCrop() { async function performMetadataCrop( cropData: Record ): Promise { - const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!, { - throwOnInvalidObject: false, - }); + const pdfToModify = await loadPdfDocument(cropperState.originalPdfBytes!); for (const pageNum in cropData) { const pdfJsPage = await cropperState.pdfDoc.getPage(Number(pageNum)); @@ -350,8 +348,7 @@ async function performFlatteningCrop( ): Promise { const newPdfDoc = await PDFLibDocument.create(); const sourcePdfDocForCopying = await loadPdfDocument( - cropperState.originalPdfBytes!, - { throwOnInvalidObject: false } + cropperState.originalPdfBytes! ); const totalPages = cropperState.pdfDoc.numPages; diff --git a/src/js/logic/cropper.ts b/src/js/logic/cropper.ts index 250e48a..56cb7a5 100644 --- a/src/js/logic/cropper.ts +++ b/src/js/logic/cropper.ts @@ -188,8 +188,7 @@ async function performFlatteningCrop(cropData: any) { // Load the original PDF with pdf-lib to copy un-cropped pages from const sourcePdfDocForCopying = await loadPdfDocument( - cropperState.originalPdfBytes, - { ignoreEncryption: true, throwOnInvalidObject: false } + cropperState.originalPdfBytes ); const totalPages = cropperState.pdfDoc.numPages; @@ -332,8 +331,7 @@ export async function setupCropperTool() { finalPdfBytes = await newPdfDoc.save(); } else { const pdfToModify = await loadPdfDocument( - cropperState.originalPdfBytes, - { ignoreEncryption: true, throwOnInvalidObject: false } + cropperState.originalPdfBytes ); await performMetadataCrop(pdfToModify, finalCropData); finalPdfBytes = await pdfToModify.save(); diff --git a/src/js/logic/delete-pages-page.ts b/src/js/logic/delete-pages-page.ts index b137c26..b61ffa5 100644 --- a/src/js/logic/delete-pages-page.ts +++ b/src/js/logic/delete-pages-page.ts @@ -94,9 +94,7 @@ async function handleFile(file: File) { } showLoader('Loading PDF...'); deleteState.file = result.file; - deleteState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + deleteState.pdfDoc = await loadPdfDocument(result.bytes); deleteState.pdfJsDoc = result.pdf; deleteState.totalPages = deleteState.pdfDoc.getPageCount(); deleteState.pagesToDelete = new Set(); diff --git a/src/js/logic/edit-metadata-page.ts b/src/js/logic/edit-metadata-page.ts index 8315030..f44683a 100644 --- a/src/js/logic/edit-metadata-page.ts +++ b/src/js/logic/edit-metadata-page.ts @@ -229,9 +229,7 @@ async function updateUI() { showLoader('Loading PDF...'); result.pdf.destroy(); pageState.file = result.file; - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); hideLoader(); const pageCount = pageState.pdfDoc.getPageCount(); diff --git a/src/js/logic/extract-pages-page.ts b/src/js/logic/extract-pages-page.ts index 7b0f15b..174ba7f 100644 --- a/src/js/logic/extract-pages-page.ts +++ b/src/js/logic/extract-pages-page.ts @@ -100,9 +100,7 @@ async function handleFile(file: File) { showLoader('Loading PDF...'); extractState.file = result.file; result.pdf.destroy(); - extractState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + extractState.pdfDoc = await loadPdfDocument(result.bytes); extractState.totalPages = extractState.pdfDoc.getPageCount(); updateFileDisplay(); diff --git a/src/js/logic/n-up-pdf-page.ts b/src/js/logic/n-up-pdf-page.ts index 7905dcb..c17807c 100644 --- a/src/js/logic/n-up-pdf-page.ts +++ b/src/js/logic/n-up-pdf-page.ts @@ -75,9 +75,7 @@ async function updateUI() { showLoader('Loading PDF...'); result.pdf.destroy(); pageState.file = result.file; - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); hideLoader(); const pageCount = pageState.pdfDoc.getPageCount(); diff --git a/src/js/logic/organize-pdf-page.ts b/src/js/logic/organize-pdf-page.ts index c320cd6..bb485dd 100644 --- a/src/js/logic/organize-pdf-page.ts +++ b/src/js/logic/organize-pdf-page.ts @@ -177,9 +177,7 @@ async function handleFile(file: File) { if (!result) return; showLoader('Loading PDF...'); - organizeState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + organizeState.pdfDoc = await loadPdfDocument(result.bytes); organizeState.pdfJsDoc = result.pdf; organizeState.file = result.file; organizeState.totalPages = organizeState.pdfDoc.getPageCount(); diff --git a/src/js/logic/pdf-booklet-page.ts b/src/js/logic/pdf-booklet-page.ts index f540409..e43cd3a 100644 --- a/src/js/logic/pdf-booklet-page.ts +++ b/src/js/logic/pdf-booklet-page.ts @@ -99,9 +99,7 @@ async function updateUI() { pageState.pdfBytes = new Uint8Array(result.bytes); pageState.pdfjsDoc = result.pdf; - pageState.pdfDoc = await loadPdfDocument(pageState.pdfBytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(pageState.pdfBytes); hideLoader(); diff --git a/src/js/logic/pdf-multi-tool.ts b/src/js/logic/pdf-multi-tool.ts index 2765d77..2ad5437 100644 --- a/src/js/logic/pdf-multi-tool.ts +++ b/src/js/logic/pdf-multi-tool.ts @@ -436,9 +436,7 @@ async function loadPdfs(files: File[]) { pwResult.pdf.destroy(); arrayBuffer = pwResult.bytes as ArrayBuffer; - const pdfDoc = await loadPdfDocument(arrayBuffer, { - throwOnInvalidObject: false, - }); + const pdfDoc = await loadPdfDocument(arrayBuffer); currentPdfDocs.push(pdfDoc); const pdfIndex = currentPdfDocs.length - 1; @@ -859,9 +857,7 @@ async function handleInsertPdf(e: Event) { if (!pwResult) return; pwResult.pdf.destroy(); - const pdfDoc = await loadPdfDocument(pwResult.bytes, { - throwOnInvalidObject: false, - }); + const pdfDoc = await loadPdfDocument(pwResult.bytes); currentPdfDocs.push(pdfDoc); const pdfIndex = currentPdfDocs.length - 1; diff --git a/src/js/logic/rotate-custom-page.ts b/src/js/logic/rotate-custom-page.ts index 77ff2d2..72cf124 100644 --- a/src/js/logic/rotate-custom-page.ts +++ b/src/js/logic/rotate-custom-page.ts @@ -236,9 +236,7 @@ async function updateUI() { } showLoader('Loading PDF...'); - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); pageState.pdfJsDoc = result.pdf; diff --git a/src/js/logic/rotate-pdf-page.ts b/src/js/logic/rotate-pdf-page.ts index f95685f..a37433f 100644 --- a/src/js/logic/rotate-pdf-page.ts +++ b/src/js/logic/rotate-pdf-page.ts @@ -208,9 +208,7 @@ async function updateUI() { } showLoader('Loading PDF...'); - pageState.pdfDoc = await loadPdfDocument(result.bytes, { - throwOnInvalidObject: false, - }); + pageState.pdfDoc = await loadPdfDocument(result.bytes); pageState.pdfJsDoc = result.pdf; diff --git a/src/js/logic/split-pdf-page.ts b/src/js/logic/split-pdf-page.ts index d2d1a92..045b24a 100644 --- a/src/js/logic/split-pdf-page.ts +++ b/src/js/logic/split-pdf-page.ts @@ -90,19 +90,17 @@ document.addEventListener('DOMContentLoaded', () => { // Load PDF Document try { - if (!state.pdfDoc) { - const result = await loadPdfWithPasswordPrompt(file); - if (!result) { - state.files = []; - updateUI(); - return; - } - result.pdf.destroy(); - state.files[0] = result.file; - state.pdfDoc = await loadPdfDocument(result.bytes); + const result = await loadPdfWithPasswordPrompt(file); + if (!result) { + state.files = []; + updateUI(); + return; } - // Update page count - metaSpan.textContent = `${formatBytes(file.size)} • ${state.pdfDoc.getPageCount()} pages`; + const pageCount = result.pdf.numPages; + result.pdf.destroy(); + state.files[0] = result.file; + state.pdfDoc = await loadPdfDocument(result.bytes); + metaSpan.textContent = `${formatBytes(file.size)} • ${pageCount} pages`; } catch (error) { console.error('Error loading PDF:', error); showAlert('Error', 'Failed to load PDF file.'); diff --git a/src/js/utils/load-pdf-document.ts b/src/js/utils/load-pdf-document.ts index adec708..b287651 100644 --- a/src/js/utils/load-pdf-document.ts +++ b/src/js/utils/load-pdf-document.ts @@ -1,14 +1,68 @@ import { PDFDocument } from 'pdf-lib'; +import { initializeQpdf } from './helpers.js'; type LoadOptions = Parameters[1]; type PDFDocumentInstance = Awaited>; +async function repairPdfBytes(pdf: Uint8Array): Promise { + try { + const qpdf = await initializeQpdf(); + qpdf.FS.writeFile('/input.pdf', pdf); + + try { + qpdf.callMain(['/input.pdf', '--decrypt', '/output.pdf']); + } catch (e) { + console.warn('[loadPdfDocument] qpdf repair warning:', e); + } + + let repaired: Uint8Array | null = null; + try { + repaired = qpdf.FS.readFile('/output.pdf', { encoding: 'binary' }); + } catch (e) { + console.warn('[loadPdfDocument] Failed to read repaired output:', e); + } + + try { + qpdf.FS.unlink('/input.pdf'); + } catch (e) { + console.warn('[loadPdfDocument] Cleanup error:', e); + } + try { + qpdf.FS.unlink('/output.pdf'); + } catch (e) { + console.warn('[loadPdfDocument] Cleanup error:', e); + } + + return repaired; + } catch (e) { + console.warn('[loadPdfDocument] qpdf not available for repair:', e); + return null; + } +} + export async function loadPdfDocument( pdf: Uint8Array | ArrayBuffer, options?: LoadOptions ): Promise { - return PDFDocument.load(pdf, { + const loadOpts = { ignoreEncryption: true, + throwOnInvalidObject: false, ...options, - }); + }; + + const inputBytes = pdf instanceof Uint8Array ? pdf : new Uint8Array(pdf); + const repaired = await repairPdfBytes(inputBytes); + + if (repaired) { + try { + return await PDFDocument.load(repaired, loadOpts); + } catch (e) { + console.warn( + '[loadPdfDocument] Failed to load repaired PDF, falling back to original:', + e + ); + } + } + + return PDFDocument.load(pdf, loadOpts); } diff --git a/src/js/workflow/nodes/decrypt-node.ts b/src/js/workflow/nodes/decrypt-node.ts index ee20630..6505e6b 100644 --- a/src/js/workflow/nodes/decrypt-node.ts +++ b/src/js/workflow/nodes/decrypt-node.ts @@ -37,9 +37,7 @@ export class DecryptNode extends BaseWorkflowNode { input.bytes, password ); - const document = await loadPdfDocument(resultBytes, { - throwOnInvalidObject: false, - }); + const document = await loadPdfDocument(resultBytes); return { type: 'pdf', diff --git a/src/js/workflow/nodes/pdf-input-node.ts b/src/js/workflow/nodes/pdf-input-node.ts index c6924ba..8492fd3 100644 --- a/src/js/workflow/nodes/pdf-input-node.ts +++ b/src/js/workflow/nodes/pdf-input-node.ts @@ -31,16 +31,14 @@ export class PDFInputNode extends BaseWorkflowNode { let isEncrypted = false; try { - await loadPdfDocument(bytes, { throwOnInvalidObject: false }); + await loadPdfDocument(bytes); } catch { isEncrypted = true; } if (isEncrypted) { try { - await loadPdfDocument(bytes, { - throwOnInvalidObject: false, - }); + await loadPdfDocument(bytes); } catch { throw new Error( `Failed to load "${file.name}" - file may be corrupted` @@ -49,9 +47,7 @@ export class PDFInputNode extends BaseWorkflowNode { throw new EncryptedPDFError(file.name); } - const document = await loadPdfDocument(bytes, { - throwOnInvalidObject: false, - }); + const document = await loadPdfDocument(bytes); this.files.push({ type: 'pdf', document, @@ -64,9 +60,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 loadPdfDocument(decryptedBytes, { - throwOnInvalidObject: false, - }); + const document = await loadPdfDocument(decryptedBytes); this.files.push({ type: 'pdf', document, diff --git a/src/js/workflow/nodes/repair-node.ts b/src/js/workflow/nodes/repair-node.ts index 92de112..05c882e 100644 --- a/src/js/workflow/nodes/repair-node.ts +++ b/src/js/workflow/nodes/repair-node.ts @@ -49,9 +49,7 @@ export class RepairNode extends BaseWorkflowNode { } const resultBytes = new Uint8Array(repairedData); - const resultDoc = await loadPdfDocument(resultBytes, { - throwOnInvalidObject: false, - }); + const resultDoc = await loadPdfDocument(resultBytes); return { type: 'pdf',