import { useEffect, useRef, useState } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; import { detectClientSignatureContext } from '../../utils/clientSignatureContext'; pdfjs.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url, ).toString(); const SIG_W = 200; const SIG_H = 60; const getSignatureWidth = (style, signerName = '') => { if (style !== 'qr' && style !== 'firmaec') return SIG_W; const normalizedName = signerName.trim(); const calculatedWidth = 78 + (normalizedName.length * 4.9); return Math.max(170, Math.min(270, Math.round(calculatedWidth))); }; export default function PdfSignaturePlacer({ pdfBlob, signerName, signatureStyle = 'classic', qrPosition = 'left', qrBackground = 'transparent', qrBorder = 'none', signatureImage = null, onPlaced, }) { const clientSignatureContext = detectClientSignatureContext(); const [numPages, setNumPages] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [sigBox, setSigBox] = useState(null); // { x, y } in container px const stageRef = useRef(null); const containerRef = useRef(null); const [pageSize, setPageSize] = useState(null); // rendered px size const [pageRenderWidth, setPageRenderWidth] = useState(600); const signatureWidth = getSignatureWidth(signatureStyle, signerName); useEffect(() => { const stage = stageRef.current; if (!stage) return undefined; const updateWidth = () => { const nextWidth = Math.max(280, Math.min(600, Math.floor(stage.clientWidth || 600))); setPageRenderWidth(nextWidth); }; updateWidth(); if (typeof ResizeObserver === 'undefined') { window.addEventListener('resize', updateWidth); return () => window.removeEventListener('resize', updateWidth); } const observer = new ResizeObserver(() => updateWidth()); observer.observe(stage); return () => observer.disconnect(); }, []); const getCanvasMetrics = () => { const container = containerRef.current; const canvas = container?.querySelector('.react-pdf__Page__canvas'); if (!container || !canvas) return null; const containerRect = container.getBoundingClientRect(); const canvasRect = canvas.getBoundingClientRect(); return { containerRect, canvasRect, offsetX: canvasRect.left - containerRect.left, offsetY: canvasRect.top - containerRect.top, width: canvasRect.width, height: canvasRect.height, }; }; const handleDocLoad = ({ numPages }) => setNumPages(numPages); const handlePageLoad = (page) => { setPageSize({ width: page.width, height: page.height }); }; const handlePointerDown = (event) => { const metrics = getCanvasMetrics(); if (!metrics) return; const x = event.clientX - metrics.canvasRect.left; const y = event.clientY - metrics.canvasRect.top; const clampedX = Math.min(Math.max(x, 0), metrics.width - signatureWidth); const clampedY = Math.min(Math.max(y, 0), metrics.height - SIG_H); setSigBox({ x: metrics.offsetX + clampedX, y: metrics.offsetY + clampedY, }); }; const handleConfirm = () => { if (!sigBox || !pageSize) return; const metrics = getCanvasMetrics(); if (!metrics) return; const relativeX = sigBox.x - metrics.offsetX; const relativeY = sigBox.y - metrics.offsetY; // PDF coordinates: origin bottom-left, Y inverted const scaleX = pageSize.pdfWidth / metrics.width; const scaleY = pageSize.pdfHeight / metrics.height; const pdfX = relativeX * scaleX; const pdfY = pageSize.pdfHeight - (relativeY + SIG_H) * scaleY; onPlaced({ page: currentPage - 1, rect: { x: Math.round(pdfX), y: Math.round(pdfY), width: Math.round(signatureWidth * scaleX), height: Math.round(SIG_H * scaleY), }, }); }; const previewDate = new Date().toLocaleDateString(clientSignatureContext.locale, { timeZone: clientSignatureContext.timeZone, }); const renderPreview = () => { if (signatureStyle === 'qr') { return (
{qrPosition === 'left' && (
QR
)}
{signerName} Verificación digital
{qrPosition === 'right' && (
QR
)}
); } if (signatureStyle === 'professional') { return (
FIRMA DIGITAL
{signerName} {previewDate}
); } if (signatureStyle === 'firmaec') { return (
{qrPosition === 'left' && ( <>
{qrBorder === 'line' &&
} )}
Validar unicamente en FirmaEC. Firmado electronicamente por: {String(signerName).toUpperCase()}
{qrPosition === 'right' && ( <> {qrBorder === 'line' &&
}
)}
); } if (signatureStyle === 'image') { return (
{signatureImage ? firma : Firma}
{signerName} Firma digital · {previewDate}
); } return (
DS
{signerName} Firma digital · {previewDate}
); }; return (
Haz clic en el documento para colocar la firma
Pág. {currentPage} / {numPages || '…'}
setPageSize({ width: p.width, height: p.height, pdfWidth: p.originalWidth, pdfHeight: p.originalHeight, })} /> {sigBox && (
e.stopPropagation()} > {signatureStyle === 'classic' &&
} {renderPreview()}
)}
{sigBox && (
)}
); }