From f7aaeccf5803509f8a0d43125bb23adc0a484fad Mon Sep 17 00:00:00 2001 From: TekH Date: Fri, 5 Jun 2026 12:52:23 +0200 Subject: [PATCH] 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. --- COPILOT_CONTEXT_EN.md | 127 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/COPILOT_CONTEXT_EN.md b/COPILOT_CONTEXT_EN.md index a8f821eb..3e271c0c 100644 --- a/COPILOT_CONTEXT_EN.md +++ b/COPILOT_CONTEXT_EN.md @@ -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` 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** |