As a user I want a proper PDF viewer so documents render reliably and annotations can be added in a later step #39
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Background
The current document preview uses a plain
<iframe>pointing at the file URL. This works for basic viewing but has two hard limits: browser PDF plugins differ across devices and can fail silently, and there is no way to overlay annotation layers on top of the rendered page — a requirement for the annotation feature (#40).This issue replaces the iframe with PDF.js (Mozilla, Apache 2.0 license) as the rendering foundation. No annotation UI is added here — that is #40.
Desired behaviour
<img>tag path and are unaffectedImplementation notes
Library:
pdfjs-dist(the prebuilt npm distribution of PDF.js).PDF.js requires its worker script to be served as a separate file. With Vite this is handled via:
Component: Replace the iframe in
src/routes/documents/[id]/+page.sveltewith a newPdfViewer.sveltecomponent:url: stringprop<canvas>element at the correct device pixel ratio (devicePixelRatioscaling to avoid blurry text on retina screens)Critical — lazy page rendering. Rendering all pages at once for a multi-page document will freeze the browser and consume hundreds of megabytes of canvas memory at 2× DPR. Only the visible page (±1 for pre-loading) must be rendered. Canvas content for pages that scroll out of view must be cleared and re-rendered on scroll. This is not an optimisation — it is a correctness requirement. An implementation that renders all pages eagerly is not acceptable.
Text layer. PDF.js can render a transparent text layer on top of each canvas, enabling text selection and copy-paste. This layer is also the foundation for the annotation drag-select in #40 (distinguishing "user is selecting text" from "user is drawing an annotation rectangle"). The text layer must be included in this implementation so that #40 does not require modifying the rendering pipeline.
Per-page container structure (required by #40):
Each page wrapper must have a stable
data-page-numberattribute andposition: relativeso annotation overlays can be positioned absolutely within it.No backend changes required — the file URL path (
/api/documents/{id}/file) is unchanged.Testing
PdfViewerrenders a canvas element when given a valid PDF URL (mockpdfjs-dist)Dependencies
User Journey
User opens a document that has a PDF attached. Instead of the browser's built-in PDF plugin (which may look different or fail entirely on some devices), the document renders inside the app's own viewer — the same fonts, layout, and quality on every browser. The user can page through the document with previous/next buttons and jump to a specific page number. They can zoom in on fine print or zoom out to see the full page. When they open a document with an image attachment instead of a PDF, the image displays exactly as before — the viewer only activates for PDFs.
E2E Scenarios