Migrate from 0.7
@docmentis/udoc-viewer 0.8 is built on a new architecture of the docMentis engine: each document renders in a worker, on one canvas, and the viewer shares its UI with the new editor. The package keeps its name, but its API changed. This guide takes an app from 0.7 to the latest 0.8. The viewer's API comparison lists every export of 0.7 beside what 0.8 has in its place.
Update the package
npm install @docmentis/udoc-viewer@latest0.8 runs in Chrome and Edge 91, Firefox 105, and Safari 16.4 or later, where 0.7 ran in Chrome 80, Firefox 80 and Safari 15.
If people should edit documents in your app, install @docmentis/udoc-editor instead: it's the viewer with an edit mode added, and everything here applies to it too.
Create the viewer in one call
0.7 created a client, then a viewer from it:
// 0.7
const client = await UDocClient.create({ license: "YOUR-LICENSE-KEY" });
const viewer = await client.createViewer({
container: "#viewer",
hideToolbar: true,
disableThumbnails: true,
});
await viewer.load("https://example.com/report.pdf");
viewer.destroy();
client.destroy();0.8 has no client. create makes the viewer in one call and takes the client's options and the viewer's together:
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("viewer")!, {
license: "YOUR-LICENSE-KEY",
showToolbar: false,
showThumbnailPanel: false,
});
await viewer.open("https://example.com/report.pdf");
// Later, when you no longer need it:
viewer.destroy();createtakes the container element itself, not a selector. A viewer with no container, 0.7's headless mode, has no counterpart. To render pages without the UI, use@docmentis/udoc-nodeon a server; the viewer'srenderPageandexportPageToBytesalso render a page without showing it.loadisopen. It takes a URL, aBlobor bytes, as before, and its options areformat,filename,delimiterandpassword. A format is named by its file extension, an image's too:"jpg","png".needsPassword()andauthenticate()are gone. The viewer asks for a document's password itself, oropentakes it as itspasswordoption.isLoadedisdocType !== null.destroy()removes the UI and stops the worker the engine runs in. There's no client left to destroy.UDocClient.versionisUDocViewer.version.
Where the engine loads from
0.7's baseUrl named a folder holding its worker.js and udoc_bg.wasm. In 0.8 the worker is part of the viewer, and the engine's files are found without any setting: beside your app, where your bundler puts them, or else on jsDelivr. To serve them yourself, copy node_modules/@docmentis/udoc-wasm/dist/ to your server and point wasm.baseUrl at that folder:
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("udoc")!, {
wasm: { baseUrl: "https://example.com/udoc-wasm/" },
});
await viewer.open("https://example.com/report.pdf");The worker starts from a blob: URL, so a Content Security Policy needs worker-src blob:.
Options
Most options keep their names. Those that hid or disabled something now say what they show, so their values flip:
| 0.7 | 0.8 |
|---|---|
hideToolbar: true | showToolbar: false |
disableLeftPanel, disableRightPanel | showLeftPanel, showRightPanel |
disableThumbnails, disableOutline, disableLayers, disableSearch, disableComments | showThumbnailPanel, showOutlinePanel, showLayersPanel, showSearchPanel, showCommentsPanel |
disableFullscreen, disableThemeSwitching | showFullscreenButton, showThemeButton |
disableDownload, disablePrint | showDownloadButton, showPrintButton |
hideLoadingOverlay | showLoadingOverlay |
zoomMode | fit (see Zoom) |
pageSpacing, spreadSpacing | pageGap: { horizontal, vertical }, in points rather than pixels |
layoutMode: "double-page" and the rest | layoutMode: "doublePage" and the rest, in camel case |
baseUrl | wasm: { baseUrl } |
googleFonts | enableGoogleFonts |
customPageOverlay | setOverlay(), after create (see Overlays) |
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("viewer")!, {
// 0.7's zoomMode "fit-spread-width-max": fit the width, never past 100%.
fit: { mode: "width", zoomCap: 1 },
// 0.7's layoutMode "double-page-odd-right".
layoutMode: "doublePageOddRight",
// 0.7's pageSpacing and spreadSpacing, in points rather than pixels.
pageGap: { horizontal: 8, vertical: 16 },
});
await viewer.open("https://example.com/report.pdf");
// 0.7's setZoomMode("custom") and setZoom(1.5).
viewer.setZoom(1.5);These keep their names and meaning: license, disableTelemetry, hideAttribution, locale, theme, scrollMode, zoom, zoomSteps, dpi, activePanel, navigationScrollAlignment and pageRotation. A few defaults changed: minZoom is 0.02 (it was 0.1), maxZoom is 16 (it was 5), and searchScrollAlignment is "nearest" (it was "center").
These are gone:
thumbnailWidth: the thumbnails fill their panel, which people can resize.hideFloatingToolbar:viewControlsPlacementmoves page navigation and zoom into the toolbar or the status bar instead.disableTextSelection:enableCopy: falselets text be selected but not copied.spacingMode:pageGapsets the gaps' sizes.enablePerformanceCounterandonPerformanceLog:showDeveloperButtonshows the performance counter in the toolbar's menu.disableBookmarks,disableAttachments,disableFonts,enableTransitions,enableAnimations,disableUpdateCheckand the__experimentaloptions.
UDocOptions lists every option 0.8 takes.
Zoom
zoomMode is fit, a Fit of a mode and a zoomCap, the zoom it never goes past:
0.7 zoomMode | 0.8 fit |
|---|---|
"fit-spread-width" | { mode: "width", zoomCap: null } |
"fit-spread-width-max" | { mode: "width", zoomCap: 1 } |
"fit-spread-height" | { mode: "height", zoomCap: null } |
"fit-spread" | { mode: "both", zoomCap: null } |
"custom" | { mode: "none", zoomCap: null }, with zoom |
setZoomMode() is setFit(), and setFit(null) goes back to each document type's own fit: the width, up to 100%, for a PDF or a Word document, and the whole page for slides and images. setZoom() drops the fit, as before.
Pages count from 0
A page is a 0-based index everywhere: currentPage, goToPage(), the currentPage:changed event, renderPage() and the pages print() takes. 0.7 counted these from 1, and its annotation calls from 0.
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("viewer")!);
await viewer.open("https://example.com/report.pdf");
viewer.on("currentPage:changed", ({ page }) => {
console.log(`Page ${page + 1} of ${viewer.pageCount}`);
});
// The fifth page: 0.7's goToPage(5).
viewer.goToPage(4);nextPage()andpreviousPage()aregoToNextPage()andgoToPrevPage().pageCountis a PDF's or a presentation's total from the start. Other documents count their pages as they're laid out, so their count grows until the layout is done.pageRotationturns a PDF's pages, andlayoutModesets two pages side by side for a PDF or a Word document. Other documents ignore them.setSpacingMode()issetPageGap(), which takes the gaps' sizes in points, ornullfor the default gaps.
Events
on() still returns the function that unsubscribes. Every event is named for what happened:
| 0.7 | 0.8 |
|---|---|
document:load | open() resolving |
error | open() rejecting, with a UDocError |
page:change | currentPage:changed, its pages 0-based |
viewport:change, panel:change, search:change | viewport:changed, panel:changed, search:changed |
download, print | document:downloaded, document:printed |
annotation:add, annotation:update, annotation:remove | annotation:inserted, annotation:updated, annotation:removed |
annotation:select, annotation:hover, annotation:click | annotation:selected, annotation:hovered, annotation:clicked |
document:loading is unchanged. document:close, ui:visibilityChange and font:usageChange are gone. The annotation events now fire for undo and redo too, and for a collaborator's changes.
Calls that changed the UI
The calls that showed, hid or disabled parts of the UI while it ran are gone: setToolbarVisible, setFloatingToolbarVisible, setFullscreenEnabled, setDownloadEnabled, setPrintEnabled, setThemeSwitchingEnabled, setTextSelectionEnabled, setLeftPanelEnabled, setRightPanelEnabled and setPanelEnabled. Set the options for them when you create the viewer instead. setActiveTool() and activeTool are gone too.
setTheme(), openPanel(), closePanel(), the zoom calls and the scroll and layout calls work as before. setFullscreen() now returns a promise, which rejects when the browser refuses, as it does outside a click or a key press.
Annotations
The annotation calls are the same, call for call, still find an annotation by its name, and still take a 0-based page. The annotation is now the engine's Annotation, which follows the PDF:
boundsisrect, with cornersx0,y0,x1andy1in PDF points.typeis the key ofkind, and what belongs to that type is under it:kind.highlight.quadPoints.metadata's author, subject and opacity are under the type'smarkup:kind.highlight.markup.author.contentsis on the annotation itself.- A color is
{ components, colorSpace }, not{ r, g, b }. - A reply is an annotation of its own, naming the one it answers in its markup's
inReplyTo. patchPageAnnotation()merges the patch all the way down, where 0.7 mergedmetadataone level deep.
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("viewer")!);
await viewer.open("https://example.com/report.pdf");
for (const annotation of await viewer.getPageAnnotations(0)) {
// The subtype is the key of `kind`, its own entries under it.
if (typeof annotation.kind !== "object" || !("highlight" in annotation.kind)) continue;
const { author } = annotation.kind.highlight.markup;
console.log(annotation.name, annotation.rect, author, annotation.contents);
// Merges into the annotation, all the way down; the rest stays as it was.
if (annotation.name !== null) {
await viewer.patchPageAnnotation(0, annotation.name, { contents: "Reviewed" });
}
}Search
search(query, options) takes a scope in place of a page range, and resolves with the matches once it has found them all. A SearchMatch holds its page, its highlight's quads and the words around it in context, and its location in place of charOffset. searchNext(), searchPrev(), clearSearch(), searchMatches and searchActiveIndex are as before.
Overlays
0.7's customPageOverlay(pageIndex, container, scale) was called once for each page as it mounted. 0.8's setOverlay(id, source) takes a function called each time the view is painted, with every page on screen, which returns the elements to show and where. You can add any number of them, each under its own id.
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("udoc")!);
await viewer.open("https://example.com/report.pdf");
// A button beside each page on screen, which follows it as the view scrolls
// and zooms.
viewer.setOverlay("comment-buttons", (frame) =>
frame.views.map((view) => ({
key: `comment:${view.index}`,
x: view.x + view.width + 8,
y: view.y,
create: () => {
const button = document.createElement("button");
button.textContent = "Comment";
// The overlay layer lets the pointer through to the page; the
// button takes it back.
button.style.pointerEvents = "auto";
return button;
},
update: (button) => {
button.onclick = () => openComments(view.index);
},
})),
);
function openComments(page: number): void {
console.log(`Comments on page ${page + 1}`);
}The layer still lets the pointer through, so an element that takes clicks sets pointer-events: auto. Customize the UI explains the rest.
Fonts
A font in fonts is a UrlFont: 0.7's typeface, bold and italic are its descriptor's family, bold and italic. registerFonts() takes the same, or a font file's bytes, and reaches the documents opened after it's called.
import { UDocViewer } from "@docmentis/udoc-viewer";
const viewer = await UDocViewer.create(document.getElementById("viewer")!, {
// 0.7's googleFonts.
enableGoogleFonts: true,
// 0.7's { typeface, bold, italic, url }, its face now a descriptor.
fonts: [
{
descriptor: { family: "Roboto", bold: false, italic: false },
url: "https://example.com/Roboto-Regular.woff2",
},
{
descriptor: { family: "Roboto", bold: true, italic: false },
url: "https://example.com/Roboto-Bold.woff2",
},
],
});
await viewer.open("https://example.com/report.docx");Saving and printing
toBytes()issaveToBytes(format), which names the format to save as:saveToBytes("pdf"). Supported formats lists what each document saves to.download()is as before.print()takes aPrintOptions, its pages a list of 0-based indices.
Words
locale works as before, with the same eleven languages; a Traditional Chinese tag such as zh-HK now reads Traditional Chinese. translations is an option of create, and its keys are the UI's own, many of them new. Check yours against Messages.
Styling
The UI's root element is .udoc-ui-root, where it was .udoc-viewer-root, and in the dark theme it also has .udoc-ui-dark, where it had .udoc-viewer-dark. The CSS variables are renamed:
| 0.7 | 0.8 |
|---|---|
--udoc-primary | --udoc-color-primary |
--udoc-text-on-primary | --udoc-color-on-primary |
--udoc-bg-viewport | --udoc-canvas-background |
--udoc-bg-panel, --udoc-bg-panel-tabs | --udoc-panel-background |
--udoc-bg-surface | --udoc-color-surface-container-high |
--udoc-bg-overlay | --udoc-color-scrim |
--udoc-text-primary | --udoc-color-on-surface |
--udoc-text-secondary, --udoc-text-muted, --udoc-text-placeholder | --udoc-color-on-surface-variant |
--udoc-text-disabled | --udoc-state-disabled-opacity |
--udoc-border, --udoc-border-light | --udoc-color-outline-variant |
--udoc-border-input | --udoc-color-outline |
--udoc-search-highlight, --udoc-search-highlight-active | --udoc-canvas-search, --udoc-canvas-search-active |
--udoc-text-selection | --udoc-canvas-selection |
--udoc-scrollbar-thumb, --udoc-scrollbar-thumb-hover | The same |
--udoc-error-text | --udoc-color-error |
--udoc-primary-hover and --udoc-primary-focus-ring have no counterpart: a control's hover and pressed looks are opacities of its own color, and the focus ring is the primary color. Nor do --udoc-bg-input, the shadows, the error box's background and border, or the progress bar's colors.
The UI is neutral gray now, its primary color near black in the light theme. To keep 0.7's blue:
.udoc-ui-root {
--udoc-color-primary: #0066cc;
--udoc-color-on-primary: #ffffff;
}
.udoc-ui-root.udoc-ui-dark {
--udoc-color-primary: #4da6ff;
--udoc-color-on-primary: #ffffff;
}Customize the UI lists every variable.
Removed
These have no counterpart in 0.8:
- The document utilities:
compose,splitByOutline,extractImages,extractFonts,parseFontInfo,compressanddecompress. getOutline,getPageText,getPageInfo,getLayoutPage,getFontUsage,metadata,renderThumbnail,renderRegion,getDocumentId,initializeFromDocIdandperformanceCounter.- The client's
license,hasFeature,getWorkerClient,getRenderCacheStats,getWasmMemoryBytes,onOOMandassetUrl. - The bookmarks, attachments and fonts panels.
- ICO, TGA, PPM, PGM, PBM, HDR, EXR and QOI images.
What's new
0.8 does a good deal 0.7 didn't: it opens Word templates, Markdown, HTML, email, video and audio, and web pages; it fills in PDF forms; it plays presentations, and PDFs, as slide shows with the presenter view; it saves and exports documents; and with @docmentis/udoc-editor, people edit them, on their own or together. Supported formats covers what opens and what can be done with it, and the changelog every release since 0.8.0.