From a5e4f9739729da224f6d70d012af73d9b47b8634 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 29 Jun 2026 11:04:53 +0200 Subject: [PATCH] Migrate PDF.js to DxPdfViewer in receiver signing page This commit introduces a detailed migration plan to replace the `PDF.js` rendering engine with `DxPdfViewer` in the receiver signing experience (`EnvelopeReceiverPage.razor`). The migration preserves the existing signing workflow and behavior while introducing a new rendering layer. Key changes: - Replaced `PDF.js` rendering surface with `DxPdfViewer`. - Preserved page-level orchestration for authorization, document loading, signature handling, and toolbar interactions. - Introduced a custom overlay adapter for signature placeholders and applied signature overlays. - Centralized page/zoom geometry acquisition for overlay alignment. - Maintained signature navigation logic and thumbnail sidebar behavior. - Updated `pdf-viewer.js` to separate engine-specific logic and adapt it for `DxPdfViewer`. - Updated styles in `envelope-viewer.css` to support the new viewer. This migration ensures that all existing workflow behaviors remain functional, including navigation, zoom, signature placement, and validation, while transitioning to the new rendering engine. --- RECEIVER_PDF_VIEWER_CONTEXT.md | 532 +++++++++++++++++++++++++++++++++ 1 file changed, 532 insertions(+) diff --git a/RECEIVER_PDF_VIEWER_CONTEXT.md b/RECEIVER_PDF_VIEWER_CONTEXT.md index 1bbeffbf..6bb8360f 100644 --- a/RECEIVER_PDF_VIEWER_CONTEXT.md +++ b/RECEIVER_PDF_VIEWER_CONTEXT.md @@ -543,6 +543,538 @@ A useful way to think about the page is: --- +## Detailed Technical Migration Plan: `PDF.js` -> `DxPdfViewer` + +This section records the implementation plan for migrating the active receiver signing experience from `PDF.js` to `DxPdfViewer` without losing any workflow behavior. + +### 1. Core migration principle + +This migration must be treated as: + +- a **rendering engine replacement** +- not a signing workflow redesign +- not a simplification to a read-only viewer +- not a direct PDF stamping rewrite + +The page-level orchestration in `EnvelopeReceiverPage.razor` remains the source of truth for: + +- receiver authorization +- document loading +- signature placeholder loading +- cached signature loading/saving +- popup and validation flow +- signature locking rules +- reset/restart behavior +- toolbar intent + +The rendering layer must be swapped while preserving those behaviors. + +### 2. Current implementation findings from code + +Based on the current files: + +- `EnvelopeReceiverPage.razor` +- `wwwroot/js/pdf-viewer.js` +- `wwwroot/css/envelope-viewer.css` +- `EnvelopeReceiverPage_DxPdfViewer.razor` + +the current page is tightly coupled to a custom `pdfViewer` JavaScript object. + +That object currently owns all viewer-engine behaviors: + +- PDF load and initialization +- current page state +- page rendering +- zoom rendering +- thumbnail rendering +- resize listener attachment +- signature button rendering +- applied signature overlay rendering +- signature navigation state + +This means the migration cannot be done by replacing only the Razor markup. The current JavaScript object is effectively both: + +- a `PDF.js` adapter +- and a receiver-signing overlay controller + +Those responsibilities must be separated conceptually during migration. + +### 3. Verified coordinate and viewport behavior from `PDF.js` + +Inspection of the `PDF.js` source comments and implementation confirms the following useful facts: + +1. `PageViewport` is created from: + - `viewBox` + - `scale` + - `rotation` + - optional offsets + +2. `PDF.js` explicitly documents that `PageViewport` creates a transform that converts: + - **PDF coordinate system** + - into **normal canvas-like coordinates** + +3. `PageViewportParameters.viewBox` is documented as: + - `xMin, yMin, xMax, yMax` + +4. `getViewport(...)` is documented as returning an object containing: + - `width` + - `height` + - transforms required for rendering + +5. `convertToViewportPoint(x, y)` is documented as converting: + - PDF coordinates + - into viewport coordinates + +6. `convertToPdfPoint(x, y)` is documented as converting: + - viewport coordinates + - back into PDF coordinates + - specifically useful for converting canvas pixel locations into PDF coordinates + +7. `rawDims` exposes unscaled page dimensions: + - `pageWidth` + - `pageHeight` + - `pageX` + - `pageY` + +8. For rotation `0`, `PageViewport` flips the Y axis into canvas-style coordinates unless `dontFlip` is used. + +### 4. Practical meaning of the current overlay math + +The current `pdf-viewer.js` implementation does **not** call `convertToViewportPoint(...)` directly. +Instead, it uses this simplified mapping: + +- `xPx = sig.x * scale` +- `yPx = sig.y * scale` + +This works only because the current upstream data contract already prepares signature coordinates in a way that matches the displayed page layout used by this app. + +From the wider workspace context, the receiver page data service already converts placeholder coordinates to `UnitOfLength.Point` before they reach the UI. + +Therefore, the active overlay contract is effectively: + +- incoming signature coordinates are already normalized for the receiver UI +- JavaScript currently assumes a top-left, page-relative, point-based overlay space +- visual pixel placement is obtained by multiplying by current display scale + +This is extremely important for the migration: + +- do **not** casually reinterpret existing signature coordinates as raw PDF bottom-left coordinates +- do **not** assume `DxPdfViewer` uses the same visible page coordinate origin +- preserve the effective contract already used by the receiver workflow + +### 5. Why the migration is technically hard + +Although both viewers display PDFs, they are fundamentally different integration surfaces. + +#### Current `PDF.js` model + +- low-level canvas rendering +- direct access to viewport scale +- direct control over single page rendering +- page DOM is owned by our code +- overlays are placed over known custom elements: + - `pdf-canvas` + - `pdf-text-layer` + - `pdf-signature-layer` + +#### Target `DxPdfViewer` model + +- component-driven render surface +- internal DOM is owned by DevExpress +- page layout lifecycle is controlled by the component +- zoom/page events may differ from `PDF.js` +- overlay host placement must be rediscovered or reintroduced + +So the migration is not a `canvas -> component` rename. +It is a **viewer capability remapping** problem. + +### 6. Technical target architecture + +The target architecture should be treated as four cooperating layers. + +#### 6.1 Blazor orchestration layer +Owned by `EnvelopeReceiverPage.razor`. + +Remains responsible for: + +- auth and redirect +- data loading +- signature popup state +- signature metadata validation +- cached signature reuse +- signed/unsigned counts +- restart behavior +- toolbar user intent + +#### 6.2 Viewer host layer +Main display surface becomes `DxPdfViewer`. + +This layer must provide equivalents for: + +- document load +- current page tracking +- total pages tracking +- page navigation +- zoom control +- layout change detection + +#### 6.3 Overlay adapter layer +Custom layer that translates page/zoom/layout information into overlay placement. + +This layer must handle: + +- signature placeholder buttons +- applied signature overlays +- page-relative visibility +- redraw after page/zoom/layout changes + +#### 6.4 Thumbnail/navigation support layer +Custom or hybrid support for: + +- left sidebar thumbnails +- active page highlight +- width persistence +- signature previous/next navigation + +### 7. Mandatory first implementation step: extract the current viewer contract + +Before changing behavior, the following current `pdfViewer` capabilities must be listed and then mapped one-by-one to the target implementation: + +- `initialize` +- `getTotalPages` +- `getCurrentPage` +- `nextPage` +- `previousPage` +- `goToPage` +- `zoomIn` +- `zoomOut` +- `setScale` +- `getScale` +- `fitToWidth` +- `renderThumbnail` +- `attachResizeListeners` +- `startResize` +- `renderSignatureButtons` +- `applySignature` +- `getSignatureNavState` +- `goToNextSignature` +- `goToPreviousSignature` +- `dispose` + +The migration should preserve this capability contract at the page level even if the internal engine changes. + +### 8. Planned migration strategy + +#### Phase 1 — Confirm the `DxPdfViewer` integration surface + +Use `EnvelopeReceiverPage_DxPdfViewer.razor` as a reference and determine exactly what `DxPdfViewer` exposes for: + +- document content binding +- page navigation +- current page reading +- page change events +- zoom setting +- zoom change events +- page layout readiness +- DOM surface that can host overlays + +Key question: + +- can we reliably obtain visible page geometry from the live `DxPdfViewer` DOM? + +If yes, overlays can be positioned relative to the rendered page. +If no, a wrapper-based approximation or alternative integration is required. + +#### Phase 2 — Replace the main render surface in `EnvelopeReceiverPage.razor` + +The current custom `canvas + text layer + signature layer` block should be replaced with a `DxPdfViewer` host region while keeping: + +- the same page route +- the same page state +- the same toolbar +- the same popup +- the same thumbnail sidebar shell + +At this stage, only the main document display needs to work. +Signature overlays may temporarily be disabled while the host geometry is established. + +#### Phase 3 — Introduce a dedicated overlay host above `DxPdfViewer` + +The new viewer surface should be wrapped with a custom page container. + +Planned structure: + +- outer frame +- thumbnail sidebar +- splitter +- main viewer wrapper +- `DxPdfViewer` +- absolute-positioned custom overlay layer + +The overlay layer must be independently controlled by our code and not depend on internal `PDF.js` DOM ids. + +#### Phase 4 — Rebuild page/zoom geometry acquisition + +Because the current implementation derives position using only `sig.x * scale`, the target implementation must determine: + +- currently visible page rectangle +- page top-left origin inside the wrapper +- effective display scale relative to logical point coordinates +- current page number + +At redraw time, the adapter must produce page-relative pixel coordinates for: + +- placeholder buttons +- applied signature blocks + +This should be centralized in one geometry function rather than scattered across multiple viewer actions. + +#### Phase 5 — Move signature overlay state to a canonical model + +The current JavaScript keeps runtime state in: + +- `signatureButtons` +- `appliedSignatures` +- `appliedSignatureElements` +- `_allSignatures` +- `_lastViewedSignatureId` + +This must remain stable across redraws. + +Preferably: + +- Blazor continues to own the authoritative business state +- JavaScript owns transient rendered DOM state only + +If necessary, applied signature state should be re-sendable from .NET after viewer redraw. +That prevents losing visual signatures when page layout changes. + +#### Phase 6 — Re-implement signature placeholder rendering on top of `DxPdfViewer` + +The current implementation filters placeholders by: + +- current page +- not already applied + +That same behavior must remain. + +Rendering rules to preserve: + +- only placeholders for the active page are shown +- already applied placeholders are hidden as buttons +- button size grows/shrinks with zoom +- button click invokes `.NET` callback `OnSignatureButtonClick` + +The existing scaling behavior uses `baseScale = 1.5` as the visual reference. +That visual convention should be preserved initially unless a new normalized sizing model is intentionally introduced. + +#### Phase 7 — Re-implement applied signature rendering on top of `DxPdfViewer` + +The current applied signature overlay includes: + +- signature image +- horizontal separator line +- signer full name +- optional position +- place and date + +The same visual block must be retained. + +Required behavior: + +- clicking a placeholder converts it to an applied signature overlay +- applied signature remains visible on the correct page +- applied signature rescales with zoom +- applied signature repositions on page/zoom changes +- applied signature is hidden when the user navigates to a different page + +#### Phase 8 — Re-implement page navigation through a central page-change pipeline + +The following actions must all converge into a single page navigation routine: + +- previous page button +- next page button +- direct page number input +- thumbnail click +- signature previous/next navigation when target is on another page + +That routine must do all of the following in order: + +1. instruct viewer to change page +2. update canonical current page state +3. wait for rendered page surface readiness if needed +4. redraw placeholder overlays +5. refresh signature navigation counter state + +#### Phase 9 — Re-implement zoom through a central zoom-change pipeline + +The following actions must converge into one zoom update path: + +- toolbar zoom in +- toolbar zoom out +- slider change +- fit-to-width +- viewer-native zoom events if the user triggers zoom through gestures + +That routine must: + +1. clamp to `50% - 300%` +2. update canonical zoom state +3. wait for layout/render completion if needed +4. redraw placeholder overlays +5. rescale applied signatures + +The current implementation already treats redraw after zoom as mandatory. That must remain true. + +#### Phase 10 — Preserve signature navigation independently from the viewer engine + +Current navigation logic is driven by the global ordered signature list and not by PDF rendering internals alone. + +This behavior must remain engine-independent: + +- previous/next traverses the full signature list +- traversal wraps around at the edges +- if the target signature is on another page, the viewer jumps first +- after page jump, the target element is scrolled into view +- toolbar counters reflect total/signed/unsigned/current index + +If `DxPdfViewer` changes scrolling mechanics, the `scrollToElement` / `scrollToButton` logic must be adapted, but the traversal rules must remain unchanged. + +#### Phase 11 — Thumbnail sidebar should remain custom unless `DxPdfViewer` can match all current behavior + +The current sidebar is not just decorative. It also provides: + +- show/hide toggle +- click navigation +- active page highlight +- resizable width +- width persistence in `localStorage` +- progressive rendering strategy + +Because of that, the safest plan is: + +- keep the custom sidebar shell and resize behavior +- only replace the source of main document rendering + +If `DxPdfViewer` cannot provide matching thumbnails directly, a hybrid solution is acceptable: + +- main document rendered by `DxPdfViewer` +- thumbnails still generated through a separate custom preview pipeline + +This still satisfies the requirement that `DxPdfViewer` becomes the main viewer technology. + +#### Phase 12 — Keep signature capture popup unchanged unless integration forces a minimal adjustment + +`receiver-signature.js` and the popup flow should remain mostly untouched. + +Preserve exactly: + +- draw tab +- text tab +- image tab +- full name required +- place required +- signature data required +- cached signature reuse +- signature change lock after signing starts + +This area is low-risk and should not be refactored unnecessarily during the viewer migration. + +### 9. File-by-file execution plan + +#### `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor` + +Planned work: + +- remove `PDF.js` CDN asset dependency from the active receiver page +- replace the custom canvas-based main surface with a `DxPdfViewer` host +- preserve toolbar, popup, auth, state and receiver-specific loading logic +- redirect viewer method calls through a new or adapted JS interop surface +- preserve current state fields wherever possible to minimize workflow regressions + +#### `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js` + +Planned work: + +- split engine-specific logic from workflow-specific logic +- remove dependency on direct `pdf-canvas` rendering for the main viewer path +- introduce `DxPdfViewer`-compatible geometry and overlay placement helpers +- preserve signature navigation logic semantics +- preserve overlay rendering semantics +- preserve resize integration for sidebar handling + +This file will likely become a viewer adapter rather than a pure `PDF.js` implementation. + +#### `EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css` + +Planned work: + +- keep page shell, toolbar, thumbnails, splitter and popup-related styles +- replace canvas/text-layer specific assumptions with viewer-wrapper styles +- introduce overlay host styles for the `DxPdfViewer` surface +- ensure z-index ordering still makes signature buttons clickable +- avoid CSS leaking into DevExpress internal DOM more than necessary + +#### `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor` + +Planned role: + +- reference only +- used to understand document binding and minimal `DxPdfViewer` setup +- not the primary delivery target unless portions are borrowed into the main receiver page + +### 10. Risks that must be explicitly tested during implementation + +1. `DxPdfViewer` may not expose visible page geometry directly. +2. `DxPdfViewer` may render asynchronously in a way that requires delayed overlay redraw. +3. Built-in zoom/page state may not map 1:1 to current custom toolbar assumptions. +4. Sidebar width changes may require explicit overlay recalculation. +5. Applied signatures may visually drift if scaling math is not centralized. +6. Signature navigation may fail if target DOM nodes are created later than expected. +7. Built-in viewer scrolling may differ from the current wrapper scrolling model. + +### 11. Acceptance checklist for the migration + +The migration is only acceptable if all of the following still work in the active page: + +- receiver authorization before document access +- document load for the authenticated receiver +- single active page viewing +- previous/next page navigation +- direct page input navigation +- thumbnail click navigation +- zoom in/out and slider zoom +- overlay alignment after zoom changes +- signature placeholder rendering on the correct page +- placeholder click applying signature overlay +- previous/next signature navigation across pages +- signed/unsigned counters updating correctly +- cached signature reuse +- signature popup validation +- signature change lock after signing starts +- restart signing behavior +- thumbnail width persistence + +### 12. Final implementation guidance + +The correct technical approach is: + +- keep `EnvelopeReceiverPage.razor` as the receiver workflow orchestrator +- treat `DxPdfViewer` as the new main rendering engine +- rebuild overlay placement as a viewer-agnostic custom layer +- preserve the current receiver-specific state machine +- preserve signature navigation rules +- preserve thumbnail/sidebar interaction model where practical + +In short: + +- **replace the renderer** +- **preserve the workflow** +- **rebuild the overlay adapter** +- **do not redesign the signing experience** + +--- + ## Files Most Likely Relevant For Future Work ### Main page