feat(pdfjs-viewer): enhance form viewer with print functionality and XFA support
Added a print button to the PDF viewer for direct printing. Implemented a scripting manager to handle XFA forms, ensuring proper rendering and interaction. Updated form-filler logic to support XFA detection and improved error handling for unsupported save operations. Refactored file handling to streamline PDF loading and viewer initialization.
This commit is contained in:
@@ -24,6 +24,17 @@ import * as pdfjsLib from 'pdfjs-dist';
|
||||
async function handleSinglePdfUpload(toolId, file) {
|
||||
showLoader('Loading PDF...');
|
||||
try {
|
||||
// pdf-lib does not support XFA, so we let pdf.js render it
|
||||
if (toolId === 'form-filler') {
|
||||
hideLoader();
|
||||
|
||||
const logic = toolLogic[toolId];
|
||||
if (logic && logic.setup) {
|
||||
await logic.setup();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const pdfBytes = await readFileAsArrayBuffer(file);
|
||||
state.pdfDoc = await PDFLibDocument.load(pdfBytes as ArrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
@@ -502,89 +513,89 @@ async function handleMultiFileUpload(toolId) {
|
||||
toolLogic.merge.setup();
|
||||
} else if (toolId === 'alternate-merge') {
|
||||
toolLogic['alternate-merge'].setup();
|
||||
} else if (toolId === 'image-to-pdf') {
|
||||
const imageList = document.getElementById('image-list');
|
||||
imageList.textContent = '';
|
||||
state.files.forEach((file) => {
|
||||
const url = URL.createObjectURL(file);
|
||||
const li = document.createElement('li');
|
||||
li.className = 'relative group cursor-move';
|
||||
li.dataset.fileName = file.name;
|
||||
} else if (toolId === 'image-to-pdf') {
|
||||
const imageList = document.getElementById('image-list');
|
||||
imageList.textContent = '';
|
||||
state.files.forEach((file) => {
|
||||
const url = URL.createObjectURL(file);
|
||||
const li = document.createElement('li');
|
||||
li.className = 'relative group cursor-move';
|
||||
li.dataset.fileName = file.name;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'w-full h-36 sm:h-40 md:h-44 bg-gray-900 rounded-md border-2 border-gray-600 flex items-center justify-center overflow-hidden';
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'w-full h-36 sm:h-40 md:h-44 bg-gray-900 rounded-md border-2 border-gray-600 flex items-center justify-center overflow-hidden';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.className = 'max-w-full max-h-full object-contain';
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.className = 'max-w-full max-h-full object-contain';
|
||||
|
||||
const p = document.createElement('p');
|
||||
p.className =
|
||||
'absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white text-xs text-center truncate p-1';
|
||||
p.textContent = file.name;
|
||||
const p = document.createElement('p');
|
||||
p.className =
|
||||
'absolute bottom-0 left-0 right-0 bg-black bg-opacity-50 text-white text-xs text-center truncate p-1';
|
||||
p.textContent = file.name;
|
||||
|
||||
wrapper.appendChild(img);
|
||||
li.append(wrapper, p);
|
||||
imageList.appendChild(li);
|
||||
});
|
||||
wrapper.appendChild(img);
|
||||
li.append(wrapper, p);
|
||||
imageList.appendChild(li);
|
||||
});
|
||||
|
||||
Sortable.create(imageList);
|
||||
// Show image-to-pdf options and wire up slider text
|
||||
const opts = document.getElementById('image-to-pdf-options');
|
||||
if (opts) {
|
||||
opts.classList.remove('hidden');
|
||||
const slider = document.getElementById('image-pdf-quality') as HTMLInputElement;
|
||||
const value = document.getElementById('image-pdf-quality-value');
|
||||
if (slider && value) {
|
||||
const update = () => (value.textContent = `${Math.round(parseFloat(slider.value) * 100)}%`);
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
}
|
||||
Sortable.create(imageList);
|
||||
// Show image-to-pdf options and wire up slider text
|
||||
const opts = document.getElementById('image-to-pdf-options');
|
||||
if (opts) {
|
||||
opts.classList.remove('hidden');
|
||||
const slider = document.getElementById('image-pdf-quality') as HTMLInputElement;
|
||||
const value = document.getElementById('image-pdf-quality-value');
|
||||
if (slider && value) {
|
||||
const update = () => (value.textContent = `${Math.round(parseFloat(slider.value) * 100)}%`);
|
||||
slider.addEventListener('input', update);
|
||||
update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toolId === 'pdf-to-jpg') {
|
||||
const qualitySlider = document.getElementById('jpg-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('jpg-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
if (toolId === 'pdf-to-jpg') {
|
||||
const qualitySlider = document.getElementById('jpg-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('jpg-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolId === 'pdf-to-png') {
|
||||
const qualitySlider = document.getElementById('png-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('png-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${qualitySlider.value}x`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
if (toolId === 'pdf-to-png') {
|
||||
const qualitySlider = document.getElementById('png-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('png-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${qualitySlider.value}x`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolId === 'pdf-to-webp') {
|
||||
const qualitySlider = document.getElementById('webp-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('webp-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
if (toolId === 'pdf-to-webp') {
|
||||
const qualitySlider = document.getElementById('webp-quality') as HTMLInputElement;
|
||||
const qualityValue = document.getElementById('webp-quality-value');
|
||||
if (qualitySlider && qualityValue) {
|
||||
const updateValue = () => {
|
||||
qualityValue.textContent = `${Math.round(parseFloat(qualitySlider.value) * 100)}%`;
|
||||
};
|
||||
qualitySlider.addEventListener('input', updateValue);
|
||||
updateValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (toolId === 'jpg-to-pdf' || toolId === 'png-to-pdf') {
|
||||
const optionsDiv = document.getElementById(`${toolId}-options`);
|
||||
if (optionsDiv) {
|
||||
optionsDiv.classList.remove('hidden');
|
||||
}
|
||||
if (toolId === 'jpg-to-pdf' || toolId === 'png-to-pdf') {
|
||||
const optionsDiv = document.getElementById(`${toolId}-options`);
|
||||
if (optionsDiv) {
|
||||
optionsDiv.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function setupFileInputHandler(toolId) {
|
||||
|
||||
@@ -20,38 +20,25 @@ export async function setupFormFiller() {
|
||||
|
||||
try {
|
||||
pdfViewerContainer.innerHTML = '';
|
||||
|
||||
const file = state.files[0];
|
||||
const arrayBuffer = await readFileAsArrayBuffer(file);
|
||||
const blob = new Blob([arrayBuffer as ArrayBuffer], { type: 'application/pdf' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
|
||||
viewerIframe = document.createElement('iframe');
|
||||
viewerIframe.src = '/pdfjs-viewer/form-viewer.html';
|
||||
viewerIframe.src = `/pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}`;
|
||||
viewerIframe.style.width = '100%';
|
||||
viewerIframe.style.height = '100%';
|
||||
viewerIframe.style.border = 'none';
|
||||
|
||||
viewerIframe.onload = () => {
|
||||
viewerReady = true;
|
||||
hideLoader();
|
||||
};
|
||||
|
||||
pdfViewerContainer.appendChild(viewerIframe);
|
||||
|
||||
window.addEventListener('message', async (event) => {
|
||||
if (event.data.type === 'viewerReady') {
|
||||
viewerReady = true;
|
||||
// Use the original uploaded bytes so that XFA streams remain intact
|
||||
// and PDF.js can fully render XFA-based forms.
|
||||
const file = state.files[0];
|
||||
const pdfBytes = await readFileAsArrayBuffer(file);
|
||||
viewerIframe?.contentWindow?.postMessage(
|
||||
{ type: 'loadPDF', data: pdfBytes },
|
||||
'*'
|
||||
);
|
||||
} else if (event.data.type === 'pdfLoaded') {
|
||||
hideLoader();
|
||||
} else if (event.data.type === 'downloadPDF') {
|
||||
const pdfData = new Uint8Array(event.data.data);
|
||||
downloadFile(
|
||||
new Blob([pdfData], { type: 'application/pdf' }),
|
||||
'filled-form.pdf'
|
||||
);
|
||||
showAlert('Success', 'Form has been filled and downloaded.');
|
||||
} else if (event.data.type === 'error') {
|
||||
showAlert('Error', event.data.message);
|
||||
}
|
||||
});
|
||||
|
||||
const formFillerOptions = document.getElementById('form-filler-options');
|
||||
if (formFillerOptions) formFillerOptions.classList.remove('hidden');
|
||||
} catch (e) {
|
||||
@@ -70,21 +57,10 @@ export async function processAndDownloadForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const win: any = viewerIframe.contentWindow;
|
||||
const doc: Document | null = win?.document ?? null;
|
||||
|
||||
// Prefer to trigger the same behavior as the toolbar's Download button
|
||||
const downloadBtn = doc?.getElementById('download') as HTMLButtonElement | null;
|
||||
if (downloadBtn) {
|
||||
downloadBtn.click();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: use the postMessage-based getData flow
|
||||
win?.postMessage({ type: 'getData' }, '*');
|
||||
} catch (e) {
|
||||
console.error('Failed to trigger form download:', e);
|
||||
showAlert('Export failed', 'Could not export the filled form. Please try again.');
|
||||
}
|
||||
// The full PDF.js viewer has its own download button in the toolbar
|
||||
// Users can use that to download or the print button to print to PDF
|
||||
showAlert(
|
||||
'Download Form',
|
||||
'Use the Download button in the PDF viewer toolbar above, or use Print to save as PDF.'
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user