2025-12-11 19:34:14 +05:30
|
|
|
import { showAlert } from '../ui.js';
|
|
|
|
|
import { downloadFile, formatBytes } from '../utils/helpers.js';
|
|
|
|
|
import { PDFDocument, PDFName } from 'pdf-lib';
|
|
|
|
|
import { icons, createIcons } from 'lucide';
|
2026-01-05 14:57:30 +05:30
|
|
|
import { SanitizePdfState } from '@/types';
|
2025-12-11 19:34:14 +05:30
|
|
|
|
2026-01-05 14:57:30 +05:30
|
|
|
const pageState: SanitizePdfState = {
|
2025-12-11 19:34:14 +05:30
|
|
|
file: null,
|
|
|
|
|
pdfDoc: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function removeMetadataFromDoc(pdfDoc: PDFDocument) {
|
|
|
|
|
const infoDict = (pdfDoc as any).getInfoDict();
|
|
|
|
|
const allKeys = infoDict.keys();
|
|
|
|
|
allKeys.forEach((key: any) => {
|
|
|
|
|
infoDict.delete(key);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
pdfDoc.setTitle('');
|
|
|
|
|
pdfDoc.setAuthor('');
|
|
|
|
|
pdfDoc.setSubject('');
|
|
|
|
|
pdfDoc.setKeywords([]);
|
|
|
|
|
pdfDoc.setCreator('');
|
|
|
|
|
pdfDoc.setProducer('');
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
if (catalogDict.has(PDFName.of('Metadata'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('Metadata'));
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove XMP metadata:', e.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const context = pdfDoc.context;
|
|
|
|
|
if ((context as any).trailerInfo) {
|
|
|
|
|
delete (context as any).trailerInfo.ID;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove document IDs:', e.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
if (catalogDict.has(PDFName.of('PieceInfo'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('PieceInfo'));
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove PieceInfo:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function removeAnnotationsFromDoc(pdfDoc: PDFDocument) {
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
for (const page of pages) {
|
|
|
|
|
try {
|
|
|
|
|
page.node.delete(PDFName.of('Annots'));
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove annotations from page:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flattenFormsInDoc(pdfDoc: PDFDocument) {
|
|
|
|
|
const form = pdfDoc.getForm();
|
|
|
|
|
form.flatten();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetState() {
|
|
|
|
|
pageState.file = null;
|
|
|
|
|
pageState.pdfDoc = null;
|
|
|
|
|
|
|
|
|
|
const fileDisplayArea = document.getElementById('file-display-area');
|
|
|
|
|
if (fileDisplayArea) fileDisplayArea.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
const toolOptions = document.getElementById('tool-options');
|
|
|
|
|
if (toolOptions) toolOptions.classList.add('hidden');
|
|
|
|
|
|
|
|
|
|
const fileInput = document.getElementById('file-input') as HTMLInputElement;
|
|
|
|
|
if (fileInput) fileInput.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function updateUI() {
|
|
|
|
|
const fileDisplayArea = document.getElementById('file-display-area');
|
|
|
|
|
const toolOptions = document.getElementById('tool-options');
|
|
|
|
|
|
|
|
|
|
if (!fileDisplayArea) return;
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
|
|
|
|
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 metaSpan = document.createElement('div');
|
|
|
|
|
metaSpan.className = 'text-xs text-gray-400';
|
|
|
|
|
metaSpan.textContent = formatBytes(pageState.file.size);
|
|
|
|
|
|
|
|
|
|
infoContainer.append(nameSpan, metaSpan);
|
|
|
|
|
|
|
|
|
|
const removeBtn = document.createElement('button');
|
|
|
|
|
removeBtn.className = 'ml-4 text-red-400 hover:text-red-300 flex-shrink-0';
|
|
|
|
|
removeBtn.innerHTML = '<i data-lucide="trash-2" class="w-4 h-4"></i>';
|
|
|
|
|
removeBtn.onclick = function () {
|
|
|
|
|
resetState();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fileDiv.append(infoContainer, removeBtn);
|
|
|
|
|
fileDisplayArea.appendChild(fileDiv);
|
|
|
|
|
createIcons({ icons });
|
|
|
|
|
|
|
|
|
|
if (toolOptions) toolOptions.classList.remove('hidden');
|
|
|
|
|
} else {
|
|
|
|
|
if (toolOptions) toolOptions.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async 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;
|
|
|
|
|
|
|
|
|
|
// Load PDF for sanitization
|
|
|
|
|
try {
|
|
|
|
|
const arrayBuffer = await file.arrayBuffer();
|
|
|
|
|
pageState.pdfDoc = await PDFDocument.load(arrayBuffer);
|
|
|
|
|
updateUI();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Error loading PDF:', e);
|
|
|
|
|
showAlert('Error', 'Failed to load PDF file.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function sanitizePdf() {
|
|
|
|
|
if (!pageState.pdfDoc) {
|
|
|
|
|
showAlert('Error', 'No PDF document loaded.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loaderModal = document.getElementById('loader-modal');
|
|
|
|
|
const loaderText = document.getElementById('loader-text');
|
|
|
|
|
if (loaderModal) loaderModal.classList.remove('hidden');
|
|
|
|
|
if (loaderText) loaderText.textContent = 'Sanitizing PDF...';
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const pdfDoc = pageState.pdfDoc;
|
|
|
|
|
|
|
|
|
|
const shouldFlattenForms = (document.getElementById('flatten-forms') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveMetadata = (document.getElementById('remove-metadata') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveAnnotations = (document.getElementById('remove-annotations') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveJavascript = (document.getElementById('remove-javascript') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveEmbeddedFiles = (document.getElementById('remove-embedded-files') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveLayers = (document.getElementById('remove-layers') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveLinks = (document.getElementById('remove-links') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveStructureTree = (document.getElementById('remove-structure-tree') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveMarkInfo = (document.getElementById('remove-markinfo') as HTMLInputElement).checked;
|
|
|
|
|
const shouldRemoveFonts = (document.getElementById('remove-fonts') as HTMLInputElement).checked;
|
|
|
|
|
|
|
|
|
|
let changesMade = false;
|
|
|
|
|
|
|
|
|
|
if (shouldFlattenForms) {
|
|
|
|
|
try {
|
|
|
|
|
flattenFormsInDoc(pdfDoc);
|
|
|
|
|
changesMade = true;
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not flatten forms: ${e.message}`);
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
if (catalogDict.has(PDFName.of('AcroForm'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('AcroForm'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (removeError: any) {
|
|
|
|
|
console.warn('Could not remove AcroForm:', removeError.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveMetadata) {
|
|
|
|
|
removeMetadataFromDoc(pdfDoc);
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveAnnotations) {
|
|
|
|
|
removeAnnotationsFromDoc(pdfDoc);
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveJavascript) {
|
|
|
|
|
try {
|
|
|
|
|
if ((pdfDoc as any).javaScripts && (pdfDoc as any).javaScripts.length > 0) {
|
|
|
|
|
(pdfDoc as any).javaScripts = [];
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
|
|
|
|
|
const namesRef = catalogDict.get(PDFName.of('Names'));
|
|
|
|
|
if (namesRef) {
|
|
|
|
|
try {
|
|
|
|
|
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
|
|
|
|
if (namesDict.has(PDFName.of('JavaScript'))) {
|
|
|
|
|
namesDict.delete(PDFName.of('JavaScript'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not access Names/JavaScript:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('OpenAction'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('OpenAction'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('AA'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('AA'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
for (const page of pages) {
|
|
|
|
|
try {
|
|
|
|
|
const pageDict = page.node;
|
|
|
|
|
|
|
|
|
|
if (pageDict.has(PDFName.of('AA'))) {
|
|
|
|
|
pageDict.delete(PDFName.of('AA'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const annotRefs = pageDict.Annots()?.asArray() || [];
|
|
|
|
|
for (const annotRef of annotRefs) {
|
|
|
|
|
try {
|
|
|
|
|
const annot = pdfDoc.context.lookup(annotRef) as any;
|
|
|
|
|
|
|
|
|
|
if (annot.has(PDFName.of('A'))) {
|
|
|
|
|
const actionRef = annot.get(PDFName.of('A'));
|
|
|
|
|
try {
|
|
|
|
|
const actionDict = pdfDoc.context.lookup(actionRef) as any;
|
|
|
|
|
const actionType = actionDict
|
|
|
|
|
.get(PDFName.of('S'))
|
|
|
|
|
?.toString()
|
|
|
|
|
.substring(1);
|
|
|
|
|
|
|
|
|
|
if (actionType === 'JavaScript') {
|
|
|
|
|
annot.delete(PDFName.of('A'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not read action:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (annot.has(PDFName.of('AA'))) {
|
|
|
|
|
annot.delete(PDFName.of('AA'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not process annotation for JS:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove page actions:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const acroFormRef = catalogDict.get(PDFName.of('AcroForm'));
|
|
|
|
|
if (acroFormRef) {
|
|
|
|
|
const acroFormDict = pdfDoc.context.lookup(acroFormRef) as any;
|
|
|
|
|
const fieldsRef = acroFormDict.get(PDFName.of('Fields'));
|
|
|
|
|
|
|
|
|
|
if (fieldsRef) {
|
|
|
|
|
const fieldsArray = pdfDoc.context.lookup(fieldsRef) as any;
|
|
|
|
|
const fields = fieldsArray.asArray();
|
|
|
|
|
|
|
|
|
|
for (const fieldRef of fields) {
|
|
|
|
|
try {
|
|
|
|
|
const field = pdfDoc.context.lookup(fieldRef) as any;
|
|
|
|
|
|
|
|
|
|
if (field.has(PDFName.of('A'))) {
|
|
|
|
|
field.delete(PDFName.of('A'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (field.has(PDFName.of('AA'))) {
|
|
|
|
|
field.delete(PDFName.of('AA'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not process field for JS:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not process form fields for JS:', e.message);
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove JavaScript: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveEmbeddedFiles) {
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
|
|
|
|
|
const namesRef = catalogDict.get(PDFName.of('Names'));
|
|
|
|
|
if (namesRef) {
|
|
|
|
|
try {
|
|
|
|
|
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
|
|
|
|
if (namesDict.has(PDFName.of('EmbeddedFiles'))) {
|
|
|
|
|
namesDict.delete(PDFName.of('EmbeddedFiles'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not access Names/EmbeddedFiles:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('EmbeddedFiles'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('EmbeddedFiles'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
for (const page of pages) {
|
|
|
|
|
try {
|
|
|
|
|
const annotRefs = page.node.Annots()?.asArray() || [];
|
|
|
|
|
const annotsToKeep = [];
|
|
|
|
|
|
|
|
|
|
for (const ref of annotRefs) {
|
|
|
|
|
try {
|
|
|
|
|
const annot = pdfDoc.context.lookup(ref) as any;
|
|
|
|
|
const subtype = annot
|
|
|
|
|
.get(PDFName.of('Subtype'))
|
|
|
|
|
?.toString()
|
|
|
|
|
.substring(1);
|
|
|
|
|
|
|
|
|
|
if (subtype !== 'FileAttachment') {
|
|
|
|
|
annotsToKeep.push(ref);
|
|
|
|
|
} else {
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
annotsToKeep.push(ref);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (annotsToKeep.length !== annotRefs.length) {
|
|
|
|
|
if (annotsToKeep.length > 0) {
|
|
|
|
|
const newAnnotsArray = pdfDoc.context.obj(annotsToKeep);
|
|
|
|
|
page.node.set(PDFName.of('Annots'), newAnnotsArray);
|
|
|
|
|
} else {
|
|
|
|
|
page.node.delete(PDFName.of('Annots'));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (pageError: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`Could not process page for attachments: ${pageError.message}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((pdfDoc as any).embeddedFiles && (pdfDoc as any).embeddedFiles.length > 0) {
|
|
|
|
|
(pdfDoc as any).embeddedFiles = [];
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('Collection'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('Collection'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove embedded files: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveLayers) {
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('OCProperties'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('OCProperties'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
for (const page of pages) {
|
|
|
|
|
try {
|
|
|
|
|
const pageDict = page.node;
|
|
|
|
|
|
|
|
|
|
if (pageDict.has(PDFName.of('OCProperties'))) {
|
|
|
|
|
pageDict.delete(PDFName.of('OCProperties'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resourcesRef = pageDict.get(PDFName.of('Resources'));
|
|
|
|
|
if (resourcesRef) {
|
|
|
|
|
try {
|
|
|
|
|
const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any;
|
|
|
|
|
if (resourcesDict.has(PDFName.of('Properties'))) {
|
|
|
|
|
resourcesDict.delete(PDFName.of('Properties'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not access Resources:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove page layers:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove layers: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveLinks) {
|
|
|
|
|
try {
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
|
|
|
|
|
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
|
|
|
|
|
try {
|
|
|
|
|
const page = pages[pageIndex];
|
|
|
|
|
const pageDict = page.node;
|
|
|
|
|
|
|
|
|
|
const annotsRef = pageDict.get(PDFName.of('Annots'));
|
|
|
|
|
if (!annotsRef) continue;
|
|
|
|
|
|
|
|
|
|
const annotsArray = pdfDoc.context.lookup(annotsRef) as any;
|
|
|
|
|
const annotRefs = annotsArray.asArray();
|
|
|
|
|
|
|
|
|
|
if (annotRefs.length === 0) continue;
|
|
|
|
|
|
|
|
|
|
const annotsToKeep = [];
|
|
|
|
|
let linksRemoved = 0;
|
|
|
|
|
|
|
|
|
|
for (const ref of annotRefs) {
|
|
|
|
|
try {
|
|
|
|
|
const annot = pdfDoc.context.lookup(ref) as any;
|
|
|
|
|
const subtype = annot
|
|
|
|
|
.get(PDFName.of('Subtype'))
|
|
|
|
|
?.toString()
|
|
|
|
|
.substring(1);
|
|
|
|
|
|
|
|
|
|
let isLink = false;
|
|
|
|
|
|
|
|
|
|
if (subtype === 'Link') {
|
|
|
|
|
isLink = true;
|
|
|
|
|
linksRemoved++;
|
|
|
|
|
} else {
|
|
|
|
|
const actionRef = annot.get(PDFName.of('A'));
|
|
|
|
|
if (actionRef) {
|
|
|
|
|
try {
|
|
|
|
|
const actionDict = pdfDoc.context.lookup(actionRef) as any;
|
|
|
|
|
const actionType = actionDict
|
|
|
|
|
.get(PDFName.of('S'))
|
|
|
|
|
?.toString()
|
|
|
|
|
.substring(1);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
actionType === 'URI' ||
|
|
|
|
|
actionType === 'Launch' ||
|
|
|
|
|
actionType === 'GoTo' ||
|
|
|
|
|
actionType === 'GoToR'
|
|
|
|
|
) {
|
|
|
|
|
isLink = true;
|
|
|
|
|
linksRemoved++;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not read action:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dest = annot.get(PDFName.of('Dest'));
|
|
|
|
|
if (dest && !isLink) {
|
|
|
|
|
isLink = true;
|
|
|
|
|
linksRemoved++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isLink) {
|
|
|
|
|
annotsToKeep.push(ref);
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not process annotation:', e.message);
|
|
|
|
|
annotsToKeep.push(ref);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (linksRemoved > 0) {
|
|
|
|
|
if (annotsToKeep.length > 0) {
|
|
|
|
|
const newAnnotsArray = pdfDoc.context.obj(annotsToKeep);
|
|
|
|
|
pageDict.set(PDFName.of('Annots'), newAnnotsArray);
|
|
|
|
|
} else {
|
|
|
|
|
pageDict.delete(PDFName.of('Annots'));
|
|
|
|
|
}
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (pageError: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`Could not process page ${pageIndex + 1} for links: ${pageError.message}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
const namesRef = catalogDict.get(PDFName.of('Names'));
|
|
|
|
|
if (namesRef) {
|
|
|
|
|
try {
|
|
|
|
|
const namesDict = pdfDoc.context.lookup(namesRef) as any;
|
|
|
|
|
if (namesDict.has(PDFName.of('Dests'))) {
|
|
|
|
|
namesDict.delete(PDFName.of('Dests'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not access Names/Dests:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('Dests'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('Dests'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove named destinations:', e.message);
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove links: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveStructureTree) {
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('StructTreeRoot'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('StructTreeRoot'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
for (const page of pages) {
|
|
|
|
|
try {
|
|
|
|
|
const pageDict = page.node;
|
|
|
|
|
if (pageDict.has(PDFName.of('StructParents'))) {
|
|
|
|
|
pageDict.delete(PDFName.of('StructParents'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn('Could not remove page StructParents:', e.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('ParentTree'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('ParentTree'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove structure tree: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveMarkInfo) {
|
|
|
|
|
try {
|
|
|
|
|
const catalogDict = (pdfDoc.catalog as any).dict;
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('MarkInfo'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('MarkInfo'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (catalogDict.has(PDFName.of('Marked'))) {
|
|
|
|
|
catalogDict.delete(PDFName.of('Marked'));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove MarkInfo: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldRemoveFonts) {
|
|
|
|
|
try {
|
|
|
|
|
const pages = pdfDoc.getPages();
|
|
|
|
|
|
|
|
|
|
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
|
|
|
|
|
try {
|
|
|
|
|
const page = pages[pageIndex];
|
|
|
|
|
const pageDict = page.node;
|
|
|
|
|
const resourcesRef = pageDict.get(PDFName.of('Resources'));
|
|
|
|
|
|
|
|
|
|
if (resourcesRef) {
|
|
|
|
|
try {
|
|
|
|
|
const resourcesDict = pdfDoc.context.lookup(resourcesRef) as any;
|
|
|
|
|
|
|
|
|
|
if (resourcesDict.has(PDFName.of('Font'))) {
|
|
|
|
|
const fontRef = resourcesDict.get(PDFName.of('Font'));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const fontDict = pdfDoc.context.lookup(fontRef) as any;
|
|
|
|
|
const fontKeys = fontDict.keys();
|
|
|
|
|
|
|
|
|
|
for (const fontKey of fontKeys) {
|
|
|
|
|
try {
|
|
|
|
|
const specificFontRef = fontDict.get(fontKey);
|
|
|
|
|
const specificFont =
|
|
|
|
|
pdfDoc.context.lookup(specificFontRef) as any;
|
|
|
|
|
|
|
|
|
|
if (specificFont.has(PDFName.of('FontDescriptor'))) {
|
|
|
|
|
const descriptorRef = specificFont.get(
|
|
|
|
|
PDFName.of('FontDescriptor')
|
|
|
|
|
);
|
|
|
|
|
const descriptor =
|
|
|
|
|
pdfDoc.context.lookup(descriptorRef) as any;
|
|
|
|
|
|
|
|
|
|
const fontFileKeys = [
|
|
|
|
|
'FontFile',
|
|
|
|
|
'FontFile2',
|
|
|
|
|
'FontFile3',
|
|
|
|
|
];
|
|
|
|
|
for (const key of fontFileKeys) {
|
|
|
|
|
if (descriptor.has(PDFName.of(key))) {
|
|
|
|
|
descriptor.delete(PDFName.of(key));
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`Could not process font ${fontKey}:`,
|
|
|
|
|
e.message
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'Could not access font dictionary:',
|
|
|
|
|
e.message
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
'Could not access Resources for fonts:',
|
|
|
|
|
e.message
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(
|
|
|
|
|
`Could not remove fonts from page ${pageIndex + 1}:`,
|
|
|
|
|
e.message
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((pdfDoc as any).fonts && (pdfDoc as any).fonts.length > 0) {
|
|
|
|
|
(pdfDoc as any).fonts = [];
|
|
|
|
|
changesMade = true;
|
|
|
|
|
}
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.warn(`Could not remove fonts: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!changesMade) {
|
|
|
|
|
showAlert(
|
|
|
|
|
'No Changes',
|
|
|
|
|
'No items were selected for removal or none were found in the PDF.'
|
|
|
|
|
);
|
|
|
|
|
if (loaderModal) loaderModal.classList.add('hidden');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sanitizedPdfBytes = await pdfDoc.save();
|
|
|
|
|
downloadFile(
|
|
|
|
|
new Blob([sanitizedPdfBytes as BlobPart], { type: 'application/pdf' }),
|
|
|
|
|
'sanitized.pdf'
|
|
|
|
|
);
|
|
|
|
|
showAlert('Success', 'PDF has been sanitized and downloaded.', 'success', () => { resetState(); });
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.error('Sanitization Error:', e);
|
|
|
|
|
showAlert('Error', `An error occurred during sanitization: ${e.message}`);
|
|
|
|
|
} finally {
|
|
|
|
|
if (loaderModal) loaderModal.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
handleFileSelect(e.dataTransfer?.files);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fileInput.addEventListener('click', function () {
|
|
|
|
|
fileInput.value = '';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (processBtn) {
|
|
|
|
|
processBtn.addEventListener('click', sanitizePdf);
|
|
|
|
|
}
|
|
|
|
|
});
|