# EnvelopeGenerator — AI Context Reference ## Purpose Digital document signing system with **unified Blazor WASM frontend** for both Senders and Receivers. Senders create envelopes and place signature fields. Receivers view PDFs, sign documents, export stamped PDFs. **Primary Libraries:** DevExpress + PDF.js (PSPDFKit removed) --- ## Architecture Evolution ### Old Architecture (Deprecated) - **Sender UI:** `EnvelopeGenerator.Web` (Razor Pages + PSPDFKit) - **Receiver UI:** `EnvelopeGenerator.ReceiverUI` (Blazor WASM + PDF.js) - **Backend:** `EnvelopeGenerator.API` ### Current Architecture - **Unified Frontend:** `EnvelopeGenerator.ReceiverUI` (Blazor WASM) — **Both Senders & Receivers** - **Backend:** `EnvelopeGenerator.API` — **Both Senders & Receivers** - **Libraries:** DevExpress + PDF.js - **PSPDFKit:** **REMOVED** --- ## Solution Structure | Project | Target | Purpose | |---|---|---| | `EnvelopeGenerator.API` | net8.0 | ASP.NET Core Web API. Backend for **both Senders & Receivers**. Auth, PDF serving, signature endpoints. | | `EnvelopeGenerator.ReceiverUI` | net8.0 WASM | **Unified Blazor WebAssembly Frontend**. UI for **both Senders & Receivers**. YARP proxy to API. | | `EnvelopeGenerator.Web` | net7/8/9 | **DEPRECATED.** Legacy Razor Pages (Sender UI). No longer used. | | `EnvelopeGenerator.Application` | multi | MediatR CQRS handlers. Business logic. | | `EnvelopeGenerator.Domain` | multi | Domain models, constants, interfaces. | | `EnvelopeGenerator.Infrastructure` | multi | EF Core repos, DB context. | | `EnvelopeGenerator.PdfEditor` | multi | iText7 utilities (NOT used in ReceiverUI). | | `EnvelopeGenerator.DependencyInjection` | multi | DI registration helpers. | | **VB.NET projects** (Service/Form/BBTests) | net462 | **Legacy. Do NOT touch.** | --- ## Key Files & Routes | File | Route/Purpose | |---|---| | `ReceiverUI/Pages/EnvelopeViewer.razor` | `/envelope/{key}` — PDF.js viewer (read-only). | | `ReceiverUI/Pages/LoginReceiver.razor` | `/login/{EnvelopeKey}` — Access code auth. | | `ReceiverUI/Pages/LoginSender.razor` | `/login` — Username/password auth. | | `ReceiverUI/wwwroot/js/pdf-viewer.js` | PDF.js wrapper (zoom, pagination, thumbnails). | | `ReceiverUI/wwwroot/js/receiver-signature.js` | Signature pad (draw/type/image). | | `ReceiverUI/wwwroot/css/envelope-viewer.css` | EnvelopeViewer styles. | | `ReceiverUI/Services/AuthService.cs` | Receiver + Sender authentication. | | `ReceiverUI/Services/SignatureCacheService.cs` | Signature caching (Redis/SQL). | | `API/Controllers/CacheController.cs` | Signature cache endpoints. | --- ## Coordinate System — CRITICAL **Database Format:** INCHES (GdPicture14 native) **Origin:** Top-left corner **Axes:** X right, Y down ### Conversion Formulas | From INCHES to | Formula | Example | |---|---|---| | **DevExpress DX** | `x_DX = x_inches * 100` | 1.5" ? 150 DX | | **PDF Points** | `x_pt = x_inches * 72` | 1.5" ? 108 pt | | **PDF.js Pixels** | Normalize ? scale | `(x_inches / pageWidth) * canvasWidth * scale` | **A4 Dimensions:** - Width: 8.27" = 595pt = 827 DX - Height: 11.69" = 842pt = 1169 DX ### Unit Systems | System | Unit | Origin | Y-Axis | |---|---|---|---| | **Database (GdPicture14)** | Inches | Top-left | Down | | PDF.js | Pixels | Top-left | Down | | iText7 PDF | Points (1/72") | **Bottom-left** | **Up** (flip required) | | ~~PSPDFKit~~ | ~~Points~~ | ~~Top-left~~ | **REMOVED** | --- ## EnvelopeViewer — PDF.js Viewer **Route:** `/envelope/{EnvelopeKey}` **Tech:** PDF.js 3.11.174 + Blazor WASM + configurable quality **File:** `ReceiverUI/Pages/EnvelopeViewer.razor` ### Key Features 1. HiDPI/Retina support (4x quality) 2. Configurable quality (`appsettings.json`) 3. Unlimited zoom (50%-300%) 4. Ctrl+Wheel global zoom 5. Resizable thumbnail sidebar (150-400px, localStorage) 6. Responsive (desktop/mobile) ### Configuration **File:** `ReceiverUI/wwwroot/appsettings.json` ```json { "PdfViewer": { "ThumbnailBaseScale": 0.75, "ThumbnailEnableHiDPI": true, "MainCanvasEnableHiDPI": true, "ZoomStepPercentage": 5 } } ``` ### JavaScript API **File:** `ReceiverUI/wwwroot/js/pdf-viewer.js` ```javascript window.pdfViewer = { initialize(canvasId, pdfDataUrl, dotNetRef), renderPage(num), renderSignatureButtons(signatures, pageNum, dotNetRef), applySignature(signatureId, dataUrl, fullName, position, place), zoomIn(), zoomOut(), dispose() } ``` --- ## Signature Workflow — EnvelopeViewer **IMPORTANT:** iText7 NOT used (GPL license issue). Client-side overlay system only. ### Workflow Steps 1. **Page Load:** - Check `SignatureCacheService` for cached signature - If cached ? skip popup, load signature - If not ? show automatic popup (mandatory) 2. **Signature Popup (DxPopup):** - **Cannot close** (no X, no ESC, no outside-click) - **3 Tabs:** Draw (canvas) / Text (font select) / Image (upload) - **Required:** Full name, Place - **Optional:** Position - **Save ?** Store in `_capturedSignature`, cache via API 3. **Signature Buttons:** - Render purple "Unterschreiben" buttons at signature field positions - Coordinates: INCHES ? POINTS ? Pixels (scaled) - File: `pdf-viewer.js` ? `renderSignatureButtons()` 4. **Apply Signature (Click "Unterschreiben"):** - JS: Remove button, create HTML overlay - Format: Image + separator + text (Name, Position, Place, Date) - **NOT stamped on PDF bytes** (visual overlay only) 5. **Re-rendering:** - Zoom/Page change ? recalculate button positions - Session state: `_capturedSignature` (lost on refresh) ### Data Model **File:** `ReceiverUI/Models/SignatureCaptureDto.cs` ```csharp public sealed record SignatureCaptureDto { public required string DataUrl { get; init; } // base64 PNG public required string FullName { get; init; } public string Position { get; init; } = ""; // Optional public required string Place { get; init; } } ``` --- ## Signature Caching **Purpose:** Persist signature across page refreshes (distributed cache: Redis/SQL) ### API Endpoints **Controller:** `API/Controllers/CacheController.cs` - `POST /api/Cache/SignatureCapture/{envelopeKey}` — Save - `GET /api/Cache/SignatureCapture/{envelopeKey}` — Load - `DELETE /api/Cache/SignatureCapture/{envelopeKey}` — Delete **Cache Key Format:** ``` signature:91751687-8ae6-4777-bf5f-b8846085e62e:{envelopeKey} ``` **Configuration:** `appsettings.json` ```json { "Cache": { "SignatureCacheExpiration": null // or "02:00:00" for 2h } } ``` ### Service **File:** `ReceiverUI/Services/SignatureCacheService.cs` ```csharp public class SignatureCacheService { Task SaveSignatureAsync(string envelopeKey, SignatureCaptureDto signature); Task GetSignatureAsync(string envelopeKey); Task DeleteSignatureAsync(string envelopeKey); } ``` **Error Handling:** Fire-and-forget saves, graceful degradation on load failure. --- ## Sender Login **Route:** `/login` **File:** `ReceiverUI/Pages/LoginSender.razor` **Tech:** Bootstrap 5 + DevExpress Blazing Berry theme ### AuthService Extension **File:** `ReceiverUI/Services/AuthService.cs` ```csharp public enum SenderLoginResult { Success, InvalidCredentials, Error } public async Task LoginSenderAsync(string username, string password) { var response = await http.PostAsJsonAsync( $"{_api.BaseUrl}/api/auth?cookie=true", new { username, password }); return response.StatusCode switch { HttpStatusCode.OK => SenderLoginResult.Success, HttpStatusCode.Unauthorized => SenderLoginResult.InvalidCredentials, _ => SenderLoginResult.Error }; } ``` ### API Integration **Endpoint:** `POST /api/auth?cookie=true` **Request:** ```json { "username": "TekH", "password": "***" } ``` **Response:** - `200 OK` ? Cookie set, redirect to `/` - `401 Unauthorized` ? Show error: "Ungόltige Anmeldedaten" - Other ? Show error: "Serverfehler" **Cookie:** HTTP-only, Secure (HTTPS), SameSite=Strict ### UI Flow 1. User enters username + password 2. Click "Anmelden" or press Enter 3. Call `AuthService.LoginSenderAsync()` 4. Success ? `Navigation.NavigateTo("/", forceLoad: true)` 5. Error ? Display alert --- ## Receiver Login **Route:** `/login/{EnvelopeKey}` **File:** `ReceiverUI/Pages/LoginReceiver.razor` ### AuthService Method ```csharp public enum EnvelopeLoginResult { Success, InvalidCode, NotFound, Error } public async Task LoginEnvelopeReceiverAsync(string key, string accessCode) { var form = new MultipartFormDataContent(); form.Add(new StringContent(accessCode), "AccessCode"); var response = await http.PostAsync( $"{_api.BaseUrl}/api/Auth/envelope-receiver/{Uri.EscapeDataString(key)}", form); return response.StatusCode switch { HttpStatusCode.OK => EnvelopeLoginResult.Success, HttpStatusCode.Unauthorized => EnvelopeLoginResult.InvalidCode, HttpStatusCode.NotFound => EnvelopeLoginResult.NotFound, _ => EnvelopeLoginResult.Error }; } ``` **Success:** Redirect to `/envelope/{key}` --- ## NuGet Packages (ReceiverUI) | Package | Version | Purpose | |---|---|---| | `DevExpress.Blazor.*` | 25.2.3 | UI components (grids, popups, etc.) | | `SkiaSharp.*` | 3.119.1 | WASM rendering | | ~~`itext`~~ | ~~8.0.5~~ | **NOT USED** (GPL license) | **External CDN:** - PDF.js 3.11.174: `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js` --- ## Mistakes History — Do NOT Repeat | Mistake | Why Wrong | |---|---| | Using iText7 in EnvelopeViewer | GPL license issue. Use overlay system instead. | | Using PSPDFKit | Removed from architecture. Use PDF.js + DevExpress. | | Hardcoded quality values in PDF.js | Use `appsettings.json` for configurability. | | Complex toolbar layouts | User wants simplicity. Keep horizontal layout. | | Over-designed UI (gradients/badges) | User prefers simple text labels. | | Ignoring "revert" instructions | Revert HTML structure, not just CSS. | | `BottomMarginBand` for signatures | Repeats on every page. Use DetailBand. | | `imageY = (page-1) * 1169 + ann.Y` | Inflates DetailBand. Calculate per-page. | --- ## Development Notes ### Deprecated Projects **DO NOT USE:** - `EnvelopeGenerator.Web` (Razor Pages) — Replaced by unified ReceiverUI - PSPDFKit — Removed, use PDF.js + DevExpress instead ### Legacy Projects (VB.NET) **DO NOT TOUCH:** `EnvelopeGenerator.Service`, `EnvelopeGenerator.Form`, `EnvelopeGenerator.BBTests` ### Signature Coordinate Evidence **File:** `EnvelopeGenerator.Form/frmFieldEditor.vb` (VB.NET) ```vb Private Const SIGNATURE_WIDTH As Single = 1.77 ' inches Private Const SIGNATURE_HEIGHT As Single = 1.96 ' inches Sub LoadAnnotation(pElement As Signature, ...) oAnnotation.Left = CSng(pElement.X) ' Direct INCHES assignment oAnnotation.Top = CSng(pElement.Y) End Sub ``` Proves database uses INCHES natively. --- ## Quick Reference ### When working with coordinates: 1. **Database ? UI:** INCHES Χ 72 = PDF Points 2. **UI ? Display:** Points Χ scale = Pixels 3. **iText7 stamping:** Flip Y-axis (top-down ? bottom-up) ### When adding features: 1. Check `Mistakes History` first 2. Prefer simplicity over complexity 3. Use `appsettings.json` for configuration 4. Keep consistent with existing design (Bootstrap 5 + Blazing Berry) 5. **Unified frontend:** ReceiverUI serves both Senders and Receivers ### When debugging: 1. **Coordinates:** Always check unit system (inches/points/pixels) 2. **Authentication:** Check cookie name/domain/SameSite 3. **Cache:** Check Redis/SQL connection + key format 4. **Frontend confusion:** Only use ReceiverUI (Web is deprecated) --- **Last Updated:** Session 17 (Architecture unification documentation)