Files
DocuSighn/js/master.js

1219 lines
48 KiB
JavaScript

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 = '<canvas id="pdfCanvas"></canvas>';
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 = '<canvas id="pdfCanvas"></canvas>';
// 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();