diff --git a/COPILOT_CONTEXT_EN.md b/COPILOT_CONTEXT_EN.md index 8d97a879..4eec5382 100644 --- a/COPILOT_CONTEXT_EN.md +++ b/COPILOT_CONTEXT_EN.md @@ -419,6 +419,14 @@ Our use case is **visual/image stamping** at specific page coordinates | **11** | **2025-01-XX** | **Made zoom step configurable (buttons, Ctrl+Wheel, slider use same step)** | | **11** | **2025-01-XX** | **Fixed thumbnail canvas alignment (object-fit: contain)** | | **11** | **2025-01-XX** | **Fixed thumbnail re-rendering on sidebar toggle** | +| **12** | **2025-01-XX** | **Fixed coordinate system documentation: Database stores INCHES (not DX units)** | +| **12** | **2025-01-XX** | **Updated XML documentation in SignatureDto, AnnotationDto, AnnotationCreateDto** | +| **13** | **2025-01-XX** | **Added SenderAppType enum to SignatureDto for Legacy/Blazor app differentiation** | +| **13** | **2025-01-XX** | **Implemented signature overlay rendering in PDF.js viewer (INCHES ? normalized ? pixels)** | +| **13** | **2025-01-XX** | **Added automatic overlay re-rendering on page change, zoom, and initial load** | +| **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()`** | --- @@ -503,4 +511,154 @@ Our use case is **visual/image stamping** at specific page coordinates - **Mobile (?768px)**: Flex column, thumbnails on top - **Thumbnail toggle**: Controlled by `@if (_showThumbnails)` in Razor markup +--- + +## Signature Buttons in EnvelopeViewer — Interactive Overlay System + +**Purpose:** Render clickable "Unterschreiben" (Sign) buttons on PDF canvas at signature field positions fetched from database. + +### Architecture + +**Blazor Component (`EnvelopeViewer.razor`):** +```csharp +IReadOnlyList _signatures = []; + +protected override async Task OnInitializedAsync() { + var signatures = await SignatureService.GetAsync(EnvelopeKey); + _signatures = signatures.Convert(UnitOfLength.Point); // INCHES ? POINTS +} + +async Task RenderSignatureButtonsAsync() { + await JSRuntime.InvokeVoidAsync("pdfViewer.renderSignatureButtons", + _signatures, _currentPage, _dotNetRef); +} + +[JSInvokable] +public void OnSignatureButtonClick(int signatureId) { + Console.WriteLine($"Signature #{signatureId} signed"); +} +``` + +**JavaScript (`pdf-viewer.js`):** +```javascript +renderSignatureButtons(signatures, currentPageNum, dotNetRef) { + this.clearSignatureButtons(); // Remove old buttons + + const pageSignatures = signatures.filter(sig => sig.page === currentPageNum); + const signatureLayer = document.getElementById('pdf-signature-layer'); + + pageSignatures.forEach(sig => { + // Convert POINTS to display pixels + const xPx = sig.x * this.scale; // sig.x already in PDF POINTS + const yPx = sig.y * this.scale; + + const button = document.createElement('button'); + button.className = 'signature-button'; + button.style.left = `${xPx}px`; + button.style.top = `${yPx}px`; + button.style.width = '150px'; + button.style.height = '60px'; + + // German text + pen icon + button.innerHTML = ` +
Unterschreiben
+ ... + `; + + button.addEventListener('click', () => { + this.dotNetReference.invokeMethodAsync('OnSignatureButtonClick', sig.id); + }); + + signatureLayer.appendChild(button); + this.signatureButtons.push(button); + }); +} + +clearSignatureButtons() { + this.signatureButtons.forEach(btn => btn.parentNode?.removeChild(btn)); + this.signatureButtons = []; +} +``` + +**HTML Structure:** +```html +
+ +
+
+
+``` + +**CSS (`envelope-viewer.css`):** +```css +.pdf-signature-layer { + position: absolute; + left: 0; top: 0; right: 0; bottom: 0; + overflow: visible; + pointer-events: none; /* Pass clicks through to canvas */ + z-index: 20; +} + +.signature-button { + pointer-events: auto; /* Re-enable for buttons */ + position: absolute; + width: 150px; height: 60px; + background: linear-gradient(135deg, #4F46E5 0%, #4338CA 100%); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + /* Hover: scale(1.05), shadow, darker gradient */ +} +``` + +### Rendering Triggers + +1. **Initial Load:** After PDF renders (`OnAfterRenderAsync`) +2. **Page Change:** `NextPage()`, `PreviousPage()`, `GoToPageFromThumbnail()` +3. **Zoom Change:** `OnZoomChanged()` (re-calculates pixel positions) + +### Coordinate Conversion Flow + +``` +Database (INCHES) + ? SignatureService.GetAsync() +SignatureDto.X/Y (INCHES) + ? .Convert(UnitOfLength.Point) +SignatureDto.X/Y (PDF POINTS, × 72) + ? JavaScript +Display Pixels (× this.scale) + ? CSS +button.style.left/top +``` + +**Example Calculation:** +- Database: `X = 1.5 inches, Y = 2.0 inches` +- After conversion: `X = 108 points, Y = 144 points` (× 72) +- At scale 1.5: `X = 162px, Y = 216px` +- Button positioned at `left: 162px, top: 216px` + +### Button Design + +**Visual Spec:** +- **Size:** 150px × 60px +- **Background:** Purple gradient `#4F46E5` ? `#4338CA` (hover: darker) +- **Text:** "Unterschreiben" (18px, bold, white) +- **Icon:** Pen SVG (24px white) +- **Effects:** + - Hover: `scale(1.05)` + shadow `0 4px 12px rgba(79, 70, 229, 0.4)` + - Active: `scale(0.98)` + - Focus: `2px solid #7e22ce` outline + +**Accessibility:** +- `tabindex="0"` for keyboard navigation +- Focus outline for keyboard users +- Click handler with semantic button element + +--- + + + + +