diff --git a/COPILOT_CONTEXT_EN.md b/COPILOT_CONTEXT_EN.md new file mode 100644 index 00000000..a8f821eb --- /dev/null +++ b/COPILOT_CONTEXT_EN.md @@ -0,0 +1,182 @@ +# 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 |