const { PDFDocument, rgb } = PDFLib; // Configure pdf.js worker pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; let originalPdfBytes = null; let signedPdfBytes = null; let pdfFileName = ''; let pdfDoc = null; let currentPage = null; let currentPageNum = 1; let totalPages = 1; let signatureDataUrl = null; // Signature position and size (relative to PDF) let signaturePos = { x: 0.5, y: 0.1 }; // Relative position (0-1) let signatureScale = 0.3; // Text overlay state (now part of signature overlay) let textOverlayText = ''; let textFontSize = 14; // WebSocket connection let ws = null; function updateConnectionStatus(isConnected) { const statusIndicator = document.getElementById('statusIndicator'); const connectionText = document.getElementById('connectionText'); const pdfInput = document.getElementById('pdfInput'); const pdfSelectButton = document.getElementById('pdfSelectButton'); const pdfSelectHint = document.getElementById('pdfSelectHint'); if (isConnected) { statusIndicator.classList.remove('disconnected'); statusIndicator.classList.add('connected'); connectionText.textContent = '✓ Verbunden mit Signatur-Station'; // Enable PDF selection pdfInput.disabled = false; pdfSelectButton.disabled = false; pdfSelectHint.style.display = 'none'; } else { statusIndicator.classList.remove('connected'); statusIndicator.classList.add('disconnected'); connectionText.textContent = '⏳ Warte auf Signatur-Station...'; // Disable PDF selection pdfInput.disabled = true; pdfSelectButton.disabled = true; pdfSelectHint.style.display = 'block'; } } function connectWebSocket() { // Use current host (works for localhost, IP addresses, and reverse proxy) const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsHost = window.location.hostname; // If using HTTPS (reverse proxy), use same port as page (usually 443, no port needed) // If using HTTP directly, use port 8080 const wsPort = window.location.protocol === 'https:' ? '' : ':8080'; const wsUrl = `${wsProtocol}//${wsHost}${wsPort}`; console.log('Verbinde WebSocket zu:', wsUrl); ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('WebSocket verbunden'); // Show connection status section document.getElementById('connectionStatus').style.display = 'block'; // Register as master ws.send(JSON.stringify({ type: 'register_master' })); }; ws.onmessage = async (event) => { const data = JSON.parse(event.data); if (data.type === 'signature') { console.log('Unterschrift empfangen'); signatureDataUrl = data.signature; await showSignatureOverlay(data.signature); } else if (data.type === 'signature_station_connected') { console.log('Signatur-Station verbunden'); updateConnectionStatus(true); } else if (data.type === 'signature_station_disconnected') { console.log('Signatur-Station getrennt'); updateConnectionStatus(false); } }; ws.onerror = (error) => { console.error('WebSocket Fehler:', error); }; ws.onclose = () => { console.log('WebSocket getrennt'); setTimeout(connectWebSocket, 1000); }; } document.getElementById('pdfInput').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; // Store filename for download pdfFileName = file.name.replace('.pdf', ''); const arrayBuffer = await file.arrayBuffer(); // Store TWO copies - one for rendering, one for pdf-lib originalPdfBytes = new Uint8Array(arrayBuffer); const renderCopy = new Uint8Array(arrayBuffer.slice(0)); // Create array for sending BEFORE any async operations const pdfArray = Array.from(originalPdfBytes); // Show status section document.getElementById('uploadSection').style.display = 'none'; document.getElementById('statusSection').style.display = 'block'; // Show text input section and update with current date document.getElementById('textInputSection').style.display = 'block'; updateTextInputWithDate(); // Show signature placeholder document.getElementById('signaturePlaceholder').style.display = 'block'; // If signature already exists, make sure overlay will be visible if (signatureDataUrl) { const overlay = document.getElementById('signatureOverlay'); if (overlay) { // Ensure overlay is ready to be shown overlay.style.display = 'none'; // Reset first overlay.classList.remove('show'); // Reset class // Reset position to default when loading new PDF signaturePos.x = 0.5; signaturePos.y = 0.1; console.log('Overlay zurückgesetzt für neues PDF-Laden, Position auf Standard zurückgesetzt'); } } else { // Reset position to default if no signature signaturePos.x = 0.5; signaturePos.y = 0.1; } // Render PDF preview using the copy await renderPdfPreview(renderCopy); // Send PDF to server using the pre-created array if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'pdf', pdf: pdfArray })); console.log('PDF gesendet, Größe:', pdfArray.length); // Send initial page info after a short delay (to ensure signature station received PDF) setTimeout(() => { if (ws && ws.readyState === WebSocket.OPEN && totalPages > 0) { ws.send(JSON.stringify({ type: 'page_change', pageNum: 1, totalPages: totalPages })); console.log('Initiale Seiteninformation gesendet: Seite 1 von', totalPages); } }, 500); } }); async function renderPdfPreview(pdfBytes, pageNum = 1) { try { // ALWAYS create a fresh copy to prevent detachment // pdf.js consumes/detaches the buffer during rendering let safeCopy; if (pdfBytes instanceof Uint8Array) { safeCopy = new Uint8Array(pdfBytes.buffer.slice(0)); } else { safeCopy = new Uint8Array(pdfBytes); } const loadingTask = pdfjsLib.getDocument({ data: safeCopy }); pdfDoc = await loadingTask.promise; totalPages = pdfDoc.numPages; currentPageNum = pageNum; currentPage = await pdfDoc.getPage(pageNum); const canvas = document.getElementById('pdfCanvas'); const ctx = canvas.getContext('2d'); const viewport = currentPage.getViewport({ scale: 1.5 }); canvas.width = viewport.width; canvas.height = viewport.height; const renderContext = { canvasContext: ctx, viewport: viewport }; await currentPage.render(renderContext).promise; // Update page navigation updatePageNavigation(); // If signature was already received, show it again after PDF loads setTimeout(() => { const overlay = document.getElementById('signatureOverlay'); const placeholder = document.getElementById('signaturePlaceholder'); if (signatureDataUrl && overlay) { // Signature was already received, restore it console.log('=== Unterschrift war bereits vorhanden, zeige sie wieder an ==='); const img = document.getElementById('signatureImage'); const placeholderImg = document.getElementById('placeholderSignatureImage'); const placeholderContent = document.getElementById('placeholderContent'); // Show in placeholder placeholder.style.display = 'block'; placeholderImg.src = signatureDataUrl; placeholderImg.style.display = 'block'; placeholderContent.style.display = 'none'; // Show in overlay - IMPORTANT: Set image src FIRST, then show overlay img.src = signatureDataUrl; // Ensure overlay is visible overlay.classList.add('show'); overlay.style.display = 'block'; // Force display console.log('Overlay show-Klasse hinzugefügt:', overlay.classList.contains('show')); // Position overlay (only if single page view, not all pages) const pdfPreview = document.getElementById('pdfPreview'); if (!pdfPreview.classList.contains('scrollable')) { // Single page view - position overlay const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { const overlayWidth = 250; // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); // 100px min height for overlay overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; overlay.style.zIndex = '10'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay positioniert nach PDF-Laden:', overlay.style.left, overlay.style.top); console.log('Canvas Größe:', canvas.clientWidth, 'x', canvas.clientHeight); console.log('Overlay sichtbar:', overlay.classList.contains('show'), 'display:', overlay.style.display); } else { // Retry positioning - canvas might not be ready yet console.log('Canvas noch nicht bereit, versuche erneut...'); setTimeout(() => { const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0) { const overlayWidth = 250; // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; overlay.style.zIndex = '10'; overlay.classList.add('show'); overlay.style.display = 'block'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay positioniert (2. Versuch nach PDF-Laden):', overlay.style.left, overlay.style.top); console.log('Overlay sichtbar:', overlay.classList.contains('show')); } else { console.error('Canvas auch nach Retry nicht gefunden!'); } }, 500); } } else { // All pages view - hide overlay overlay.classList.remove('show'); overlay.style.display = 'none'; } // Show controls document.getElementById('signatureControls').classList.add('show'); } else { // No signature yet, show placeholder if (!overlay.classList.contains('show')) { placeholder.style.display = 'block'; } else { placeholder.style.display = 'none'; } } // Update text in overlay if it exists updateTextInOverlay(); }, 300); // Increased delay to ensure canvas is ready console.log('PDF Seite', pageNum, 'erfolgreich gerendert'); } catch (error) { console.error('Fehler beim Rendern des PDFs:', error); alert('Fehler beim Anzeigen der PDF: ' + error.message); } } async function renderAllPages(pdfBytes) { try { // Create a fresh copy let safeCopy; if (pdfBytes instanceof Uint8Array) { safeCopy = new Uint8Array(pdfBytes.buffer.slice(0)); } else { safeCopy = new Uint8Array(pdfBytes); } console.log('Rendere ALLE Seiten, Größe:', safeCopy.length, 'bytes'); const loadingTask = pdfjsLib.getDocument({ data: safeCopy }); const doc = await loadingTask.promise; const numPages = doc.numPages; console.log('PDF hat', numPages, 'Seiten'); // Get container and clear it const container = document.getElementById('pdfCanvasContainer'); container.innerHTML = ''; // Make pdf-preview scrollable const pdfPreview = document.getElementById('pdfPreview'); pdfPreview.classList.add('scrollable'); // Hide signature overlay when showing all pages const overlay = document.getElementById('signatureOverlay'); if (overlay) { overlay.classList.remove('show'); overlay.style.display = 'none'; console.log('Overlay ausgeblendet beim Rendern aller Seiten'); } // Render all pages for (let pageNum = 1; pageNum <= numPages; pageNum++) { // Add page separator const separator = document.createElement('div'); separator.className = 'pdf-page-separator'; separator.textContent = `Seite ${pageNum} von ${numPages}`; container.appendChild(separator); // Create canvas for this page const canvas = document.createElement('canvas'); canvas.className = 'pdf-page-canvas'; canvas.id = `pdf-page-${pageNum}`; container.appendChild(canvas); // Render page const page = await doc.getPage(pageNum); const ctx = canvas.getContext('2d'); const viewport = page.getViewport({ scale: 1.5 }); canvas.width = viewport.width; canvas.height = viewport.height; const renderContext = { canvasContext: ctx, viewport: viewport }; await page.render(renderContext).promise; } // Hide page navigation after placing document.getElementById('pageNavigation').style.display = 'none'; console.log('Alle', numPages, 'Seiten erfolgreich gerendert'); } catch (error) { console.error('Fehler beim Rendern aller Seiten:', error); alert('Fehler beim Anzeigen der PDF: ' + error.message); } } function updatePageNavigation() { const pageNav = document.getElementById('pageNavigation'); const pageInfo = document.getElementById('pageInfo'); const prevBtn = document.getElementById('prevPageBtn'); const nextBtn = document.getElementById('nextPageBtn'); const jumpInput = document.getElementById('jumpToPageInput'); const pageSelector = document.getElementById('pageSelector'); const currentPageNumSpan = document.getElementById('currentPageNum'); if (totalPages > 1) { pageNav.style.display = 'flex'; pageInfo.textContent = `Seite ${currentPageNum} von ${totalPages}`; prevBtn.disabled = currentPageNum <= 1; nextBtn.disabled = currentPageNum >= totalPages; // Update jump input max value and placeholder if (jumpInput) { jumpInput.max = totalPages; jumpInput.placeholder = `1-${totalPages}`; } // Show page selector if signature is visible if (document.getElementById('signatureOverlay').classList.contains('show')) { pageSelector.style.display = 'block'; currentPageNumSpan.textContent = currentPageNum; } } else { pageNav.style.display = 'none'; pageSelector.style.display = 'none'; } } async function jumpToPage() { const jumpInput = document.getElementById('jumpToPageInput'); const pageNum = parseInt(jumpInput.value); if (isNaN(pageNum) || pageNum < 1 || pageNum > totalPages) { alert(`Bitte geben Sie eine gültige Seitennummer zwischen 1 und ${totalPages} ein.`); jumpInput.value = ''; return; } if (pageNum === currentPageNum) { jumpInput.value = ''; return; // Already on this page } await renderPdfPreview(originalPdfBytes, pageNum); // Reposition overlay if signature is visible setTimeout(() => { const overlay = document.getElementById('signatureOverlay'); if (overlay && overlay.classList.contains('show')) { const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { const overlayWidth = 250; // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top); } } }, 100); // Send page change to signature station if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'page_change', pageNum: pageNum, totalPages: totalPages })); } // Clear input jumpInput.value = ''; } async function changePage(direction) { const newPage = currentPageNum + direction; if (newPage < 1 || newPage > totalPages) return; await renderPdfPreview(originalPdfBytes, newPage); // Reposition overlay if signature is visible setTimeout(() => { const overlay = document.getElementById('signatureOverlay'); if (overlay && overlay.classList.contains('show')) { const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { const overlayWidth = 250; // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay nach Seitenwechsel positioniert:', overlay.style.left, overlay.style.top); } } }, 100); // Send page change to signature station if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'page_change', pageNum: newPage, totalPages: totalPages })); } } function updatePageSelection() { const currentOnly = document.getElementById('currentPageOnly'); const allPages = document.getElementById('allPages'); // Toggle logic if (currentOnly.checked && allPages.checked) { if (event.target === currentOnly) { allPages.checked = false; } else { currentOnly.checked = false; } } // Ensure at least one is checked if (!currentOnly.checked && !allPages.checked) { currentOnly.checked = true; } } // Placeholder handling is now simpler - just show/hide async function showSignatureOverlay(signatureDataUrl) { console.log('=== showSignatureOverlay aufgerufen ==='); console.log('signatureDataUrl Länge:', signatureDataUrl ? signatureDataUrl.length : 'NULL'); const overlay = document.getElementById('signatureOverlay'); const img = document.getElementById('signatureImage'); const placeholder = document.getElementById('signaturePlaceholder'); const placeholderImg = document.getElementById('placeholderSignatureImage'); const placeholderContent = document.getElementById('placeholderContent'); if (!overlay || !img || !placeholder) { console.error('FEHLER: Overlay, img oder placeholder nicht gefunden!'); return; } // Show signature in placeholder first (always visible) placeholder.style.display = 'block'; placeholderImg.src = signatureDataUrl; placeholderImg.style.display = 'block'; placeholderContent.style.display = 'none'; console.log('Unterschrift im Platzhalter angezeigt'); // Also show in overlay for dragging on PDF img.src = signatureDataUrl; overlay.classList.add('show'); overlay.style.display = 'block'; // Force display overlay.style.position = 'absolute'; // Ensure positioning works overlay.style.zIndex = '10'; // Ensure it's on top // Update text in overlay if text input has value updateTextInOverlay(); document.getElementById('signatureControls').classList.add('show'); // Show page selector if multi-page if (totalPages > 1) { document.getElementById('pageSelector').style.display = 'block'; document.getElementById('currentPageNum').textContent = currentPageNum; } document.getElementById('statusMessage').className = 'status signed'; document.getElementById('statusMessage').textContent = '✅ Unterschrift erhalten! Unterschrift oben sichtbar. Ziehe sie auf das PDF.'; document.getElementById('downloadButton').disabled = true; document.getElementById('downloadHint').style.display = 'none'; // Initialize drag and resize handlers (only once) initializeDragAndResize(); // Wait for canvas to be ready, then position signature overlay setTimeout(() => { const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0 && canvas.clientHeight > 0) { // Position signature overlay on PDF (centered horizontally, top area) const overlayWidth = 250; // Initial width // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); // 100px min height for overlay overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay positioniert bei:', overlay.style.left, overlay.style.top); console.log('Canvas Größe:', canvas.clientWidth, 'x', canvas.clientHeight); console.log('Overlay sichtbar:', overlay.classList.contains('show')); } else { console.error('Canvas nicht gefunden oder noch nicht geladen'); // Retry after a bit longer delay setTimeout(() => { const canvas = document.getElementById('pdfCanvas'); if (canvas && canvas.clientWidth > 0) { const overlayWidth = 250; // Calculate position and ensure it's within bounds let leftPos = canvas.clientWidth * signaturePos.x - overlayWidth / 2; let topPos = canvas.clientHeight * signaturePos.y; // Clamp position to visible area leftPos = Math.max(0, Math.min(leftPos, canvas.clientWidth - overlayWidth)); topPos = Math.max(0, Math.min(topPos, canvas.clientHeight - 100)); overlay.style.left = leftPos + 'px'; overlay.style.top = topPos + 'px'; overlay.style.position = 'absolute'; // Update signaturePos with clamped values signaturePos.x = (leftPos + overlayWidth / 2) / canvas.clientWidth; signaturePos.y = topPos / canvas.clientHeight; console.log('Overlay positioniert (2. Versuch) bei:', overlay.style.left, overlay.style.top); } }, 500); } }, 200); } // Dragging state (shared across calls) let isDragging = false; let isResizing = false; let dragStartX, dragStartY, dragInitialX, dragInitialY; let resizeStartX, resizeStartWidth; // Initialize dragging/resizing once let dragInitialized = false; function initializeDragAndResize() { if (dragInitialized) return; // Only initialize once const overlay = document.getElementById('signatureOverlay'); const resizeHandle = document.getElementById('resizeHandle'); if (!overlay || !resizeHandle) return; // Make draggable overlay.addEventListener('mousedown', (e) => { if (e.target.classList.contains('resize-handle') || e.target.classList.contains('handle')) { return; } isDragging = true; dragStartX = e.clientX; dragStartY = e.clientY; dragInitialX = overlay.offsetLeft; dragInitialY = overlay.offsetTop; overlay.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (isDragging && overlay.classList.contains('show')) { const dx = e.clientX - dragStartX; const dy = e.clientY - dragStartY; overlay.style.left = (dragInitialX + dx) + 'px'; overlay.style.top = (dragInitialY + dy) + 'px'; } if (isResizing && overlay.classList.contains('show')) { const dx = e.clientX - resizeStartX; const newWidth = Math.max(100, resizeStartWidth + dx); overlay.querySelector('img').style.maxWidth = newWidth + 'px'; } }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; overlay.style.cursor = 'move'; updateSignaturePosition(); } if (isResizing) { isResizing = false; updateSignatureScale(); } }); // Make resizable resizeHandle.addEventListener('mousedown', (e) => { e.stopPropagation(); isResizing = true; resizeStartX = e.clientX; resizeStartWidth = overlay.querySelector('img').width; }); dragInitialized = true; } function makeDraggable(element) { // Function kept for compatibility, but actual initialization is in initializeDragAndResize } function makeResizable(element) { // Function kept for compatibility, but actual initialization is in initializeDragAndResize } function updateSignaturePosition() { const overlay = document.getElementById('signatureOverlay'); const canvas = document.getElementById('pdfCanvas'); // Use clientWidth/clientHeight instead of width/height (display size vs internal size) const canvasDisplayWidth = canvas.clientWidth || canvas.width; const canvasDisplayHeight = canvas.clientHeight || canvas.height; if (canvasDisplayWidth > 0 && canvasDisplayHeight > 0) { signaturePos.x = overlay.offsetLeft / canvasDisplayWidth; signaturePos.y = overlay.offsetTop / canvasDisplayHeight; console.log('Neue Position:', signaturePos, 'Canvas Display:', canvasDisplayWidth, 'x', canvasDisplayHeight); } else { console.error('Canvas Display Größe ist 0!'); } } function updateSignatureScale() { const img = document.getElementById('signatureImage'); const canvas = document.getElementById('pdfCanvas'); signatureScale = img.width / canvas.width; console.log('Neue Skalierung:', signatureScale); } async function addSignatureToPdf() { try { if (!originalPdfBytes || originalPdfBytes.length === 0) { console.error('originalPdfBytes ist nicht verfügbar'); alert('Fehler: PDF-Daten nicht verfügbar. Bitte laden Sie die PDF erneut hoch.'); return false; } // Determine which pages to add signature to const currentOnly = document.getElementById('currentPageOnly').checked; const allPagesChecked = document.getElementById('allPages').checked; let pagesToSign = []; if (allPagesChecked) { // All pages for (let i = 1; i <= totalPages; i++) { pagesToSign.push(i); } } else { // Current page only pagesToSign.push(currentPageNum); } console.log('=== FÜGE UNTERSCHRIFTEN HINZU ==='); console.log('Seiten zum Signieren:', pagesToSign.join(', ')); // Load PDF with pdf-lib // Use ignoreEncryption to handle encrypted PDFs const pdfLibDoc = await PDFDocument.load(originalPdfBytes, { ignoreEncryption: true }); const pages = pdfLibDoc.getPages(); // Get signature position and size from overlay const overlay = document.getElementById('signatureOverlay'); const overlayLeft = overlay.offsetLeft; const overlayTop = overlay.offsetTop; const overlayWidth = overlay.querySelector('img').width; // Embed signature image once const signatureImage = await pdfLibDoc.embedPng(signatureDataUrl); // Add signature and text to each selected page for (const pageNum of pagesToSign) { const page = pages[pageNum - 1]; // 0-indexed const { width, height } = page.getSize(); console.log('--- Bearbeite Seite', pageNum, '---'); console.log('Seite Dimensionen:', width, 'x', height); // Get canvas display size const canvas = document.getElementById('pdfCanvas'); const canvasDisplayWidth = canvas.clientWidth; const canvasDisplayHeight = canvas.clientHeight; const scaleX = width / canvasDisplayWidth; const scaleY = height / canvasDisplayHeight; console.log('Scale Faktoren - X:', scaleX, 'Y:', scaleY); console.log('Overlay Position:', overlayLeft, overlayTop); console.log('Overlay Breite:', overlayWidth); // Calculate signature dimensions in PDF coordinates const sigWidth = overlayWidth * scaleX; const sigHeight = sigWidth * (signatureImage.height / signatureImage.width); // Convert position to PDF coordinates (bottom-left origin) const x = overlayLeft * scaleX; const y = height - (overlayTop * scaleY) - sigHeight; console.log('PDF Position:', x, y, 'Größe:', sigWidth, 'x', sigHeight); // Add signature to page page.drawImage(signatureImage, { x: x, y: y, width: sigWidth, height: sigHeight, }); console.log('✓ Unterschrift zu Seite', pageNum, 'hinzugefügt'); // Add text overlay if it exists (text is part of signature overlay, positioned top-left) if (textOverlayText) { // Text is positioned relative to signature overlay (top-left) // Calculate text position: overlay left position + small offset, overlay top position - small offset const textOffsetX = 5 * scaleX; // 5px offset from left const textOffsetY = -5 * scaleY; // 5px offset from top (negative because PDF y is bottom-up) const textX = x + textOffsetX; // overlayTop is from top of canvas, convert to PDF coordinates (bottom-up) const overlayTopInPdf = height - (overlayTop * scaleY); const textY = overlayTopInPdf + textOffsetY; // Top of overlay - small offset // Add text to page page.drawText(textOverlayText, { x: textX, y: textY, size: textFontSize * scaleY, color: rgb(0, 0, 0), }); console.log('✓ Text zu Seite', pageNum, 'hinzugefügt'); } } // Save PDF console.log('Speichere PDF...'); const pdfBytes = await pdfLibDoc.save(); console.log('PDF gespeichert, Typ:', pdfBytes.constructor.name, 'Größe:', pdfBytes.length); if (!pdfBytes || pdfBytes.length === 0) { console.error('FEHLER: pdfDoc.save() hat leeres Array zurückgegeben!'); alert('Fehler beim Speichern der PDF. Bitte versuchen Sie es erneut.'); return false; } // Store as Uint8Array signedPdfBytes = new Uint8Array(pdfBytes); console.log('signedPdfBytes gespeichert, Typ:', signedPdfBytes.constructor.name, 'Größe:', signedPdfBytes.length); console.log('=== UNTERSCHRIFTEN ERFOLGREICH HINZUGEFÜGT ==='); return true; } catch (error) { console.error('Fehler beim Hinzufügen der Unterschrift:', error); console.error('Stack:', error.stack); alert('Fehler beim Verarbeiten der Unterschrift: ' + error.message); return false; } } function getCurrentDateString() { const today = new Date(); const day = String(today.getDate()).padStart(2, '0'); const month = String(today.getMonth() + 1).padStart(2, '0'); const year = today.getFullYear(); return `${day}.${month}.${year}`; } function updateTextInputWithDate() { const textInput = document.getElementById('textInput'); if (textInput) { const currentDate = getCurrentDateString(); textInput.value = `Abgeholt am ${currentDate} durch:`; } } function updateTextInOverlay() { const textInput = document.getElementById('textInput'); const text = textInput ? textInput.value : ''; textOverlayText = text; const textOverlayContent = document.getElementById('textOverlayContent'); if (textOverlayContent) { if (text && text.trim()) { textOverlayContent.textContent = text; textOverlayContent.style.display = 'block'; } else { textOverlayContent.style.display = 'none'; } } } function removeSignatureOverlay() { // Only remove the overlay, keep signedPdfBytes intact const overlay = document.getElementById('signatureOverlay'); overlay.classList.remove('show'); overlay.style.display = 'none'; // Explicitly hide overlay document.getElementById('signatureControls').classList.remove('show'); // Hide text in overlay const textOverlayContent = document.getElementById('textOverlayContent'); if (textOverlayContent) { textOverlayContent.style.display = 'none'; } // Reset placeholder to default state const placeholder = document.getElementById('signaturePlaceholder'); const placeholderImg = document.getElementById('placeholderSignatureImage'); const placeholderContent = document.getElementById('placeholderContent'); placeholderImg.style.display = 'none'; placeholderContent.style.display = 'flex'; placeholder.style.display = 'block'; signatureDataUrl = null; console.log('removeSignatureOverlay() aufgerufen - signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); } function removeSignature() { // Complete reset including signedPdfBytes const overlay = document.getElementById('signatureOverlay'); overlay.classList.remove('show'); overlay.style.display = 'none'; // Explicitly hide overlay document.getElementById('signatureControls').classList.remove('show'); // Reset placeholder to default state const placeholder = document.getElementById('signaturePlaceholder'); const placeholderImg = document.getElementById('placeholderSignatureImage'); const placeholderContent = document.getElementById('placeholderContent'); placeholder.style.display = 'block'; placeholderImg.style.display = 'none'; placeholderImg.src = ''; placeholderContent.style.display = 'flex'; document.getElementById('statusMessage').className = 'status waiting'; document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...'; document.getElementById('downloadButton').disabled = true; document.getElementById('downloadHint').style.display = 'none'; signatureDataUrl = null; console.log('removeSignature() aufgerufen - SETZE signedPdfBytes auf NULL'); signedPdfBytes = null; } // Place signature button document.getElementById('placeSignatureButton').addEventListener('click', async () => { if (!signatureDataUrl) { console.error('Keine signatureDataUrl vorhanden'); return; } console.log('=== PLATZIERE UNTERSCHRIFT ==='); console.log('signatureDataUrl vorhanden:', signatureDataUrl ? 'Ja' : 'Nein'); console.log('originalPdfBytes vorhanden:', originalPdfBytes ? 'Ja (' + originalPdfBytes.length + ' bytes)' : 'Nein'); // Show loading state const btn = document.getElementById('placeSignatureButton'); const originalText = btn.textContent; btn.textContent = '⏳ PDF wird erstellt...'; btn.disabled = true; // Add signature to PDF const success = await addSignatureToPdf(); console.log('addSignatureToPdf Ergebnis:', success); console.log('signedPdfBytes nach Bearbeitung:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); if (success && signedPdfBytes) { // Hide signature overlay (but keep signedPdfBytes!) removeSignatureOverlay(); // Update status document.getElementById('statusMessage').className = 'status ready'; document.getElementById('statusMessage').textContent = '✅ PDF wurde erstellt! Bereit zum Download.'; // Enable download document.getElementById('downloadButton').disabled = false; document.getElementById('downloadHint').style.display = 'block'; console.log('Download-Button Status:', document.getElementById('downloadButton').disabled ? 'DEAKTIVIERT' : 'AKTIVIERT'); console.log('signedPdfBytes für Download:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); // Send confirmation to signature station that signature was placed console.log('=== SENDE PLATZIERUNGS-BESTÄTIGUNG ==='); console.log('WebSocket vorhanden:', ws ? 'Ja' : 'Nein'); console.log('WebSocket readyState:', ws ? ws.readyState : 'N/A'); console.log('WebSocket OPEN:', ws && ws.readyState === WebSocket.OPEN ? 'Ja' : 'Nein'); if (ws && ws.readyState === WebSocket.OPEN) { try { const message = JSON.stringify({ type: 'signature_placed' }); ws.send(message); console.log('✅ Platzierungs-Bestätigung an Server gesendet'); } catch (error) { console.error('❌ Fehler beim Senden der Bestätigung:', error); } } else { console.error('❌ WebSocket nicht verfügbar oder nicht geöffnet - Bestätigung kann nicht gesendet werden'); } // Render the FINAL PDF with signature - show ALL pages console.log('Rendere finale PDF mit Unterschrift - ALLE Seiten...'); const renderCopy = new Uint8Array(signedPdfBytes); await renderAllPages(renderCopy); console.log('Finale PDF angezeigt - alle Seiten'); console.log('signedPdfBytes nach Rendering:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); console.log('=== PLATZIERUNG ERFOLGREICH ==='); } else { // Reset button on error console.error('Platzierung fehlgeschlagen! success:', success, 'signedPdfBytes:', signedPdfBytes ? 'vorhanden' : 'NULL'); btn.textContent = originalText; btn.disabled = false; alert('Fehler beim Platzieren der Unterschrift. Bitte versuchen Sie es erneut.'); } }); document.getElementById('downloadButton').addEventListener('click', async () => { console.log('=== DOWNLOAD BUTTON GEKLICKT ==='); console.log('signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL/UNDEFINED'); console.log('signedPdfBytes type:', typeof signedPdfBytes); if (!signedPdfBytes || signedPdfBytes.length === 0) { console.error('FEHLER: signedPdfBytes ist nicht verfügbar!'); alert('Fehler: Kein signiertes PDF verfügbar. Bitte platzieren Sie die Unterschrift zuerst.'); return; } try { console.log('Starte Download, PDF Größe:', signedPdfBytes.length, 'bytes'); // Ensure signedPdfBytes is Uint8Array const pdfData = signedPdfBytes instanceof Uint8Array ? signedPdfBytes : new Uint8Array(signedPdfBytes); console.log('PDF Daten konvertiert, Typ:', pdfData.constructor.name, 'Größe:', pdfData.length); // Download with _signed suffix const blob = new Blob([pdfData], { type: 'application/pdf' }); console.log('Blob erstellt, Größe:', blob.size); const url = URL.createObjectURL(blob); console.log('Blob URL erstellt:', url); const a = document.createElement('a'); a.href = url; a.download = pdfFileName + '_signed.pdf'; console.log('Download Dateiname:', a.download); a.click(); URL.revokeObjectURL(url); console.log('PDF heruntergeladen als:', pdfFileName + '_signed.pdf'); console.log('=== DOWNLOAD ERFOLGREICH ==='); // Reset for next PDF document.getElementById('uploadSection').style.display = 'block'; document.getElementById('statusSection').style.display = 'none'; // Hide text input section document.getElementById('textInputSection').style.display = 'none'; // Reset file input document.getElementById('pdfInput').value = ''; // Reset text input to default with current date updateTextInputWithDate(); // Reset place signature button const placeBtn = document.getElementById('placeSignatureButton'); placeBtn.textContent = '✓ Unterschrift platzieren & PDF erstellen'; placeBtn.disabled = false; // Reset page navigation and scrollable state currentPageNum = 1; totalPages = 1; document.getElementById('pageNavigation').style.display = 'none'; document.getElementById('pageSelector').style.display = 'none'; document.getElementById('pdfPreview').classList.remove('scrollable'); // Reset canvas container to single canvas const container = document.getElementById('pdfCanvasContainer'); container.innerHTML = ''; removeSignature(); originalPdfBytes = null; signedPdfBytes = null; pdfFileName = ''; pdfDoc = null; currentPage = null; textOverlayText = ''; console.log('Alles zurückgesetzt für nächste PDF'); } catch (error) { console.error('Fehler beim Download:', error); alert('Fehler beim Herunterladen: ' + error.message); } }); function resetToStart() { console.log('=== ZURÜCK ZUM ANFANG ==='); // Show upload section, hide status section document.getElementById('uploadSection').style.display = 'block'; document.getElementById('statusSection').style.display = 'none'; // Hide text input section document.getElementById('textInputSection').style.display = 'none'; // Reset file input document.getElementById('pdfInput').value = ''; // Reset text input to default with current date updateTextInputWithDate(); // Reset place signature button const placeBtn = document.getElementById('placeSignatureButton'); placeBtn.textContent = '✓ Unterschrift platzieren & PDF erstellen'; placeBtn.disabled = false; // Reset download button document.getElementById('downloadButton').disabled = true; document.getElementById('downloadHint').style.display = 'none'; // Reset page navigation and scrollable state currentPageNum = 1; totalPages = 1; document.getElementById('pageNavigation').style.display = 'none'; document.getElementById('pageSelector').style.display = 'none'; document.getElementById('pdfPreview').classList.remove('scrollable'); // Reset canvas container to single canvas const container = document.getElementById('pdfCanvasContainer'); container.innerHTML = ''; // Reset status message document.getElementById('statusMessage').className = 'status waiting'; document.getElementById('statusMessage').textContent = 'Warte auf Unterschrift von der Signatur-Station...'; // Clear all data removeSignature(); originalPdfBytes = null; signedPdfBytes = null; pdfFileName = ''; pdfDoc = null; currentPage = null; signatureDataUrl = null; textOverlayText = ''; // Reset text input to default with current date updateTextInputWithDate(); console.log('Zurück zum Anfang - bereit für neue PDF'); } // Discard button event listener document.getElementById('discardButton').addEventListener('click', () => { if (confirm('Möchten Sie wirklich verwerfen? Alle Änderungen gehen verloren.')) { // Notify signature station to reset if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'discard' })); console.log('Verwerfen-Nachricht an Signatur-Station gesendet'); } resetToStart(); } }); // Initialize drag and resize on load initializeDragAndResize(); // Add Enter key handler for jump to page input document.addEventListener('DOMContentLoaded', () => { const jumpInput = document.getElementById('jumpToPageInput'); if (jumpInput) { jumpInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { jumpToPage(); } }); } }); // Update text in overlay when text input changes document.addEventListener('DOMContentLoaded', () => { const textInput = document.getElementById('textInput'); if (textInput) { textInput.addEventListener('input', () => { updateTextInOverlay(); }); } }); // Connect WebSocket on load connectWebSocket();