From 75dad6e75d8cba53cae388b82267e7afe771b4c6 Mon Sep 17 00:00:00 2001 From: alam00000 Date: Thu, 16 Apr 2026 22:40:00 +0530 Subject: [PATCH] fix: replace iframe PDF rendering with direct canvas in form creator --- src/js/logic/form-creator.ts | 240 ++++------------------ src/tests/form-creator-extraction.test.ts | 43 +++- 2 files changed, 83 insertions(+), 200 deletions(-) diff --git a/src/js/logic/form-creator.ts b/src/js/logic/form-creator.ts index 603970b..5acf3a6 100644 --- a/src/js/logic/form-creator.ts +++ b/src/js/logic/form-creator.ts @@ -18,14 +18,6 @@ type LucideWindow = Window & { createIcons(): void; }; }; -type PdfViewerApplicationLike = { - pdfViewer?: { - pagesCount: number; - }; -}; -type PdfViewerWindow = Window & { - PDFViewerApplication?: PdfViewerApplicationLike; -}; import { initializeGlobalShortcuts } from '../utils/shortcuts-init.js'; import { downloadFile, hexToRgb } from '../utils/helpers.js'; @@ -68,8 +60,6 @@ let uploadedPdfjsDoc: PDFDocumentProxy | null = null; let uploadedFileName: string | null = null; let pageSize: { width: number; height: number } = { width: 612, height: 792 }; let currentScale = 1.333; -let pdfViewerOffset = { x: 0, y: 0 }; -let pdfViewerScale = 1.333; let resizing = false; let resizeField: FormField | null = null; @@ -2228,23 +2218,11 @@ downloadBtn.addEventListener('click', async () => { const pdfPage = pdfDoc.getPage(field.pageIndex); const { height: pageHeight } = pdfPage.getSize(); - const scaleX = 1 / pdfViewerScale; - const scaleY = 1 / pdfViewerScale; - - const adjustedX = field.x - pdfViewerOffset.x; - const adjustedY = field.y - pdfViewerOffset.y; - - 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 }, - }); + const x = field.x / currentScale; + const y = + pageHeight - field.y / currentScale - field.height / currentScale; + const width = field.width / currentScale; + const height = field.height / currentScale; if (field.type === 'text') { const textField = form.createTextField(field.name); @@ -2862,181 +2840,53 @@ async function renderCanvas(): Promise { canvas.innerHTML = ''; - if (uploadedPdfDoc) { + if (uploadedPdfjsDoc) { try { - const arrayBuffer = await uploadedPdfDoc.save(); - const blob = new Blob([arrayBuffer.buffer as ArrayBuffer], { - type: 'application/pdf', - }); - const blobUrl = URL.createObjectURL(blob); + const pdfjsPage = await uploadedPdfjsDoc.getPage(currentPageIndex + 1); + const viewport = pdfjsPage.getViewport({ scale: currentScale }); - const iframe = document.createElement('iframe'); - iframe.src = `${import.meta.env.BASE_URL}pdfjs-viewer/viewer.html?file=${encodeURIComponent(blobUrl)}#page=${currentPageIndex + 1}&toolbar=0`; - iframe.style.width = '100%'; - iframe.style.height = `${canvasHeight}px`; - iframe.style.border = 'none'; - iframe.style.position = 'absolute'; - iframe.style.top = '0'; - iframe.style.left = '0'; - iframe.style.pointerEvents = 'none'; - iframe.style.opacity = '0.8'; + const pageCanvas = document.createElement('canvas'); + pageCanvas.width = viewport.width; + pageCanvas.height = viewport.height; + pageCanvas.style.position = 'absolute'; + pageCanvas.style.top = '0'; + pageCanvas.style.left = '0'; + pageCanvas.style.pointerEvents = 'none'; - iframe.onload = () => { - try { - const viewerWindow = iframe.contentWindow as PdfViewerWindow | null; - if (viewerWindow && viewerWindow.PDFViewerApplication) { - const app = viewerWindow.PDFViewerApplication; + const ctx = pageCanvas.getContext('2d'); + if (ctx) { + await pdfjsPage.render({ + canvasContext: ctx, + viewport, + canvas: pageCanvas, + }).promise; + } - const style = viewerWindow.document.createElement('style'); - 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); + canvas.appendChild(pageCanvas); - const checkRender = setInterval(() => { - if (app.pdfViewer && app.pdfViewer.pagesCount > 0) { - clearInterval(checkRender); + if (pendingFieldExtraction && uploadedPdfDoc) { + pendingFieldExtraction = false; + extractExistingFields(uploadedPdfDoc); + extractedFieldNames.forEach((name) => existingFieldNames.delete(name)); - const pageContainer = - viewerWindow.document.querySelector('.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) { - pendingFieldExtraction = false; - extractExistingFields(uploadedPdfDoc); - extractedFieldNames.forEach((name) => - existingFieldNames.delete(name) - ); - - const form = uploadedPdfDoc.getForm(); - for (const name of extractedFieldNames) { - try { - const existingField = form.getFieldMaybe(name); - if (existingField) { - form.removeField(existingField); - } - } catch (error) { - console.warn( - `Failed to remove extracted field "${name}" after import:`, - error - ); - } - } - - renderCanvas(); - updateFieldCount(); - } - }, 50); - } - } - }, 100); + const form = uploadedPdfDoc.getForm(); + for (const name of extractedFieldNames) { + try { + const existingField = form.getFieldMaybe(name); + if (existingField) { + form.removeField(existingField); + } + } catch (error) { + console.warn( + `Failed to remove extracted field "${name}" after import:`, + error + ); } - } 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, - }); + renderCanvas(); + updateFieldCount(); + } } catch (error) { console.error('Error rendering PDF:', error); } @@ -3115,8 +2965,8 @@ function extractExistingFields(pdfDoc: PDFDocument): void { pdfDoc, fieldCounterStart: fieldCounter, metrics: { - pdfViewerOffset, - pdfViewerScale, + pdfViewerOffset: { x: 0, y: 0 }, + pdfViewerScale: currentScale, }, }); diff --git a/src/tests/form-creator-extraction.test.ts b/src/tests/form-creator-extraction.test.ts index ae463a5..e1f8e4d 100644 --- a/src/tests/form-creator-extraction.test.ts +++ b/src/tests/form-creator-extraction.test.ts @@ -13,9 +13,11 @@ import { import { extractExistingFields } from '../js/logic/form-creator-extraction.ts'; import type { ExtractedFieldLike } from '@/types'; +const FORM_CREATOR_SCALE = 1.333; + const TEST_EXTRACTION_METRICS = { pdfViewerOffset: { x: 0, y: 0 }, - pdfViewerScale: 1, + pdfViewerScale: FORM_CREATOR_SCALE, }; function extractFieldsForTest(pdfDoc: PDFDocument): ExtractedFieldLike[] { @@ -377,12 +379,13 @@ describe('form creator extraction regression', () => { const field = extracted.find((entry) => entry.name === 'page2TextField'); expect(field).toBeDefined(); + const pageHeight = pdfDoc.getPages()[1].getSize().height; expect(field).toMatchObject({ pageIndex: 1, - x: rect.x, - y: pdfDoc.getPages()[1].getSize().height - rect.y - rect.height, - width: rect.width, - height: rect.height, + x: rect.x * FORM_CREATOR_SCALE, + y: (pageHeight - rect.y - rect.height) * FORM_CREATOR_SCALE, + width: rect.width * FORM_CREATOR_SCALE, + height: rect.height * FORM_CREATOR_SCALE, }); }); @@ -411,4 +414,34 @@ describe('form creator extraction regression', () => { expect(pageMap.get('page1ExtraField')).toBe(0); 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); + }); });