Add EnvelopeViewer for read-only PDF viewing with PDF.js

Introduced a new EnvelopeViewer component (`EnvelopeViewer.razor`)
to replace the legacy ReportViewer for non-signing workflows.
The new viewer is based on PDF.js and provides a modern,
lightweight, and responsive user experience.

Added `pdf-viewer.js` to handle PDF rendering, zoom, pagination,
and mouse wheel control, integrated with Blazor via JSInterop.
Externalized styles to `envelope-viewer.css` with a modern
glassmorphism design.

Enhanced user experience with unlimited zoom, global mouse
wheel zoom (`Ctrl+Wheel`), and responsive design. Fixed issues
like scroll behavior and canvas size restrictions.

Updated `COPILOT_CONTEXT_EN.md` to document the new
EnvelopeViewer, its architecture, and its advantages over the
legacy ReportViewer. Added external dependencies for PDF.js
via CDN and updated project history to reflect these changes.
This commit is contained in:
2026-06-05 12:52:23 +02:00
parent 41f3df4c71
commit f7aaeccf58

View File

@@ -25,8 +25,11 @@ A digital document signing system. Senders upload PDFs and place signature annot
| File | Purpose |
|---|---|
| `ReceiverUI/Pages/ReportViewer.razor` | Main receiver page. All signing logic. |
| `ReceiverUI/Pages/EnvelopeViewer.razor` | **NEW** PDF.js-based viewer (`/envelope/{key}`). Replaces ReportViewer.razor. Simple read-only PDF viewing with zoom/navigation. |
| `ReceiverUI/Pages/ReportViewer.razor` | **LEGACY** DevExpress-based signing page (`/receiver/{key}`). Still used for signature workflow. Will be deprecated. |
| `ReceiverUI/wwwroot/js/pdf-viewer.js` | **NEW** PDF.js wrapper: rendering, zoom, pagination, mouse wheel control. |
| `ReceiverUI/wwwroot/js/receiver-signature.js` | JS: checkbox overlay, signature pad (draw/type/image). |
| `ReceiverUI/wwwroot/css/envelope-viewer.css` | **NEW** Styles for EnvelopeViewer.razor (external CSS, not inline). |
| `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. |
@@ -55,7 +58,109 @@ Conversions:
---
## ReceiverUI Signing Flow (ReportViewer.razor)
## EnvelopeViewer (NEW) — PDF.js Read-Only Viewer
**Route:** `/envelope/{EnvelopeKey}`
**Purpose:** Simple, modern PDF viewing without signing functionality.
**Technology:** PDF.js 3.11.174 + custom JavaScript wrapper
### Architecture
**Blazor Component (`EnvelopeViewer.razor`):**
- Fetches PDF via `DocumentService.GetDocumentAsync(EnvelopeKey)`
- Converts to base64 data URL: `data:application/pdf;base64,{base64}`
- Initializes PDF.js viewer via JSInterop with `DotNetObjectReference` for callbacks
- Displays controls: Zoom In/Out, Page Navigation, Zoom percentage
- CSS externalized to `envelope-viewer.css`
**JavaScript (`pdf-viewer.js`):**
```javascript
window.pdfViewer = {
pdfDoc, canvas, ctx, scale, currentRenderTask,
dotNetReference, wheelEventAttached,
initialize(canvasId, pdfDataUrl, dotNetRef),
renderPage(num),
attachWheelEvent(), // Global Ctrl+Wheel zoom
zoomIn(), zoomOut(),
nextPage(), previousPage(),
dispose()
}
```
**CSS (`envelope-viewer.css`):**
- `.envelope-viewer-layout`: Full-height gradient background
- `.envelope-action-bar`: Top bar with logo, title, controls (sticky)
- `.pdf-frame`: Fixed-size white container (`calc(100vh - 200px)` × 90% width, max 1200px)
- `.pdf-canvas`: `display: inline-block`, unlimited zoom, scrollable when exceeds frame
- Modern glassmorphism design with gradients and shadows
### Features
1. **Unlimited Zoom:**
- Canvas size not restricted by `max-width`
- Frame stays fixed, scroll bars appear automatically
- `text-align: center` for small sizes, full scroll for zoomed views
2. **Global Mouse Wheel Zoom:**
- Event listener on `document.body` (works anywhere on page)
- `Ctrl + Mouse Wheel` triggers `zoomIn()`/`zoomOut()`
- Calls `dotNetReference.invokeMethodAsync('OnZoomChanged', scale)` to update Blazor UI
- `{ passive: false }` to enable `preventDefault()`
3. **Render Task Cancellation:**
- Stores `currentRenderTask` to cancel previous render if new one starts
- Catches `RenderingCancelledException` to avoid console errors
- Queue system (`pageNumPending`) for rapid page changes
4. **Responsive Design:**
- Desktop: 90% width, 1200px max
- Mobile: 95% width, adjusted heights
- Adaptive padding and font sizes
### Flow
1. **Component Load:**
```csharp
OnInitializedAsync():
- Fetch PDF bytes
- Convert to base64 data URL
- Set _isLoading = false
OnAfterRenderAsync():
- Create DotNetObjectReference
- JSRuntime.InvokeAsync("pdfViewer.initialize", canvasId, pdfDataUrl, dotNetRef)
- Update _totalPages, _currentPage, _pdfLoaded
```
2. **User Interaction:**
- Button clicks ? `ZoomIn()`/`ZoomOut()` ? `JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn")`
- Ctrl+Wheel ? JS `attachWheelEvent()` ? `dotNetRef.invokeMethodAsync('OnZoomChanged')`
- Page buttons ? `NextPage()`/`PreviousPage()` ? `JSRuntime.InvokeAsync("pdfViewer.nextPage")`
3. **Cleanup:**
```csharp
DisposeAsync():
- JSRuntime.InvokeVoidAsync("pdfViewer.dispose")
- _dotNetRef?.Dispose()
```
### Key Differences from ReportViewer
| Feature | EnvelopeViewer (NEW) | ReportViewer (LEGACY) |
|---------|----------------------|------------------------|
| Technology | PDF.js + Canvas | DevExpress XtraReports |
| Route | `/envelope/{key}` | `/receiver/{key}` |
| Purpose | Read-only viewing | Signature workflow |
| Dependencies | PDF.js CDN | DevExpress NuGet packages |
| Zoom | Unlimited, smooth | Report viewer default |
| Mouse Wheel | Custom Ctrl+Wheel | Browser default |
| File Size | Minimal (JS + CSS) | Heavy (DX libs) |
| Maintenance | Simple, standard web | Complex, vendor-specific |
---
## ReceiverUI Signing Flow (ReportViewer.razor — LEGACY)
### On Load (`OnInitializedAsync`)
1. `AuthService.CheckEnvelopeAccessAsync` ? redirect to login if unauthorized
@@ -137,12 +242,16 @@ return report;
| Package | Version | Purpose |
|---|---|---|
| `DevExpress.Blazor.Reporting.Viewer` | 25.2.3 | DxReportViewer component |
| `DevExpress.Blazor.PdfViewer` | 25.2.3 | PDF viewer |
| `DevExpress.Blazor.Reporting.Viewer` | 25.2.3 | DxReportViewer (LEGACY, used in ReportViewer.razor) |
| `DevExpress.Blazor.PdfViewer` | 25.2.3 | PDF viewer (not used in EnvelopeViewer) |
| `DevExpress.Drawing.Skia` | 25.2.3 | Drawing backend |
| `itext` | 8.0.5 | PDF stamping (iText7) |
| `SkiaSharp.*` | 3.119.1 | WASM native rendering |
**External CDN (EnvelopeViewer):**
- PDF.js 3.11.174 (via `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js`)
- PDF.js Worker (`pdf.worker.min.js`)
---
## Mistakes History — Do NOT Repeat
@@ -155,6 +264,9 @@ return report;
| `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 |
| **PDF.js: `display: flex` on `.pdf-frame`** | **Prevents left-edge scroll when canvas exceeds container** |
| **PDF.js: `max-width: 100%` on canvas** | **Limits zoom; user expects unlimited zoom capability** |
| **Mouse wheel on `.pdf-frame` only** | **Only works when mouse over PDF; should work anywhere on page** |
---
@@ -180,3 +292,10 @@ Our use case is **visual/image stamping** at specific page coordinates
| 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 |
| **11** | **2025-01-XX** | **Created EnvelopeViewer.razor (`/envelope/{key}`) with PDF.js 3.11.174** |
| **11** | **2025-01-XX** | **Implemented `pdf-viewer.js`: canvas rendering, zoom, pagination, render task cancellation** |
| **11** | **2025-01-XX** | **Externalized CSS to `envelope-viewer.css`: modern glassmorphism design** |
| **11** | **2025-01-XX** | **Fixed scroll issues: removed `display: flex`, used `text-align: center` + `inline-block`** |
| **11** | **2025-01-XX** | **Removed canvas `max-width` restriction for unlimited zoom** |
| **11** | **2025-01-XX** | **Added global mouse wheel zoom: `Ctrl+Wheel` on `document.body`, JSInterop callback to Blazor** |
| **11** | **2025-01-XX** | **Updated COPILOT_CONTEXT_EN.md: EnvelopeViewer replaces ReportViewer for read-only viewing** |