diff --git a/COPILOT_CONTEXT_EN.md b/COPILOT_CONTEXT_EN.md
index 342d8ebb..86883058 100644
--- a/COPILOT_CONTEXT_EN.md
+++ b/COPILOT_CONTEXT_EN.md
@@ -427,6 +427,16 @@ Our use case is **visual/image stamping** at specific page coordinates
| **13** | **2025-01-XX** | **Added clickable signature buttons on PDF canvas (Sign/Unterschreiben)** |
| **13** | **2025-01-XX** | **Signature buttons: 150px×60px, purple gradient, pen icon, hover effects** |
| **13** | **2025-01-XX** | **JavaScript: `pdfViewer.renderSignatureButtons()` and `pdfViewer.clearSignatureButtons()`** |
+| **14** | **2025-01-26** | **Added signature creation popup (DxPopup) - opens automatically on page load** |
+| **14** | **2025-01-26** | **Popup features: 3 tabs (Draw/Text/Image), required fields (Name, Place), optional Position** |
+| **14** | **2025-01-26** | **Popup: No close button (X), no ESC/outside-click - only saves with validation** |
+| **14** | **2025-01-26** | **Modern, clean popup design matching EnvelopeViewer theme (purple gradients, rounded inputs)** |
+| **14** | **2025-01-26** | **Implemented German-style professional signature rendering on PDF canvas** |
+| **14** | **2025-01-26** | **Click "Unterschreiben" button ? removes button, renders applied signature** |
+| **14** | **2025-01-26** | **Signature layout: Image + separator line + Name (bold) + Position + Place, Date** |
+| **14** | **2025-01-26** | **JavaScript: `pdfViewer.applySignature()` creates HTML overlay (230px box, #f8f9fa background)** |
+| **14** | **2025-01-26** | **German date format: dd.MM.yyyy (e.g., 26.01.2025) via `toLocaleDateString('de-DE')`** |
+| **14** | **2025-01-26** | **XSS protection: `escapeHtml()` function sanitizes user-provided text** |
---
@@ -657,7 +667,7 @@ button.style.left/top
---
-## Signature Workflow in EnvelopeViewer — NEW Implementation (Session 13+)
+## Signature Workflow in EnvelopeViewer — NEW Implementation (Session 13-14)
**IMPORTANT: iText7 NOT USED in EnvelopeViewer**
- **Reason:** GPL license incompatibility (requires source code sharing)
@@ -672,46 +682,280 @@ button.style.left/top
record SignatureCapture(
string DataUrl, // base64 PNG: "data:image/png;base64,iVBORw0KG..."
string FullName, // Required: "Max Mustermann"
- string Position, // Optional: "Geschäftsführer"
+ string Position, // Optional: "Geschäftsführer" (can be empty)
string Place // Required: "Berlin"
);
```
-**Applied Signature (per SignatureDto):**
-```javascript
-{
- signatureId: 42,
- dataUrl: "data:image/png;base64,iVBORw0KG...",
- fullName: "Max Mustermann",
- position: "Geschäftsführer",
- place: "Berlin",
- date: "15.01.2025",
- x: 108, // PDF POINTS (already converted from INCHES)
- y: 144, // PDF POINTS
- width: 150, // pixels
- height: 60 // pixels
+**Applied Signature (HTML Overlay):**
+```html
+
+

+
+
+ Max Mustermann
+
Geschäftsführer
+
Berlin, 26.01.2025
+
+
+```
+
+### Complete Workflow (Session 14 Update)
+
+**Step 1: Page Load & Automatic Popup**
+```csharp
+protected override async Task OnInitializedAsync() {
+ // ... load PDF and signatures ...
+
+ // Open signature popup automatically
+ _activeSignatureTab = SignatureTabDraw;
+ _signaturePopupVisible = true;
+ _popupValidationMessage = null;
}
```
-### Workflow Steps
+**Features:**
+- Opens automatically on page load (no manual trigger needed)
+- Cannot be closed manually (no X button, ESC disabled, no outside-click)
+- User MUST create signature before viewing PDF
-**Step 1: Signature Creation Popup**
-- Reuse `DxPopup` from ReportViewer.razor
-- 3 tabs: Draw / Text / Image (using `receiver-signature.js`)
-- Fields: Full name, Position (optional), Place
-- Click "Speichern" ? `_capturedSignature` saved to state
+**Step 2: Signature Creation Popup (DxPopup)**
-**Step 2: Apply Signature to Canvas**
-- User clicks "Unterschreiben" button on PDF
-- JavaScript creates HTML overlay (NOT Canvas drawing)
-- Overlay repositions on zoom/page change
-- **NO PDF byte modification** (GPL license issue)
+**Tabs:**
+1. **Zeichnen (Draw):** Canvas-based signature pad (`receiver-signature.js`)
+ - Touch-friendly: `touch-action: none`
+ - Line width: 2.5px, black (`#111`)
+ - Canvas: 560×180px, rounded corners, shadow
-**Step 3: Visual Display Only**
-- Signatures shown as HTML `` overlays
-- Positioned using absolute positioning
-- Persist in Blazor component state
-- Lost on page refresh (no server-side save)
+2. **Text:** Type signature with font selection
+ - Fonts: Brush Script, Segoe Script, Lucida Handwriting, Comic Sans, Cursive
+ - Real-time preview on canvas
+
+3. **Bild (Image):** Upload PNG/JPG/WebP
+ - File input with preview
+ - Auto-resize to fit canvas
+
+**Required Fields:**
+- ? **Vor- und Nachname** (Full Name) — Red asterisk `*`
+- ? **Ort** (Place) — Red asterisk `*`
+- ? **Position** — Optional, gray "(optional)" label
+
+**Validation:**
+```csharp
+async Task SaveSignatureAsync() {
+ if (string.IsNullOrWhiteSpace(_signerFullName)) {
+ _popupValidationMessage = "Bitte geben Sie Vor- und Nachname ein.";
+ return;
+ }
+ if (string.IsNullOrWhiteSpace(_signaturePlace)) {
+ _popupValidationMessage = "Bitte geben Sie den Ort ein.";
+ return;
+ }
+ var signatureDataUrl = await GetActiveSignatureDataUrlAsync();
+ if (string.IsNullOrWhiteSpace(signatureDataUrl)) {
+ _popupValidationMessage = "Die Unterschrift ist erforderlich.";
+ return;
+ }
+
+ // Save to session state
+ _capturedSignature = new(signatureDataUrl, _signerFullName.Trim(),
+ _signerPosition.Trim(), _signaturePlace.Trim());
+ _signaturePopupVisible = false;
+}
+```
+
+**Design (Modern & Clean):**
+- **Tabs:** Purple active state (`#4F46E5`), 3px bottom border
+- **Inputs:** 2px solid border (`#e9ecef`), 6px border-radius, consistent padding
+- **Canvas:** Light shadow (`0 1px 3px rgba(0,0,0,0.1)`), rounded corners
+- **Buttons:**
+ - **Erneuern:** Outline secondary with refresh icon
+ - **Speichern:** Purple gradient + checkmark icon + shadow
+- **Error:** Red left border (`4px solid #dc3545`), light red background (`#fee`)
+
+**Step 3: PDF Viewing with Signature Buttons**
+
+After popup closes:
+```csharp
+protected override async Task OnAfterRenderAsync(bool firstRender) {
+ // ... PDF initialization ...
+
+ await RenderSignatureButtonsAsync(); // Render "Unterschreiben" buttons
+}
+```
+
+**Buttons appear at signature field positions:**
+- Purple gradient background
+- "Unterschreiben" text + pen icon
+- Hover: scale(1.05) + darker color
+- Positioned using PDF POINTS ? display pixels conversion
+
+**Step 4: Apply Signature (Click "Unterschreiben")**
+
+**C# Handler:**
+```csharp
+[JSInvokable]
+public async Task OnSignatureButtonClick(int signatureId) {
+ if (_capturedSignature == null) return;
+
+ await JSRuntime.InvokeVoidAsync("pdfViewer.applySignature",
+ signatureId,
+ _capturedSignature.DataUrl,
+ _capturedSignature.FullName,
+ _capturedSignature.Position,
+ _capturedSignature.Place);
+}
+```
+
+**JavaScript Implementation:**
+```javascript
+async applySignature(signatureId, signatureDataUrl, fullName, position, place) {
+ // 1. Find and remove button
+ const button = this.signatureButtons.find(btn =>
+ btn.getAttribute('data-signature-id') == signatureId);
+ button.parentNode.removeChild(button);
+
+ // 2. Create signature container (German standard format)
+ const signatureContainer = document.createElement('div');
+ signatureContainer.className = 'applied-signature';
+ signatureContainer.style.position = 'absolute';
+ signatureContainer.style.left = button.style.left; // Same position as button
+ signatureContainer.style.top = button.style.top;
+ signatureContainer.style.width = '230px';
+ signatureContainer.style.backgroundColor = '#f8f9fa';
+ signatureContainer.style.border = '1px solid #dee2e6';
+ signatureContainer.style.borderRadius = '6px';
+ signatureContainer.style.padding = '12px';
+
+ // 3. Add signature image
+ const img = document.createElement('img');
+ img.src = signatureDataUrl;
+ img.style.width = '100%';
+ img.style.maxHeight = '70px';
+ img.style.objectFit = 'contain';
+
+ // 4. Add separator line (German standard)
+ const separator = document.createElement('div');
+ separator.style.borderTop = '1px solid #495057';
+ separator.style.marginTop = '6px';
+ separator.style.marginBottom = '8px';
+
+ // 5. Add text information
+ const today = new Date();
+ const dateStr = today.toLocaleDateString('de-DE', {
+ day: '2-digit', month: '2-digit', year: 'numeric'
+ }); // "26.01.2025"
+
+ const infoHtml = [
+ `${this.escapeHtml(fullName)}`,
+ position ? this.escapeHtml(position) : null,
+ `${this.escapeHtml(place)}, ${dateStr}`
+ ].filter(x => x).join('
');
+
+ const info = document.createElement('div');
+ info.style.fontSize = '9px';
+ info.style.color = '#495057';
+ info.innerHTML = infoHtml;
+
+ // 6. Assemble and add to layer
+ signatureContainer.appendChild(img);
+ signatureContainer.appendChild(separator);
+ signatureContainer.appendChild(info);
+
+ document.getElementById('pdf-signature-layer').appendChild(signatureContainer);
+}
+```
+
+**German Standard Layout:**
+```
+???????????????????????????????
+? [Signature Image] ? ? Base64 PNG, max 70px height
+? ?
+??????????????????????????????? ? 1px separator (#495057)
+? ?
+? Max Mustermann (Bold) ? ? Name (font-weight: 600, #212529)
+? Geschäftsführer ? ? Position (optional, normal weight)
+? Berlin, 26.01.2025 ? ? Place, Date (dd.MM.yyyy)
+? ?
+???????????????????????????????
+```
+
+**Step 5: Persistence & Re-rendering**
+
+- **Zoom/Page Change:** Applied signatures re-render automatically
+- **Session State:** `_capturedSignature` stored in Blazor component
+- **Limitation:** Lost on page refresh (no server-side storage)
+- **Future:** Export to PDF with actual byte stamping (requires non-GPL library)
+
+**Security:**
+```javascript
+escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text; // Browser auto-escapes
+ return div.innerHTML;
+}
+```
+Protects against XSS attacks from malicious input in Name/Position/Place fields.
+
+---
+
+### Popup Design Specification
+
+**DxPopup Properties:**
+```razor
+
+ CloseOnOutsideClick="false"
+ ShowCloseButton="false"
+ CloseOnEscape="false">
+```
+
+**Tab Design:**
+- **Active Tab:** `border-bottom: 3px solid #4F46E5; color: #4F46E5; font-weight: 600;`
+- **Inactive Tab:** `color: #6c757d;`
+- **Tab Bar:** `border-bottom: 2px solid #e9ecef;`
+
+**Input Styling:**
+```css
+input, select {
+ border: 2px solid #e9ecef;
+ border-radius: 6px;
+ padding: 0.625rem;
+}
+```
+
+**Canvas Styling:**
+```css
+canvas {
+ border: 2px solid #e9ecef;
+ border-radius: 8px;
+ background: white;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+}
+```
+
+**Button Styling:**
+```css
+/* Erneuern (Renew) */
+.btn-outline-secondary {
+ border-radius: 6px;
+ padding: 0.625rem 1.25rem;
+ font-weight: 500;
+}
+
+/* Speichern (Save) */
+.btn-primary {
+ background: linear-gradient(135deg, #4F46E5 0%, #4338CA 100%);
+ border: none;
+ border-radius: 6px;
+ padding: 0.625rem 2rem;
+ font-weight: 600;
+ box-shadow: 0 2px 4px rgba(79, 70, 229, 0.3);
+}
+```
**Future Enhancement Required:**
- Replace iText7 with commercial PDF library (e.g., PSPDFKit, Syncfusion)