udoc-viewer
Universal document viewer for your website or app, powered by WebAssembly. Supports PDF, PPTX (early stage), and images.
Free. Unlimited. Forever.
Live Demo
This demo uses the UDocClient and UDocViewer API directly, exactly as documented below. For a full-featured demo with all viewer capabilities, see the full scale demo.
Installation
Install the package using your preferred package manager:
npm install @docmentis/udoc-viewerOr with yarn/pnpm:
yarn add @docmentis/udoc-viewerpnpm add @docmentis/udoc-viewerQuick Start
Get up and running with the viewer in just a few lines of code:
import { UDocClient } from '@docmentis/udoc-viewer';
// Create a client (loads the WASM engine)
const client = await UDocClient.create();
// Create a viewer attached to a container element
const viewer = await client.createViewer({
container: '#viewer'
});
// Load a document
await viewer.load('https://example.com/document.pdf');
// Clean up when done
viewer.destroy();
client.destroy();HTML Setup: Make sure you have a container element in your HTML:
<div id="viewer" style="width: 100%; height: 600px;"></div>Client API
The UDocClient is the entry point that manages the WASM engine and creates viewers.
Creating a Client
const client = await UDocClient.create({
// Custom base URL for worker and WASM files (optional)
baseUrl: 'https://cdn.example.com/udoc/',
});Client Options
| Option | Type | Default | Description |
|---|---|---|---|
| baseUrl | string | — | Custom base URL for worker and WASM files |
Viewer Options
Configure the viewer with various display and interaction options:
const viewer = await client.createViewer({
// Container element or CSS selector (required for UI mode)
container: '#viewer',
// Scroll mode: 'continuous' or 'single-page'
scrollMode: ScrollMode.Continuous,
// Layout mode: 'single-page', 'two-page', 'two-page-cover'
layoutMode: LayoutMode.SinglePage,
// Zoom mode: 'fit-width', 'fit-page', 'fit-height', 'actual-size', 'custom'
zoomMode: ZoomMode.FitSpreadWidth,
// Initial zoom level (when zoomMode is 'custom')
zoom: 1,
// Spacing between pages in pixels
pageSpacing: 10,
});All Viewer Options
| Option | Type | Default | Description |
|---|---|---|---|
| container | string | HTMLElement | — | Container element or CSS selector (required for UI mode) |
| scrollMode | ScrollMode | Continuous | Scroll mode: 'continuous' or 'single-page' |
| layoutMode | LayoutMode | SinglePage | Layout mode: 'single-page', 'two-page', 'two-page-cover' |
| zoomMode | ZoomMode | FitSpreadWidth | Zoom mode: 'fit-width', 'fit-page', 'fit-height', 'actual-size', 'custom' |
| zoom | number | 1 | Initial zoom level (when zoomMode is 'custom') |
| zoomSteps | number[] | [0.25, 0.5, ...] | Custom zoom steps for zoom in/out |
| pageSpacing | number | 10 | Spacing between pages in pixels |
| activePanel | string | null | null | Initially active panel: 'thumbnails', 'outline', or null |
| dpi | number | 96 | Target display DPI |
UI Mode vs Headless Mode: When you provide a container, the viewer renders with full UI (toolbar, thumbnails, etc.). Without a container, it runs in headless mode for programmatic use.
Loading Documents
The viewer accepts multiple document sources:
// From URL
await viewer.load('https://example.com/document.pdf');
// From File object (e.g., from file input)
const fileInput = document.querySelector('input[type="file"]');
await viewer.load(fileInput.files[0]);
// From raw bytes
const response = await fetch('/document.pdf');
const buffer = await response.arrayBuffer();
await viewer.load(new Uint8Array(buffer));
// Close current document
viewer.close();Document Information
Access document metadata and structure:
// Check if document is loaded
if (viewer.isLoaded) {
// Get page count
const total = viewer.pageCount;
// Get page dimensions (0-based index)
const info = await viewer.getPageInfo(0);
console.log(`Page 1: ${info.width} x ${info.height} points`);
// Get document outline (table of contents)
const outline = await viewer.getOutline();
// Get annotations on a page
const annotations = await viewer.getPageAnnotations(0);
}Page Rendering (Headless)
Render pages to images without UI, useful for generating thumbnails or image exports:
// Create headless viewer (no container)
const viewer = await client.createViewer();
await viewer.load(pdfBytes);
// Render page to ImageData
const imageData = await viewer.renderPage(0, { scale: 2 });
// Render to Blob
const blob = await viewer.renderPage(0, {
format: 'blob',
imageType: 'image/png'
});
// Render to data URL
const dataUrl = await viewer.renderPage(0, {
format: 'data-url',
imageType: 'image/jpeg',
quality: 0.9
});Render Options
| Option | Type | Default | Description |
|---|---|---|---|
| scale | number | 1 | Scale factor for rendering |
| format | string | imagedata | Output format: 'imagedata', 'blob', 'data-url' |
| imageType | string | image/png | Image MIME type for blob/data-url output |
| quality | number | 0.92 | Image quality for JPEG output (0-1) |
Document Composition
Compose new documents by cherry-picking and rotating pages from existing documents:
// Create a new document from pages of existing documents
const [newDoc] = await client.compose([
[
{ doc: viewerA, pages: "1-3" },
{ doc: viewerB, pages: "5", rotation: 90 }
]
]);
// Export the composed document
const bytes = await newDoc.toBytes();
await newDoc.download('composed.pdf');Tip: The pages parameter accepts page ranges like "1-3", single pages like "5", or combinations like "1,3,5-7". Pages are 1-indexed.
Events
Subscribe to viewer events to react to document state changes:
// Document loaded
const unsubscribe = viewer.on('document:load', ({ pageCount }) => {
console.log(`Loaded ${pageCount} pages`);
});
// Document closed
viewer.on('document:close', () => {
console.log('Document closed');
});
// Error occurred
viewer.on('error', ({ error, phase }) => {
console.error(`Error during ${phase}:`, error);
});
// Unsubscribe from event
unsubscribe();
// or
viewer.off('document:load', handler);| Event | Payload | Description |
|---|---|---|
| document:load | { pageCount } | Fired when a document is successfully loaded |
| document:close | — | Fired when the current document is closed |
| error | { error, phase } | Fired when an error occurs (fetch, parse, or render) |
Cleanup
Properly dispose of resources when done to prevent memory leaks:
// Destroy the viewer first
viewer.destroy();
// Then destroy the client
client.destroy();Important: Always destroy the viewer before destroying the client. The client manages the WASM engine which the viewer depends on.