- Updated function parameters and return types in `page-preview.ts`, `pdf-decrypt.ts`, and `pymupdf-loader.ts` for improved type safety. - Introduced type definitions for `CpdfInstance`, `PyMuPDFInstance`, and other related types to ensure better type checking. - Enhanced error handling in `sanitize.ts` by creating a utility function for error messages. - Removed unnecessary type assertions and improved type inference in `editor.ts`, `serialization.ts`, and `tools.test.ts`. - Added type definitions for markdown-it plugins to improve compatibility and type safety. - Enforced stricter TypeScript settings by enabling `noImplicitAny` in `tsconfig.json`. - Cleaned up test files by refining type assertions and ensuring consistency in type usage.
142 lines
3.9 KiB
TypeScript
142 lines
3.9 KiB
TypeScript
import { showLoader, hideLoader, showAlert } from '../ui.js';
|
|
import {
|
|
downloadFile,
|
|
initializeQpdf,
|
|
readFileAsArrayBuffer,
|
|
} from '../utils/helpers.js';
|
|
import { state } from '../state.js';
|
|
import JSZip from 'jszip';
|
|
import { deduplicateFileName } from '../utils/deduplicate-filename.js';
|
|
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
|
|
import type { QpdfInstanceExtended } from '@/types';
|
|
|
|
export async function repairPdfFile(file: File): Promise<Uint8Array | null> {
|
|
const inputPath = '/input.pdf';
|
|
const outputPath = '/repaired_form.pdf';
|
|
let qpdf: QpdfInstanceExtended;
|
|
|
|
try {
|
|
qpdf = await initializeQpdf();
|
|
const fileBuffer = await readFileAsArrayBuffer(file);
|
|
const uint8Array = new Uint8Array(fileBuffer as ArrayBuffer);
|
|
|
|
qpdf.FS.writeFile(inputPath, uint8Array);
|
|
|
|
const args = [inputPath, '--decrypt', outputPath];
|
|
|
|
try {
|
|
qpdf.callMain(args);
|
|
} catch (e) {
|
|
console.warn(`QPDF execution warning for ${file.name}:`, e);
|
|
}
|
|
|
|
let repairedData: Uint8Array | null = null;
|
|
try {
|
|
repairedData = qpdf.FS.readFile(outputPath, { encoding: 'binary' });
|
|
} catch (e) {
|
|
console.warn(`Failed to read output for ${file.name}:`, e);
|
|
}
|
|
|
|
try {
|
|
try {
|
|
qpdf.FS.unlink(inputPath);
|
|
} catch (e) {
|
|
console.warn(e);
|
|
}
|
|
try {
|
|
qpdf.FS.unlink(outputPath);
|
|
} catch (e) {
|
|
console.warn(e);
|
|
}
|
|
} catch (cleanupError) {
|
|
console.warn('Cleanup error:', cleanupError);
|
|
}
|
|
|
|
return repairedData;
|
|
} catch (error) {
|
|
console.error(`Error repairing ${file.name}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function repairPdf() {
|
|
if (state.files.length === 0) {
|
|
showAlert('No Files', 'Please select one or more PDF files.');
|
|
return;
|
|
}
|
|
|
|
const successfulRepairs: { name: string; data: Uint8Array }[] = [];
|
|
const failedRepairs: string[] = [];
|
|
|
|
try {
|
|
const decryptedFiles = await batchDecryptIfNeeded(state.files);
|
|
showLoader('Initializing repair engine...');
|
|
state.files = decryptedFiles;
|
|
|
|
for (let i = 0; i < state.files.length; i++) {
|
|
const file = state.files[i];
|
|
showLoader(`Repairing ${file.name} (${i + 1}/${state.files.length})...`);
|
|
|
|
const repairedData = await repairPdfFile(file);
|
|
|
|
if (repairedData && repairedData.length > 0) {
|
|
successfulRepairs.push({
|
|
name: `repaired-${file.name}`,
|
|
data: repairedData,
|
|
});
|
|
} else {
|
|
failedRepairs.push(file.name);
|
|
}
|
|
}
|
|
|
|
hideLoader();
|
|
|
|
if (successfulRepairs.length === 0) {
|
|
showAlert(
|
|
'Repair Failed',
|
|
'Unable to repair any of the uploaded PDF files.'
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (failedRepairs.length > 0) {
|
|
const failedList = failedRepairs.join(', ');
|
|
showAlert(
|
|
'Partial Success',
|
|
`Repaired ${successfulRepairs.length} file(s). Failed to repair: ${failedList}`
|
|
);
|
|
}
|
|
|
|
if (successfulRepairs.length === 1) {
|
|
const file = successfulRepairs[0];
|
|
const blob = new Blob([new Uint8Array(file.data)], {
|
|
type: 'application/pdf',
|
|
});
|
|
downloadFile(blob, file.name);
|
|
} else {
|
|
showLoader('Creating ZIP archive...');
|
|
const zip = new JSZip();
|
|
const usedNames = new Set<string>();
|
|
successfulRepairs.forEach((file) => {
|
|
const zipEntryName = deduplicateFileName(file.name, usedNames);
|
|
zip.file(zipEntryName, file.data);
|
|
});
|
|
|
|
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
|
downloadFile(zipBlob, 'repaired_pdfs.zip');
|
|
hideLoader();
|
|
}
|
|
|
|
if (failedRepairs.length === 0) {
|
|
showAlert('Success', 'All files repaired successfully!');
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error('Critical error during repair:', error);
|
|
hideLoader();
|
|
showAlert(
|
|
'Error',
|
|
'An unexpected error occurred during the repair process.'
|
|
);
|
|
}
|
|
}
|