fix: replace iframe PDF rendering with direct canvas in form creator

This commit is contained in:
alam00000
2026-04-16 22:40:00 +05:30
parent 651793bffa
commit 75dad6e75d
2 changed files with 83 additions and 200 deletions

View File

@@ -18,14 +18,6 @@ type LucideWindow = Window & {
createIcons(): void; createIcons(): void;
}; };
}; };
type PdfViewerApplicationLike = {
pdfViewer?: {
pagesCount: number;
};
};
type PdfViewerWindow = Window & {
PDFViewerApplication?: PdfViewerApplicationLike;
};
import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js';
import { downloadFile, hexToRgb } from '../utils/helpers.js'; import { downloadFile, hexToRgb } from '../utils/helpers.js';
@@ -68,8 +60,6 @@ let uploadedPdfjsDoc: PDFDocumentProxy | null = null;
let uploadedFileName: string | null = null; let uploadedFileName: string | null = null;
let pageSize: { width: number; height: number } = { width: 612, height: 792 }; let pageSize: { width: number; height: number } = { width: 612, height: 792 };
let currentScale = 1.333; let currentScale = 1.333;
let pdfViewerOffset = { x: 0, y: 0 };
let pdfViewerScale = 1.333;
let resizing = false; let resizing = false;
let resizeField: FormField | null = null; let resizeField: FormField | null = null;
@@ -2228,23 +2218,11 @@ downloadBtn.addEventListener('click', async () => {
const pdfPage = pdfDoc.getPage(field.pageIndex); const pdfPage = pdfDoc.getPage(field.pageIndex);
const { height: pageHeight } = pdfPage.getSize(); const { height: pageHeight } = pdfPage.getSize();
const scaleX = 1 / pdfViewerScale; const x = field.x / currentScale;
const scaleY = 1 / pdfViewerScale; const y =
pageHeight - field.y / currentScale - field.height / currentScale;
const adjustedX = field.x - pdfViewerOffset.x; const width = field.width / currentScale;
const adjustedY = field.y - pdfViewerOffset.y; const height = field.height / currentScale;
const x = adjustedX * scaleX;
const y = pageHeight - adjustedY * scaleY - field.height * scaleY;
const width = field.width * scaleX;
const height = field.height * scaleY;
console.log(`Field "${field.name}":`, {
screenPos: { x: field.x, y: field.y },
adjustedPos: { x: adjustedX, y: adjustedY },
pdfPos: { x, y, width, height },
metrics: { offset: pdfViewerOffset, scale: pdfViewerScale },
});
if (field.type === 'text') { if (field.type === 'text') {
const textField = form.createTextField(field.name); const textField = form.createTextField(field.name);
@@ -2862,141 +2840,34 @@ async function renderCanvas(): Promise<void> {
canvas.innerHTML = ''; canvas.innerHTML = '';
if (uploadedPdfDoc) { if (uploadedPdfjsDoc) {
try { try {
const arrayBuffer = await uploadedPdfDoc.save(); const pdfjsPage = await uploadedPdfjsDoc.getPage(currentPageIndex + 1);
const blob = new Blob([arrayBuffer.buffer as ArrayBuffer], { const viewport = pdfjsPage.getViewport({ scale: currentScale });
type: 'application/pdf',
});
const blobUrl = URL.createObjectURL(blob);
const iframe = document.createElement('iframe'); const pageCanvas = document.createElement('canvas');
iframe.src = `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}#page=${currentPageIndex + 1}&toolbar=0`; pageCanvas.width = viewport.width;
iframe.style.width = '100%'; pageCanvas.height = viewport.height;
iframe.style.height = `${canvasHeight}px`; pageCanvas.style.position = 'absolute';
iframe.style.border = 'none'; pageCanvas.style.top = '0';
iframe.style.position = 'absolute'; pageCanvas.style.left = '0';
iframe.style.top = '0'; pageCanvas.style.pointerEvents = 'none';
iframe.style.left = '0';
iframe.style.pointerEvents = 'none';
iframe.style.opacity = '0.8';
iframe.onload = () => { const ctx = pageCanvas.getContext('2d');
try { if (ctx) {
const viewerWindow = iframe.contentWindow as PdfViewerWindow | null; await pdfjsPage.render({
if (viewerWindow && viewerWindow.PDFViewerApplication) { canvasContext: ctx,
const app = viewerWindow.PDFViewerApplication; viewport,
canvas: pageCanvas,
const style = viewerWindow.document.createElement('style'); }).promise;
style.textContent = `
* {
margin: 0 !important;
padding: 0 !important;
} }
html, body {
margin: 0 !important;
padding: 0 !important;
background-color: transparent !important;
overflow: hidden !important;
}
#toolbarContainer {
display: none !important;
}
#mainContainer {
top: 0 !important;
position: absolute !important;
left: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
#outerContainer {
background-color: transparent !important;
margin: 0 !important;
padding: 0 !important;
}
#viewerContainer {
top: 0 !important;
background-color: transparent !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
}
.toolbar {
display: none !important;
}
.pdfViewer {
padding: 0 !important;
margin: 0 !important;
}
.page {
margin: 0 !important;
padding: 0 !important;
border: none !important;
box-shadow: none !important;
}
`;
viewerWindow.document.head.appendChild(style);
const checkRender = setInterval(() => { canvas.appendChild(pageCanvas);
if (app.pdfViewer && app.pdfViewer.pagesCount > 0) {
clearInterval(checkRender);
const pageContainer =
viewerWindow.document.querySelector<HTMLElement>('.page');
if (pageContainer) {
const initialRect = pageContainer.getBoundingClientRect();
const offsetX = -initialRect.left;
const offsetY = -initialRect.top;
pageContainer.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
setTimeout(() => {
const rect = pageContainer.getBoundingClientRect();
const style = viewerWindow.getComputedStyle(pageContainer);
const borderLeft = parseFloat(style.borderLeftWidth) || 0;
const borderTop = parseFloat(style.borderTopWidth) || 0;
const borderRight = parseFloat(style.borderRightWidth) || 0;
pdfViewerOffset = {
x: rect.left + borderLeft,
y: rect.top + borderTop,
};
const contentWidth = rect.width - borderLeft - borderRight;
pdfViewerScale = contentWidth / currentPage.width;
console.log('📏 Calibrated Metrics (force positioned):', {
initialPosition: {
left: initialRect.left,
top: initialRect.top,
},
appliedTransform: { x: offsetX, y: offsetY },
finalRect: {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
},
computedBorders: {
left: borderLeft,
top: borderTop,
right: borderRight,
},
finalOffset: pdfViewerOffset,
finalScale: pdfViewerScale,
pdfDimensions: {
width: currentPage.width,
height: currentPage.height,
},
});
if (pendingFieldExtraction && uploadedPdfDoc) { if (pendingFieldExtraction && uploadedPdfDoc) {
pendingFieldExtraction = false; pendingFieldExtraction = false;
extractExistingFields(uploadedPdfDoc); extractExistingFields(uploadedPdfDoc);
extractedFieldNames.forEach((name) => extractedFieldNames.forEach((name) => existingFieldNames.delete(name));
existingFieldNames.delete(name)
);
const form = uploadedPdfDoc.getForm(); const form = uploadedPdfDoc.getForm();
for (const name of extractedFieldNames) { for (const name of extractedFieldNames) {
@@ -3016,27 +2887,6 @@ async function renderCanvas(): Promise<void> {
renderCanvas(); renderCanvas();
updateFieldCount(); updateFieldCount();
} }
}, 50);
}
}
}, 100);
}
} catch (e) {
console.error('Error accessing iframe content:', e);
}
};
canvas.appendChild(iframe);
console.log('Canvas dimensions:', {
width: canvasWidth,
height: canvasHeight,
scale: currentScale,
});
console.log('PDF page dimensions:', {
width: currentPage.width,
height: currentPage.height,
});
} catch (error) { } catch (error) {
console.error('Error rendering PDF:', error); console.error('Error rendering PDF:', error);
} }
@@ -3115,8 +2965,8 @@ function extractExistingFields(pdfDoc: PDFDocument): void {
pdfDoc, pdfDoc,
fieldCounterStart: fieldCounter, fieldCounterStart: fieldCounter,
metrics: { metrics: {
pdfViewerOffset, pdfViewerOffset: { x: 0, y: 0 },
pdfViewerScale, pdfViewerScale: currentScale,
}, },
}); });

View File

@@ -13,9 +13,11 @@ import {
import { extractExistingFields } from '../js/logic/form-creator-extraction.ts'; import { extractExistingFields } from '../js/logic/form-creator-extraction.ts';
import type { ExtractedFieldLike } from '@/types'; import type { ExtractedFieldLike } from '@/types';
const FORM_CREATOR_SCALE = 1.333;
const TEST_EXTRACTION_METRICS = { const TEST_EXTRACTION_METRICS = {
pdfViewerOffset: { x: 0, y: 0 }, pdfViewerOffset: { x: 0, y: 0 },
pdfViewerScale: 1, pdfViewerScale: FORM_CREATOR_SCALE,
}; };
function extractFieldsForTest(pdfDoc: PDFDocument): ExtractedFieldLike[] { function extractFieldsForTest(pdfDoc: PDFDocument): ExtractedFieldLike[] {
@@ -377,12 +379,13 @@ describe('form creator extraction regression', () => {
const field = extracted.find((entry) => entry.name === 'page2TextField'); const field = extracted.find((entry) => entry.name === 'page2TextField');
expect(field).toBeDefined(); expect(field).toBeDefined();
const pageHeight = pdfDoc.getPages()[1].getSize().height;
expect(field).toMatchObject({ expect(field).toMatchObject({
pageIndex: 1, pageIndex: 1,
x: rect.x, x: rect.x * FORM_CREATOR_SCALE,
y: pdfDoc.getPages()[1].getSize().height - rect.y - rect.height, y: (pageHeight - rect.y - rect.height) * FORM_CREATOR_SCALE,
width: rect.width, width: rect.width * FORM_CREATOR_SCALE,
height: rect.height, height: rect.height * FORM_CREATOR_SCALE,
}); });
}); });
@@ -411,4 +414,34 @@ describe('form creator extraction regression', () => {
expect(pageMap.get('page1ExtraField')).toBe(0); expect(pageMap.get('page1ExtraField')).toBe(0);
expect(pageMap.get('page2TextField')).toBe(1); expect(pageMap.get('page2TextField')).toBe(1);
}); });
it('round-trips extracted canvas coords back to original PDF coords within 1pt', async () => {
const { pdfDoc } = await buildTwoPageTextFieldPdf();
const page = pdfDoc.getPages()[0];
const { height: pageHeight } = page.getSize();
const originalWidget = pdfDoc
.getForm()
.getTextField('page1TextField')
.acroField.getWidgets()[0];
const originalRect = originalWidget.getRectangle();
const extracted = extractFieldsForTest(pdfDoc);
const field = extracted.find((f) => f.name === 'page1TextField');
expect(field).toBeDefined();
const pdfX = field!.x / FORM_CREATOR_SCALE;
const pdfY =
pageHeight -
field!.y / FORM_CREATOR_SCALE -
field!.height / FORM_CREATOR_SCALE;
const pdfW = field!.width / FORM_CREATOR_SCALE;
const pdfH = field!.height / FORM_CREATOR_SCALE;
expect(pdfX).toBeCloseTo(originalRect.x, 0);
expect(pdfY).toBeCloseTo(originalRect.y, 0);
expect(pdfW).toBeCloseTo(originalRect.width, 0);
expect(pdfH).toBeCloseTo(originalRect.height, 0);
});
}); });