docMentis
Developer Guide

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-viewer

Or with yarn/pnpm:

yarn add @docmentis/udoc-viewer
pnpm add @docmentis/udoc-viewer

Quick Start

Get up and running with the viewer in just a few lines of code:

example.ts
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

OptionTypeDefaultDescription
baseUrlstringCustom 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

OptionTypeDefaultDescription
containerstring | HTMLElementContainer element or CSS selector (required for UI mode)
scrollModeScrollModeContinuousScroll mode: 'continuous' or 'single-page'
layoutModeLayoutModeSinglePageLayout mode: 'single-page', 'two-page', 'two-page-cover'
zoomModeZoomModeFitSpreadWidthZoom mode: 'fit-width', 'fit-page', 'fit-height', 'actual-size', 'custom'
zoomnumber1Initial zoom level (when zoomMode is 'custom')
zoomStepsnumber[][0.25, 0.5, ...]Custom zoom steps for zoom in/out
pageSpacingnumber10Spacing between pages in pixels
activePanelstring | nullnullInitially active panel: 'thumbnails', 'outline', or null
dpinumber96Target 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

OptionTypeDefaultDescription
scalenumber1Scale factor for rendering
formatstringimagedataOutput format: 'imagedata', 'blob', 'data-url'
imageTypestringimage/pngImage MIME type for blob/data-url output
qualitynumber0.92Image 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);
EventPayloadDescription
document:load{ pageCount }Fired when a document is successfully loaded
document:closeFired 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.