2026-01-07 16:55:12 +05:30
|
|
|
|
<!doctype html>
|
2025-11-14 10:12:53 +05:30
|
|
|
|
<html dir="ltr" mozdisallowselectionprint>
|
2026-01-07 16:55:12 +05:30
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8" />
|
|
|
|
|
|
<meta
|
|
|
|
|
|
name="viewport"
|
|
|
|
|
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<meta name="google" content="notranslate" />
|
|
|
|
|
|
<title>PDF Form Filler - Bento PDF</title>
|
|
|
|
|
|
<link rel="stylesheet" href="pdf_viewer.css" />
|
|
|
|
|
|
<link rel="stylesheet" href="viewer.css" />
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
font-family: sans-serif;
|
|
|
|
|
|
background-color: #525252;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#viewerContainer {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
background-color: #404040;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
height: 48px;
|
|
|
|
|
|
background-color: #323639;
|
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar button {
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar button:hover {
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar button:disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar .page-info {
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
margin: 0 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar input[type='number'] {
|
|
|
|
|
|
width: 60px;
|
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#viewerContainer {
|
|
|
|
|
|
top: 48px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#viewer {
|
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page {
|
|
|
|
|
|
margin: 10px auto;
|
|
|
|
|
|
border: 1px solid #999;
|
|
|
|
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar .spacer {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar .zoom-controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 5px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar select {
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toolbar select option {
|
|
|
|
|
|
background-color: #323639;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Print styles */
|
|
|
|
|
|
/* @media print {
|
2025-11-15 22:09:17 +05:30
|
|
|
|
.toolbar {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#viewerContainer {
|
|
|
|
|
|
position: static !important;
|
|
|
|
|
|
overflow: visible !important;
|
|
|
|
|
|
background: white !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page {
|
|
|
|
|
|
page-break-after: always;
|
|
|
|
|
|
page-break-inside: avoid;
|
|
|
|
|
|
margin: 0 auto !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
} */
|
2026-01-07 16:55:12 +05:30
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<button id="previousPage" title="Previous Page">‹</button>
|
|
|
|
|
|
<input type="number" id="pageNumber" min="1" value="1" />
|
|
|
|
|
|
<span class="page-info">/ <span id="numPages">--</span></span>
|
|
|
|
|
|
<button id="nextPage" title="Next Page">›</button>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="spacer"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="zoom-controls">
|
|
|
|
|
|
<button id="zoomOut" title="Zoom Out">−</button>
|
|
|
|
|
|
<select id="scaleSelect">
|
|
|
|
|
|
<option value="auto">Auto</option>
|
|
|
|
|
|
<option value="page-fit">Fit Page</option>
|
|
|
|
|
|
<option value="page-width">Fit Width</option>
|
|
|
|
|
|
<option value="0.5">50%</option>
|
|
|
|
|
|
<option value="0.75">75%</option>
|
|
|
|
|
|
<option value="1" selected>100%</option>
|
|
|
|
|
|
<option value="1.25">125%</option>
|
|
|
|
|
|
<option value="1.5">150%</option>
|
|
|
|
|
|
<option value="2">200%</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<button id="zoomIn" title="Zoom In">+</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<button id="print" title="Print to PDF">Print to PDF</button>
|
|
|
|
|
|
<button id="download" title="Save & Download">Download</button>
|
2025-11-14 10:12:53 +05:30
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-01-07 16:55:12 +05:30
|
|
|
|
<div id="viewerContainer">
|
|
|
|
|
|
<div id="viewer" class="pdfViewer"></div>
|
|
|
|
|
|
</div>
|
2025-11-14 10:12:53 +05:30
|
|
|
|
|
2026-01-07 16:55:12 +05:30
|
|
|
|
<script type="module">
|
|
|
|
|
|
import * as pdfjsLib from './pdf.mjs';
|
|
|
|
|
|
import {
|
|
|
|
|
|
EventBus,
|
|
|
|
|
|
PDFViewer,
|
|
|
|
|
|
PDFLinkService,
|
|
|
|
|
|
PDFScriptingManager,
|
|
|
|
|
|
} from './pdf_viewer.mjs';
|
2025-11-14 10:12:53 +05:30
|
|
|
|
|
2026-01-07 16:55:12 +05:30
|
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.mjs';
|
|
|
|
|
|
|
|
|
|
|
|
const eventBus = new EventBus();
|
|
|
|
|
|
const linkService = new PDFLinkService({ eventBus });
|
|
|
|
|
|
|
|
|
|
|
|
// Create scripting manager for XFA forms with JavaScript
|
|
|
|
|
|
const scriptingManager = new PDFScriptingManager({
|
|
|
|
|
|
eventBus,
|
|
|
|
|
|
// Use the packaged sandbox for PDF.js JavaScript (required for XFA)
|
|
|
|
|
|
sandboxBundleSrc: './pdf.sandbox.mjs',
|
|
|
|
|
|
docProperties: async (pdfDocument) => {
|
|
|
|
|
|
// Minimal doc properties are fine; XFA scripts mostly need field objects
|
|
|
|
|
|
return {
|
|
|
|
|
|
title: '',
|
|
|
|
|
|
author: '',
|
|
|
|
|
|
subject: '',
|
|
|
|
|
|
keywords: '',
|
|
|
|
|
|
creator: '',
|
|
|
|
|
|
producer: '',
|
|
|
|
|
|
creationDate: null,
|
|
|
|
|
|
modDate: null,
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const pdfViewer = new PDFViewer({
|
|
|
|
|
|
container: document.getElementById('viewerContainer'),
|
|
|
|
|
|
viewer: document.getElementById('viewer'),
|
|
|
|
|
|
eventBus,
|
|
|
|
|
|
linkService,
|
|
|
|
|
|
scriptingManager,
|
|
|
|
|
|
renderer: 'canvas',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
linkService.setViewer(pdfViewer);
|
|
|
|
|
|
scriptingManager.setViewer(pdfViewer);
|
|
|
|
|
|
|
|
|
|
|
|
let pdfDocument = null;
|
|
|
|
|
|
let currentScale = 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
// Listen for messages from parent window
|
|
|
|
|
|
window.addEventListener('message', async (event) => {
|
|
|
|
|
|
if (event.data.type === 'loadPDF') {
|
|
|
|
|
|
const { data } = event.data;
|
|
|
|
|
|
loadPDF(data);
|
|
|
|
|
|
} else if (event.data.type === 'getData') {
|
|
|
|
|
|
const pdfData = await getPDFData();
|
|
|
|
|
|
window.parent.postMessage({ type: 'pdfData', data: pdfData }, '*');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
async function loadPDF(data) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const loadingTask = pdfjsLib.getDocument({
|
|
|
|
|
|
data,
|
|
|
|
|
|
enableXfa: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
pdfDocument = await loadingTask.promise;
|
|
|
|
|
|
// Wire scripting to the loaded document so XFA/JS executes
|
|
|
|
|
|
await scriptingManager.setDocument(pdfDocument);
|
|
|
|
|
|
pdfViewer.setDocument(pdfDocument);
|
|
|
|
|
|
linkService.setDocument(pdfDocument);
|
2025-11-14 10:12:53 +05:30
|
|
|
|
|
2026-01-07 16:55:12 +05:30
|
|
|
|
document.getElementById('numPages').textContent =
|
|
|
|
|
|
pdfDocument.numPages;
|
|
|
|
|
|
document.getElementById('pageNumber').max = pdfDocument.numPages;
|
|
|
|
|
|
|
|
|
|
|
|
// Notify parent that PDF is loaded
|
|
|
|
|
|
window.parent.postMessage(
|
|
|
|
|
|
{ type: 'pdfLoaded', numPages: pdfDocument.numPages },
|
|
|
|
|
|
'*'
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error loading PDF:', error);
|
|
|
|
|
|
window.parent.postMessage(
|
|
|
|
|
|
{ type: 'error', message: error.message },
|
|
|
|
|
|
'*'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-11-14 10:12:53 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-07 16:55:12 +05:30
|
|
|
|
async function getPDFData() {
|
|
|
|
|
|
if (!pdfDocument) return null;
|
|
|
|
|
|
|
|
|
|
|
|
// Check if this is an XFA form
|
|
|
|
|
|
if (pdfDocument.isPureXfa) {
|
|
|
|
|
|
console.warn(
|
|
|
|
|
|
'Pure XFA form detected - saving with filled data is not supported'
|
|
|
|
|
|
);
|
|
|
|
|
|
window.parent.postMessage(
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'error',
|
|
|
|
|
|
message:
|
|
|
|
|
|
'XFA forms cannot be saved with filled data. Please print to PDF instead.',
|
|
|
|
|
|
},
|
|
|
|
|
|
'*'
|
|
|
|
|
|
);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// For AcroForms, saveDocument() should work
|
|
|
|
|
|
const data = await pdfDocument.saveDocument();
|
|
|
|
|
|
return data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Error saving PDF document:', error);
|
|
|
|
|
|
window.parent.postMessage(
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'error',
|
|
|
|
|
|
message:
|
|
|
|
|
|
'Failed to save PDF. This may be an XFA form or protected PDF.',
|
|
|
|
|
|
},
|
|
|
|
|
|
'*'
|
|
|
|
|
|
);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-11-14 10:12:53 +05:30
|
|
|
|
}
|
2026-01-07 16:55:12 +05:30
|
|
|
|
|
|
|
|
|
|
// Navigation controls
|
|
|
|
|
|
document.getElementById('previousPage').addEventListener('click', () => {
|
|
|
|
|
|
if (pdfViewer.currentPageNumber > 1) {
|
|
|
|
|
|
pdfViewer.currentPageNumber--;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('nextPage').addEventListener('click', () => {
|
|
|
|
|
|
if (pdfViewer.currentPageNumber < pdfDocument?.numPages) {
|
|
|
|
|
|
pdfViewer.currentPageNumber++;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('pageNumber').addEventListener('change', (e) => {
|
|
|
|
|
|
const pageNum = parseInt(e.target.value);
|
|
|
|
|
|
if (pageNum >= 1 && pageNum <= pdfDocument?.numPages) {
|
|
|
|
|
|
pdfViewer.currentPageNumber = pageNum;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Zoom controls
|
|
|
|
|
|
document.getElementById('zoomIn').addEventListener('click', () => {
|
|
|
|
|
|
currentScale = Math.min(currentScale + 0.1, 3.0);
|
2025-11-14 10:12:53 +05:30
|
|
|
|
pdfViewer.currentScale = currentScale;
|
2026-01-07 16:55:12 +05:30
|
|
|
|
document.getElementById('scaleSelect').value = currentScale.toFixed(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('zoomOut').addEventListener('click', () => {
|
|
|
|
|
|
currentScale = Math.max(currentScale - 0.1, 0.1);
|
|
|
|
|
|
pdfViewer.currentScale = currentScale;
|
|
|
|
|
|
document.getElementById('scaleSelect').value = currentScale.toFixed(2);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('scaleSelect').addEventListener('change', (e) => {
|
|
|
|
|
|
const value = e.target.value;
|
|
|
|
|
|
if (
|
|
|
|
|
|
value === 'auto' ||
|
|
|
|
|
|
value === 'page-fit' ||
|
|
|
|
|
|
value === 'page-width'
|
|
|
|
|
|
) {
|
|
|
|
|
|
pdfViewer.currentScaleValue = value;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
currentScale = parseFloat(value);
|
|
|
|
|
|
pdfViewer.currentScale = currentScale;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Print button - trigger browser print
|
|
|
|
|
|
document.getElementById('print').addEventListener('click', () => {
|
|
|
|
|
|
if (!pdfDocument) return;
|
|
|
|
|
|
window.print();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Download button
|
|
|
|
|
|
document
|
|
|
|
|
|
.getElementById('download')
|
|
|
|
|
|
.addEventListener('click', async () => {
|
|
|
|
|
|
const data = await getPDFData();
|
|
|
|
|
|
if (data) {
|
|
|
|
|
|
window.parent.postMessage(
|
|
|
|
|
|
{ type: 'downloadPDF', data: Array.from(data) },
|
|
|
|
|
|
'*'
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Update page number display when page changes
|
|
|
|
|
|
eventBus.on('pagechanging', (evt) => {
|
|
|
|
|
|
document.getElementById('pageNumber').value = evt.pageNumber;
|
|
|
|
|
|
document.getElementById('previousPage').disabled = evt.pageNumber <= 1;
|
|
|
|
|
|
document.getElementById('nextPage').disabled =
|
|
|
|
|
|
evt.pageNumber >= pdfDocument?.numPages;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Notify parent that viewer is ready
|
|
|
|
|
|
window.parent.postMessage({ type: 'viewerReady' }, '*');
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
2025-11-14 10:12:53 +05:30
|
|
|
|
</html>
|