# EnvelopeGenerator — Copilot Context Notes (English) ## Purpose A digital document signing system. Senders upload PDFs and place signature annotation fields via PSPDFKit (EnvelopeGenerator.Web). Receivers open the document in a Blazor WASM viewer, confirm each signature field via a checkbox overlay, draw/type/upload their signature, and export the stamped PDF. --- ## Solution Structure | Project | Target | Description | |---|---|---| | `EnvelopeGenerator.API` | net8.0 | ASP.NET Core Web API. Receiver auth (cookie), annotation reading, PDF serving. | | `EnvelopeGenerator.ReceiverUI` | net8.0 WASM | Blazor WebAssembly. Receiver UI. YARP proxies API calls. | | `EnvelopeGenerator.Web` | net7/8/9 | Razor Pages. Sender UI + PSPDFKit annotation placement. | | `EnvelopeGenerator.Application` | multi | MediatR CQRS handlers. | | `EnvelopeGenerator.Domain` | multi | Domain models, constants, interfaces. | | `EnvelopeGenerator.Infrastructure` | multi | EF Core repos, DB context. | | `EnvelopeGenerator.PdfEditor` | multi | iText7 utilities. NOT used in ReceiverUI flow. | | `EnvelopeGenerator.DependencyInjection` | multi | DI registration helpers. | | VB.NET projects (Service/Form/BBTests) | net462 | Legacy. Do NOT touch. | --- ## Key Files | File | Purpose | |---|---| | `ReceiverUI/Pages/ReportViewer.razor` | Main receiver page. All signing logic. | | `ReceiverUI/wwwroot/js/receiver-signature.js` | JS: checkbox overlay, signature pad (draw/type/image). | | `ReceiverUI/wwwroot/fake-data/annotations.json` | Dev-mode fake annotations (YARP proxy target). | | `ReceiverUI/Models/AnnotationDto.cs` | Annotation position model. All properties non-nullable. | | `ReceiverUI/Services/AnnotationService.cs` | Fetches `List` from API or fake-data. | | `ReceiverUI/Services/DocumentService.cs` | Fetches PDF bytes from API. | | `ReceiverUI/Services/AuthService.cs` | Manages receiver session cookie. | | `API/Controllers/AnnotationController.cs` | GET `api/Annotation/{key}` ? annotation list. | | `API/Controllers/DocumentController.cs` | GET `api/Document/{key}` ? PDF bytes. | --- ## AnnotationDto — Coordinate System ``` Unit : 1/100 inch (DX units) — DevExpress XtraReports native Origin : Top-left corner of page X : increases rightward Y : increases downward A4 in DX units: Width = 827, Height = 1169 Conversions: PSPDFKit (pt, top-left): xDX = xPsPdf * (100/72) GDPicture (pt, bottom-left): yDX = (pageHeightPt - yGD - elemHeightPt) * (100/72) DX ? PDF points: pt = dx * (72/100) ``` --- ## ReceiverUI Signing Flow (ReportViewer.razor) ### On Load (`OnInitializedAsync`) 1. `AuthService.CheckEnvelopeAccessAsync` ? redirect to login if unauthorized 2. `AnnotationService.GetAnnotationsAsync` ? fills `_annotations` 3. `DocumentService.GetDocumentAsync` ? fills `_basePdfBytes` (real mode) 4. `BuildFreshBaseReport()` ? `XtraReport` for `DxReportViewer` ### Signature Popup - Tabs: Draw / Text / Image - Fields: full name (required), position (optional), place (required) - Saved to `_capturedSignature` record - If annotations exist ? popup closes ? JS checkbox overlays installed ### JS Checkbox Overlay (`receiver-signature.js`) - `receiverSignature.installAnnotationCheckboxes(annotations, checkedIds, dotNetRef)` - One `.annot-sig-cb-wrapper` div per annotation, absolutely positioned over viewer scroll container - Position: `left = pageRect.left + ann.x * scaleX`, `top = pageRect.top + ann.y * scaleY` - `scaleX = pagePixelWidth / 827`, `scaleY = pagePixelHeight / 1169` - Click ? `dotNetRef.invokeMethodAsync('OnAnnotationToggled', id, checked)` ### Apply Signatures ("Unterschriften anwenden" — `SubmitSignaturesAsync`) **Real PDF mode (`_basePdfBytes` is set):** - Calls `StampSignaturesOnPdf` using **iText7** directly on PDF bytes - Coordinate conversion: `xPt = ann.X * (72/100)`, `yPt = pageHeight - ann.Y * (72/100) - sigHeight` (Y flip: DX top-down ? PDF bottom-up) - Returns stamped bytes ? loaded into new `XtraReport` with `XRPdfContent` - Viewer refreshed with `ViewerKey++` **Dev/fake mode (`_basePdfBytes` is null):** - Falls back to `AddSignatureAtAnnotation` (XtraReports DetailBand + BeforePrint counter) - This is intentionally left as a dev fallback only ### Export - `reportViewer.ExportToAsync(ExportFormat.Pdf)` --- ## StampSignaturesOnPdf — iText7 Implementation ```csharp // Located in ReportViewer.razor @code section // Called by SubmitSignaturesAsync when _basePdfBytes is available static byte[] StampSignaturesOnPdf( byte[] sourcePdfBytes, byte[] signatureImageBytes, string signerFullName, string signerPosition, string signaturePlace, IReadOnlyList annotations) { // Opens PDF with PdfReader/PdfWriter // For each annotation: // pageNum = ann.Page (clamped to totalPages) // xPt = ann.X * (72f/100f) // imgBottomY = pageHeight - ann.Y * (72f/100f) - sigHeightPt ? Y-axis flip // PdfCanvas.AddImageFittedIntoRectangle(imageData, rect, false) // Separator line at bottom of image // Canvas text block below separator (font: Helvetica 7pt, color: RGB(73,80,87)) // Returns stamped byte[] } ``` --- ## BuildFreshBaseReport() ```csharp // Real PDF mode: var report = new XtraReport(); var detail = new DetailBand(); report.Bands.Add(detail); detail.Controls.Add(new XRPdfContent { Source = _basePdfBytes, GenerateOwnPages = true }); return report; // Dev/fake mode: returns pre-built report from ReportStorage ``` --- ## NuGet Packages in ReceiverUI | Package | Version | Purpose | |---|---|---| | `DevExpress.Blazor.Reporting.Viewer` | 25.2.3 | DxReportViewer component | | `DevExpress.Blazor.PdfViewer` | 25.2.3 | PDF viewer | | `DevExpress.Drawing.Skia` | 25.2.3 | Drawing backend | | `itext` | 8.0.5 | PDF stamping (iText7) | | `SkiaSharp.*` | 3.119.1 | WASM native rendering | --- ## Mistakes History — Do NOT Repeat | Mistake | Why Wrong | |---|---| | `BottomMarginBand` for per-page signatures | Repeats on every page; Y offset wrong | | `imageY = (page-1) * 1169 + ann.Y` | Inflates DetailBand; 35 pages ? 140 pages | | `e.Graph?.PrintingSystem` in BeforePrint | `Graph` not on `CancelEventArgs` | | `ctrl.Report?.PrintingSystem` | `PrintingSystem` not on `XtraReportBase` in WASM | | Adding stamp endpoint to `DocumentController` | Not needed; stamping is done client-side in ReceiverUI | | iText7 via API (server-side) | Unnecessary; iText7 runs fine in WASM directly | --- ## DevExpress Article (2023-08-28) — Why It Does NOT Apply The article describes **X.509 cryptographic digital signatures** via `PdfDocumentSigner` + `Pkcs7Signer`. Our use case is **visual/image stamping** at specific page coordinates — different problem, different API. `XRPdfSignature` in the article requires pre-placed fields in the report designer, not runtime coordinates. --- ## Change Log | Session | Date | Change | |---|---|---| | 1–3 | — | Core infrastructure: services, YARP proxy, JS overlay, signature pad | | 4 | — | `AddSignatureAtAnnotation` with BottomMarginBand — ? repeated on all pages | | 5 | — | `BeforePrint` + `e.Graph?.PrintingSystem` — ? compile error | | 6 | — | BeforePrint counter — ? correct pattern, wrong band | | 7 | — | Switched to DetailBand — ? correct band | | 8 | — | `(page-1)*1169+Y` offset — ? 35?140 page inflation | | 9 | — | Fixed: `BoundsF.Y = ann.Y` + counter; created COPILOT_CONTEXT.md | | 10 | — | Investigated DevExpress article — not applicable to our case | | 10 | — | Added iText7 to ReceiverUI; implemented `StampSignaturesOnPdf` — ? deterministic coordinates, no page count side effects | | 10 | — | Split COPILOT_CONTEXT.md into COPILOT_CONTEXT_EN.md and COPILOT_CONTEXT_TR.md |