Add comprehensive documentation for EnvelopeGenerator
Enhanced documentation for the EnvelopeGenerator system: - Added purpose section describing its functionality as a digital document signing system. - Documented solution structure, listing projects, frameworks, and purposes. - Highlighted key files in `ReceiverUI` and `API` with their roles. - Explained `AnnotationDto` coordinate system and unit conversions. - Detailed the signing flow in `ReceiverUI`, including page load, signature popup, and export steps. - Documented JavaScript checkbox overlay logic for annotations. - Provided a breakdown of `StampSignaturesOnPdf` using iText7. - Described `BuildFreshBaseReport` behavior in real and dev modes. - Listed NuGet packages with versions and purposes. - Added "Mistakes History" to prevent repeated errors. - Clarified why DevExpress X.509 signing article does not apply. - Summarized development sessions in a change log.
This commit is contained in:
182
COPILOT_CONTEXT_EN.md
Normal file
182
COPILOT_CONTEXT_EN.md
Normal file
@@ -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<AnnotationDto>` 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<AnnotationDto> 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 |
|
||||
Reference in New Issue
Block a user