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; // 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'; // 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(); 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'); // 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 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; // 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 changePage(direction) { const newPage = currentPageNum + direction; if (newPage < 1 || newPage > totalPages) return; await renderPdfPreview(originalPdfBytes, newPage); // 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; } } async function showSignatureOverlay(signatureDataUrl) { const overlay = document.getElementById('signatureOverlay'); const img = document.getElementById('signatureImage'); img.src = signatureDataUrl; overlay.classList.add('show'); 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! Wähle die Seite(n) und positioniere sie.'; document.getElementById('downloadButton').disabled = true; document.getElementById('downloadHint').style.display = 'none'; // Position signature initially (use clientWidth for displayed size) const canvas = document.getElementById('pdfCanvas'); overlay.style.left = (canvas.clientWidth * signaturePos.x) + 'px'; overlay.style.top = (canvas.clientHeight * signaturePos.y) + 'px'; // Make signature draggable makeDraggable(overlay); makeResizable(overlay); } function makeDraggable(element) { let isDragging = false; let startX, startY, initialX, initialY; element.addEventListener('mousedown', (e) => { if (e.target.classList.contains('resize-handle') || e.target.classList.contains('handle')) { return; } isDragging = true; startX = e.clientX; startY = e.clientY; initialX = element.offsetLeft; initialY = element.offsetTop; element.style.cursor = 'grabbing'; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; element.style.left = (initialX + dx) + 'px'; element.style.top = (initialY + dy) + 'px'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; element.style.cursor = 'move'; updateSignaturePosition(); } }); } function makeResizable(element) { const resizeHandle = document.getElementById('resizeHandle'); let isResizing = false; let startX, startWidth; resizeHandle.addEventListener('mousedown', (e) => { e.stopPropagation(); isResizing = true; startX = e.clientX; startWidth = element.querySelector('img').width; }); document.addEventListener('mousemove', (e) => { if (!isResizing) return; const dx = e.clientX - startX; const newWidth = Math.max(100, startWidth + dx); element.querySelector('img').style.maxWidth = newWidth + 'px'; }); document.addEventListener('mouseup', () => { if (isResizing) { isResizing = false; updateSignatureScale(); } }); } function updateSignaturePosition() { const overlay = document.getElementById('signatureOverlay'); const canvas = document.getElementById('pdfCanvas'); signaturePos.x = overlay.offsetLeft / canvas.width; signaturePos.y = overlay.offsetTop / canvas.height; console.log('Neue Position:', signaturePos); } 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 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'); } // 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 removeSignatureOverlay() { // Only remove the overlay, keep signedPdfBytes intact document.getElementById('signatureOverlay').classList.remove('show'); document.getElementById('signatureControls').classList.remove('show'); signatureDataUrl = null; console.log('removeSignatureOverlay() aufgerufen - signedPdfBytes:', signedPdfBytes ? signedPdfBytes.length + ' bytes' : 'NULL'); } function removeSignature() { // Complete reset including signedPdfBytes document.getElementById('signatureOverlay').classList.remove('show'); document.getElementById('signatureControls').classList.remove('show'); 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'; // Reset file input document.getElementById('pdfInput').value = ''; // 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; 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'; // Reset file input document.getElementById('pdfInput').value = ''; // 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; 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(); } }); // Connect WebSocket on load connectWebSocket();