Skip to content

Source code

PDF viewer with PDF.js

Use uiPdf(config) with pdf.html for canvas-based PDF viewing with previous/next navigation, fit-width zoom, extracted page text, loading/error feedback, and a download of the original document. createAdmin registers the provider. The factory is available as pdfViewer from @agon/atrium/components/pdf. Open /components/pdf for a three-page sample and an unavailable-file scenario.

The integration uses Mozilla’s pinned pdfjs-dist 6.3.289 dependency. The engine and worker load when a viewer opens a document. They are not requested on ordinary pages. See the upstream PDF.js examples and document options.

Minimal integration

<section x-data="{ documentUrl: '/documents/report.pdf' }">
<h1>Report</h1>
<div
x-data="uiPdf({ src: documentUrl, label: 'Monthly report' })"
x-modelable="source"
x-model="documentUrl"
>
<include src="ui:pdf.html"></include>
</div>
</section>

Use an application-base-aware URL if the document ships with your frontend. For example, a page provider can initialize documentUrl from `${import.meta.env.BASE_URL}documents/report.pdf`. Backend URLs can be absolute or origin-relative. Changing the parent model replaces the document and cancels the previous load. x-modelable is optional when the source never changes.

Settings and public methods

SettingDefaultBehavior
src''.HTTP, HTTPS, or blob URL. An empty source displays an empty state.
labelTranslated PDF document label.Accessible viewer-region name.
httpHeaders{}.Consumer-owned headers passed to PDF.js document requests.
withCredentialsfalse.Allow credentials on cross-origin document requests.
passwordUnset.Optional password for an encrypted file.

The provider exposes reactive source, page, total, zoom, pageText, and status values. States are idle, empty, loading, ready, and error. Use go(number) to navigate to a valid one-based page and magnify(multiplier) to change zoom relative to fit width. Zoom is bounded between 0.5 and 2. refresh() reloads the current document and destroy() releases it. Configuration headers and passwords are fixed for that provider instance. Remount it when those settings change.

The controls and viewport frame follow theme tokens, language, shell direction, and responsive layout. PDF page colors remain faithful to the source. Width changes rerender the current page, while height-only changes do not trigger loops. Pixel density is capped at two, and each canvas is bounded to 16,777,216 pixels to limit memory use. Rendering uses a fresh canvas so stale operations cannot draw over the active page.

Loading a document participates in the application’s shared progress tracker, including red feedback on a failed load and neutral cancellation when leaving the page. Subsequent page renders use the local loading state. A removed viewer cancels its render, destroys the document loading task and worker, disconnects its resize observer, and rejects late results.

Production assets and CSP

Retain the shared adminHtml Vite plugin. It emits the PDF.js character maps, fonts, image decoder support files, color profile, and their licenses under the configured asset directory. A versioned pdfjs-<version>/ folder keeps internal filenames intact. Vite fingerprints the worker independently. Asset URLs follow the deployment base, including subdirectory builds. No CDN or manual copy step is required.

Deploy the entire output directory. Serve .mjs and decoder .js files as JavaScript, .pdf as application/pdf, and .wasm as application/wasm. Configure missing assets as genuine 404 responses rather than serving the application HTML. Use immutable caching for versioned support files and fingerprinted worker URLs.

The default renderer uses PDF.js’s JavaScript image-decoder fallbacks through useWasm: false. It works with the repository’s production CSP without adding script unsafe-eval or wasm-unsafe-eval. Same-origin workers follow script-src 'self' when no separate worker policy exists. If your host sets worker-src, permit 'self'. Remote document endpoints require an appropriate connect-src and valid server-side CORS responses. Keep the existing inline-style allowance for generated presentation styles.

Boundaries and consumer responsibilities

This is an embedded document viewer, not the full Mozilla viewer application. It does not provide annotation editing, interactive PDF forms, document search, thumbnails, print controls, or an interactive password prompt. A supplied password can open an encrypted file. An unavailable file, incorrect password, or render failure produces a generic retry message rather than exposing transport details.

Extracted page text is available in an expandable section and is inserted as plain text. It is not a semantic reconstruction of complex PDF reading order. Scanned pages without embedded text need OCR upstream. The original-file link opens the PDF in the browser’s native viewer. Some embedded browsers cannot display raw PDFs. The separate download action lets users open the file in a dedicated reader. The canvas itself is decorative to assistive technology.

Your backend owns authorization, headers, CORS, byte-range support, and document retention. The original-file link cannot attach custom request headers, so it needs a cookie-authenticated or signed URL when applicable. The download uses the bytes from the authenticated PDF.js document, so it does not issue an unauthenticated link request. Its temporary blob URL is released on replacement or teardown. The component never stores credentials in preferences. The consumer owns the lifetime of supplied blob URLs and must revoke them when no longer used. Do not initialize a second renderer on the shared surface.

Author

Laurent Declercq l.declercq@agon-innovation.ch

License

Unless otherwise stated all source code is licensed under LGPL 2.1 and has the following copyright:

© 2026, Agon Partners Innovation AG, All rights reserved.

Version

Version: 20260921