Web SDKv1.0.0

PDFViewer

The viewer: renders a PDFDocument inside a DOM container and wires its pages, form fields, toolbar, sidebar and tools. It takes a PDFViewerConfig; viewer.config is that configuration resolved over the defaults, with what the license does not cover switched off.

The viewer is on the page when the constructor returns; it is ready — pages laid out, fields and tools wired — a moment later, which whenReady() resolves at and isReady reports. Every member that acts throws INVALID_STATE after destroy(); the getters keep answering their last values. Which license feature each member needs is stated on the member; a member without one needs nothing beyond an open document.

classnew PDFViewer(container, doc, config?)

Usage

TypeScript
import { Kernstra, PDFDocument, PDFViewer } from '@kernstra/pdf-web-sdk';

await Kernstra.init({ license: import.meta.env.KERNSTRA_LICENSE });

const container = document.getElementById('viewer')!;
const hideLoader = PDFViewer.showLoadingIndicator(container);
const doc = await PDFDocument.fromUrl('/report.pdf');
const viewer = new PDFViewer(container, doc, { mode: 'form-fill' });
await viewer.whenReady();
hideLoader();

viewer.on('pageChanged', ({ pageIndex }) => setCurrentPage(pageIndex));

// Later
viewer.destroy();
await doc.close();

Constructor

new PDFViewer(container: HTMLElement, document: PDFDocument, config?: PDFViewerConfig)

Throws NOT_INITIALIZED before Kernstra.init() has resolved, DOCUMENT_CLOSED for a document already closed, INVALID_ARGUMENT when container is not an element (a ref not yet set), INVALID_STATE when the container already holds a viewer (destroy that one first — a component that re-renders builds one viewer per element, never a second into the same one), and ENGINE_FAILED when the SDK has stopped on an unrecoverable fault (fatal). The viewer is on the page when the constructor returns; it is ready a moment later, which whenReady() resolves at. A loader PDFViewer.showLoadingIndicator put in the container stays over the viewer until you remove it, so "show the loader, open, construct, await viewer.whenReady(), hide" is the whole sequence. If new PDFViewer() throws, the viewer has already undone whatever it had set up; there is nothing to destroy.

ParameterTypeDescription
containerHTMLElementThe element the viewer is rendered into.
documentPDFDocumentAn open PDFDocument.
config?PDFViewerConfigThe viewer configuration; your options over the defaults.

Properties

currentPageIndexgettersince 1.0.0
get currentPageIndex(): number
The page on show, as a zero-based index — what pageChanged last named.
Returns number
TypeScript
label.textContent = `Page ${viewer.currentPageIndex + 1} of ${doc.pageCount}`;
scalegettersince 1.0.0
get scale(): number
Current zoom scale (1.0 = 100%).
Returns number
TypeScript
zoomLabel.textContent = `${Math.round(viewer.scale * 100)}%`;
configgettersince 1.0.0
get config(): Readonly<ResolvedPDFViewerConfig>
Resolved configuration: your options over the defaults, with what the license does not cover switched off (its tools, and toolbar.print / download). Its tools names every tool: false for one you, the mode or the license switched off, otherwise your options for it or true. Follows the rights of the viewer's document: grows at once when they grow, and narrows only when pages are imported or another document is opened.
Returns Readonly<ResolvedPDFViewerConfig>
TypeScript
const canSign = viewer.config.tools.sign !== false;
toolsgettersince 1.0.0
get tools(): ToolsApi
Handles for running tools — see viewer.tools.editText. A handle throws SDKError INVALID_STATE, with instructions, when its tool is not running, rather than failing silently.
Returns ToolsApi
TypeScript
await viewer.activateTool('editText');
const blocks = await viewer.tools.editText.listBlocks(0);
hasUnsavedChangesgettersince 1.0.0
get hasUnsavedChanges(): boolean
true from the first change on — a tool's, a page's, a field filled in or signed, the document API's, an undo — until the document is exported whole by an exporter from createExporter() (the toolbar's Download; download(), getBlob(), getArrayBuffer(), getBase64(), uploadTo() without pages) or markSaved() is called. A change made while an export ran stays unsaved; an undo back to the saved state still counts. documentChanged reports every change of it; nothing reports it going back to false. The export is the "save": a host that uploads the bytes keeps its own flag for an upload that failed, or a "leave without saving?" guard lets the user go after one — see Saving as the user works.
Returns boolean
TypeScript
window.addEventListener('beforeunload', (e) => {
  if (viewer.hasUnsavedChanges) e.preventDefault();
});
isReadygettersince 1.0.0
get isReady(): boolean
Whether the viewer is ready: its pages laid out and its tools wired. whenReady() resolves at that moment.
Returns boolean
activeToolgettersince 1.0.0
get activeTool(): ToolName | null
The active tool's name — 'selectText' at rest by default, from the moment the viewer goes back to it — or null when no tool is active. A property, read like viewer.scale.
Returns ToolName | null
TypeScript
const editing = viewer.activeTool === 'editText';
modegettersince 1.0.0
get mode(): SDKMode
The viewer's operating mode, as configured — see SDKMode and the resting tool.
Returns SDKMode
activePanelgettersince 1.0.0
get activePanel(): SidebarTab | null
The open sidebar panel, or null when the sidebar is collapsed. The panelChanged event reports every change of it.
Returns SidebarTab | null

Navigation

goToPagemethodsince 1.0.0
goToPage(pageIndex: number): void
Scroll to page (zero-based index).
ParameterTypeDescription
pageIndexnumberZero-based page index.
Returns void
  • INVALID_ARGUMENT for an index that is not a whole number or names no page.
  • INVALID_STATE after destroy().
  • DOCUMENT_CLOSED once the document has been closed.
TypeScript
viewer.goToPage(doc.pageCount - 1); // the last page
nextPagemethodsince 1.0.0
nextPage(): void
Navigate to next page. Stops at the end.
Returns void
  • INVALID_STATE after destroy().
prevPagemethodsince 1.0.0
prevPage(): void
Navigate to previous page. Stops at the first page.
Returns void
  • INVALID_STATE after destroy().

Zoom

setScalemethodsince 1.0.0
setScale(scale: number, anchor?: { clientX: number; clientY: number }): void
Set zoom level (1.0 = 100%). The same scale again changes nothing. The pages are redrawn at the new scale a moment after the call returns.
ParameterTypeDescription
scalenumberThe zoom level (1.0 = 100%). A positive number.
anchor?{ clientX: number; clientY: number }Keeps the point under it in place; without it, the middle of the view.
Returns void
  • INVALID_ARGUMENT for a scale that is not a positive number.
  • INVALID_STATE after destroy().
TypeScript
viewer.setScale(1.5);
zoomInmethodsince 1.0.0
zoomIn(): void
One step, as the toolbar's buttons and ⌘/Ctrl + do: to the next preset of navigation.zoomPresets above the current scale, then by steps up to navigation.maxZoom. Leaves a fit mode.
Returns void
  • INVALID_STATE after destroy().
zoomOutmethodsince 1.0.0
zoomOut(): void
One step, as the toolbar's buttons and ⌘/Ctrl − do: to the next preset of navigation.zoomPresets below the current scale, then by steps down to navigation.minZoom. Leaves a fit mode.
Returns void
  • INVALID_STATE after destroy().
fitToWidthmethodsince 1.0.0
fitToWidth(): void
Zoom to fit page width in container.
Returns void
  • INVALID_STATE after destroy().
fitToPagemethodsince 1.0.0
fitToPage(): void
Zoom to fit entire page in viewport.
Returns void
  • INVALID_STATE after destroy().

Tools

activateToolmethodsince 1.0.0
activateTool(tool: ToolName): Promise<void>
Activate a tool (again: put it away, back to the resting tool). Edit Text left with edits pending writes them first; a refused write keeps it, and the promise resolves with it still active. Needs the tool's license feature; refused for a tool the license does not cover.
ParameterTypeDescription
toolToolNameOne of the 26 names of TOOL_NAMES, in the toolbar's order. Two of them are panels rather than tools: 'search' and 'managePages' open their panel and resolve, and activeTool stays what it was — openPanel('search') / openPanel('pages') are the same action by their own name. The exported PANEL_TOOL_NAMES names those two, so a host that marks its buttons pressed from activeTool or toolChanged need not hard-code them.
Returns Promise<void>
  • INVALID_ARGUMENT for a name that is no tool or a tool that is off — see isToolEnabled.
  • FEATURE_NOT_LICENSED for one the license does not cover.
  • PERMISSION_DENIED for one the document's permissions forbid — see toolBlockedReason.
  • UNKNOWN_ERROR (the cause as its cause; or the tool's own SDKError) for a tool that could not start — nothing of it stays, and the viewer goes back to the resting tool.
  • INVALID_STATE after destroy().
TypeScript
await viewer.activateTool('highlight');
await viewer.activateTool('highlight'); // again: back to the resting tool
deactivateToolmethodsince 1.0.0
deactivateTool(): Promise<void>
Put the active tool away and go back to the resting tool (defaultTool, Select by default); with the resting tool active, put down what is selected — an annotation picked up, the text Select selected. Edit Text with edits pending writes them first, as Done does, and the promise resolves once the tool is away; a refused write keeps the tool (activeTool tells). With nothing pending the tool is away before the call returns.
Returns Promise<void>
  • INVALID_STATE after destroy().
TypeScript
await viewer.deactivateTool();
isToolEnabledmethodsince 1.0.0
isToolEnabled(tool: ToolName): boolean
Whether a tool may run: covered by the license, not false in tools, and offered by the mode (Select, Pan and Search in every mode, Sign in FORM_FILL and FULL, the rest in FULL only). toolbar.groups does not affect it. Mirror it in your own chrome instead of duplicating the rule.
ParameterTypeDescription
toolToolNameThe tool to ask about.
Returns boolean
TypeScript
signButton.hidden = !viewer.isToolEnabled('sign');
toolBlockedReasonmethodsince 1.0.0
toolBlockedReason(tool: ToolName): ToolBlockedReason | null
Why the open document forbids a tool — { reason, message }: reason is 'permission' (the document's permissions), 'certified' (its certification) or 'downloading' (it edits, and the document's bytes are still arriving), message the words in the viewer's language ("This document does not allow comments") — or null when it allows it. A tool it forbids is still offered, grayed out with the message. A host drawing its own chrome shows the same, and branches on reason.
ParameterTypeDescription
toolToolNameThe tool to ask about.
Returns ToolBlockedReason | null { reason, message }, or null when the document allows it (and on a destroyed viewer).
TypeScript
const blocked = viewer.toolBlockedReason('note');
noteButton.disabled = blocked !== null;
noteButton.title = blocked?.message ?? '';

History

snapshotmethodsince 1.0.0
snapshot(): Promise<void>
Save current document state for undo (called automatically after committed edits). Skipped, with an error event, while a progressively-loaded document is still downloading — so undo has a gap until whenFullyLoaded() resolves. A change whose undo step could not be recorded (an error with EXPORT_FAILED) still counts: hasUnsavedChanges turns true and documentChanged reports it. A change made through the document API while Edit Text has unwritten edits writes those edits first, then reloads; if they cannot be written, the viewer reports INVALID_STATE on error and the edits are lost.
Returns Promise<void> Resolves once recorded.
  • INVALID_STATE after destroy().
undomethodsince 1.0.0
undo(): Promise<void>
Restore previous document state. Edit Text edits pending are written first, as one step, and that step is what this undo backs out; nothing is undone when the write is refused.
Returns Promise<void> Resolves once restored.
  • INVALID_STATE after destroy().
TypeScript
if (viewer.canUndo()) await viewer.undo();
redomethodsince 1.0.0
redo(): Promise<void>
Re-apply previously undone state. Edit Text edits pending are written first, as for undo().
Returns Promise<void> Resolves once re-applied.
  • INVALID_STATE after destroy().
canUndomethodsince 1.0.0
canUndo(): boolean
Returns true if undo is available. For buttons that follow the stack, listen to documentChanged, which carries canUndo and canRedo.
Returns boolean
canRedomethodsince 1.0.0
canRedo(): boolean
Returns true if redo is available.
Returns boolean
markSavedmethodsince 1.0.0
markSaved(): void
Tells the viewer the document as it stands is saved: hasUnsavedChanges is false until the next change. Called for you when the document is exported whole; call it yourself after saving through doc.save() or your own path.
Returns void
  • INVALID_STATE after destroy().
TypeScript
const bytes = await doc.save();
await fetch('/documents/42', { method: 'PUT', body: bytes.slice() });
viewer.markSaved();

Forms

focusFieldmethodsince 1.0.0
focusField(name: string): boolean
Scroll a field into view (any page) and focus its editor. Values are read and set through doc.forms, with or without a viewer. While this viewer is open they go through its fields: shown at once, on its undo stack, in its export, and formFieldChanged fires once per applied field.
ParameterTypeDescription
namestringThe field's fully qualified name.
Returns boolean false if unknown.
  • INVALID_STATE after destroy().
TypeScript
viewer.focusField('applicant.email');

Comments and panels

showCommentmethodsince 1.0.0
showComment(id: string): Promise<boolean>
Open the Comments panel on the comment with that id (a reply's or a status's id opens its comment): its card chosen, its mark scrolled to and outlined.
ParameterTypeDescription
idstringA comment's id; a reply's or a status's id opens its comment.
Returns Promise<boolean> false if no comment has it or the panel is not offered.
  • INVALID_STATE after destroy().
TypeScript
await viewer.showComment(comment.id);
commentPermissionsmethodsince 1.0.0
commentPermissions(id: string): Promise<CommentPermissions | null>
What the user may do to a comment by the panel's rules — { reply, setStatus, edit, delete }; null when no comment has that id (none, or a link, form field, pop-up or hidden annotation).
ParameterTypeDescription
idstringThe comment's id.
Returns Promise<CommentPermissions | null> { reply, setStatus, edit, delete }, each a boolean.
  • INVALID_STATE after destroy().
TypeScript
const may = await viewer.commentPermissions(id);
deleteButton.hidden = may?.delete !== true;
openPanelmethodsince 1.0.0
openPanel(panel: SidebarTab | null): boolean
Open a sidebar panel, or close the sidebar with null. Which panels are offered is the sidebar option.
ParameterTypeDescription
panelSidebarTab | null'pages', 'outline', 'attachments', 'signatures' or 'comments'; null closes the sidebar.
Returns boolean false if that panel is not offered.
  • INVALID_STATE after destroy().
TypeScript
viewer.openPanel('comments');

Coordinates

pageToClientmethodsince 1.0.0
pageToClient<T extends Rect | Point>(value: T, pageIndex: number): T | null
A point or rectangle of a page, in page space, as it lies in the viewer now: CSS pixels from the container's top-left corner at the current zoom, scroll and rotation (a Rect comes back with its top-left corner as x, y, as the DOM measures). Convert again after zoomChanged, a scroll or a resize. Page space and the geometry helpers are described under Helpers.
ParameterTypeDescription
valueRect | PointA point or rectangle in page space.
pageIndexnumberZero-based page index.
Returns Rect | Point | null null before the page is laid out, or on a destroyed viewer.
  • INVALID_ARGUMENT for a page index the document has not, or a value that is neither a Point nor a Rect of finite numbers.
TypeScript
const [field] = await doc.forms.fields(0);
const box = field ? viewer.pageToClient(field.rect, 0) : null;
if (box) {
  badge.style.transform = `translate(${box.x}px, ${box.y}px)`;
}
clientToPagemethodsince 1.0.0
clientToPage<T extends Rect | Point>(value: T, pageIndex: number): T | null
The reverse of pageToClient: CSS pixels from the container's top-left corner, as the page holds them.
ParameterTypeDescription
valueRect | PointCSS pixels from the container's top-left corner.
pageIndexnumberZero-based page index.
Returns Rect | Point | null null before the page is laid out, or on a destroyed viewer.
  • INVALID_ARGUMENT for a page index the document has not, or a value that is neither a Point nor a Rect of finite numbers.
TypeScript
container.addEventListener('click', (e) => {
  const bounds = container.getBoundingClientRect();
  const at = viewer.clientToPage({ x: e.clientX - bounds.left, y: e.clientY - bounds.top }, 0);
  // at: where on page 0 the user clicked, in page space, or null
});

Export and print

createExportermethodsince 1.0.0
createExporter(config?: ExportConfig): PDFExporter
A PDFExporter wired to this viewer's form values and signatures. Its defaults are the viewer's export option; config overrides them. Obtain the exporter here rather than with new PDFExporter(document): it carries what the viewer holds and reports exportStarted / exportCompleted on the viewer, and an export of the whole document clears hasUnsavedChanges the moment the bytes leave the viewer — whether your upload of them succeeds is yours to track (see Saving as the user works).
ParameterTypeDescription
config?ExportConfigOverrides the viewer's export option for this exporter.
Returns PDFExporter
  • INVALID_STATE after destroy().
TypeScript
const exporter = viewer.createExporter();
await exporter.download('report.pdf');
printmethodsince 1.0.0
print(options?: PrintOptions): Promise<void>
Ask for pages and annotations (or take them from options), render at 300 DPI (within performance.maxRenderPixels) with progress, and open the browser print dialog. Needs the print feature.
ParameterTypeDescription
options?PrintOptions{ pages?: number[]; annotations?: boolean } — the pages to print (zero-based; every page when omitted) and whether annotations are printed (true when omitted). Without it the viewer asks for pages and annotations.
Returns Promise<void> Resolves when the browser reports the print dialog closed (afterprint), or after a minute where it never does.
  • INVALID_ARGUMENT for a pages list that is empty or names a page the document has not.
  • FEATURE_NOT_LICENSED without the print feature.
  • PERMISSION_DENIED when the document forbids printing.
  • INVALID_STATE after destroy().
TypeScript
await viewer.print({ pages: [0, 1], annotations: false });

Events

onmethodsince 1.0.0
on<K extends keyof ViewerEventMap>(event: K, handler: (data: ViewerEventMap[K]) => void, options?: SubscribeOptions): () => void
Subscribe to a typed event. Document lifecycle events (initialized, licenseChanged, documentLoaded, documentClosed) are global and fire on Kernstra.events, not here; a handler registered on the wrong emitter never runs and never warns — see EventEmitter.
ParameterTypeDescription
eventkeyof ViewerEventMapA per-viewer event name — see Events.
handler(data: ViewerEventMap[K]) => voidReceives the typed payload.
options?SubscribeOptionsonce hears the next emit only; signal unsubscribes when an AbortSignal aborts (a signal already aborted subscribes nothing).
Returns () => void The unsubscribe.
  • INVALID_STATE after destroy().
TypeScript
const stop = viewer.on('pageChanged', ({ pageIndex }) => setCurrentPage(pageIndex));
stop();

const controller = new AbortController();
viewer.on('zoomChanged', ({ scale }) => setZoom(scale), { signal: controller.signal });
viewer.on('documentChanged', () => markDirty(), { signal: controller.signal });
controller.abort(); // both gone
offmethodsince 1.0.0
off<K extends keyof ViewerEventMap>(event: K, handler: (data: ViewerEventMap[K]) => void): void
Unsubscribe from an event, with the same handler reference.
ParameterTypeDescription
eventkeyof ViewerEventMapThe event name.
handler(data: ViewerEventMap[K]) => voidThe same handler reference that was passed to on().
Returns void
TypeScript
const onPage = (data: ViewerEventMap['pageChanged']) => setCurrentPage(data.pageIndex);
viewer.on('pageChanged', onPage);
viewer.off('pageChanged', onPage);

Lifecycle

whenReadymethodsince 1.0.0
whenReady(): Promise<void>
Resolves once the viewer is ready: pages laid out, form fields and tools wired, the pages in view queued to paint — the moment to remove a loading indicator of your own. A progressive document's later pages still arrive after it (doc.whenFullyLoaded() says when every byte has).
Returns Promise<void>
  • ABORTED if destroy() comes first.
  • PAGE_ERROR when the file's fields or pages cannot be wired (the viewer stands; the error event reports it too).
TypeScript
const viewer = new PDFViewer(container, doc);
await viewer.whenReady();
hideLoader();
destroymethodsince 1.0.0
destroy(): void

Removes everything the viewer added to the page: its DOM, every document- and window-level listener, observers, timers, the render worker, its tools, and any of its dialogs or popovers still open (a pending print() resolves without printing). Safe to call twice. A shape or annotation still selected goes back into the document where it was. With the last viewer on the page it also removes the shared stylesheets. The Image tool's recent images, saved signatures and user stamps stay for the page session, for the next viewer.

It does not close the underlying PDFDocument — call document.close() yourself, before or after, or you leak its memory. After it, every member that acts — goToPage(), setScale(), activateTool(), print(), search(), openPanel(), undo(), createExporter() and the rest — throws INVALID_STATE rather than doing nothing; the getters keep answering their last values. A stale handler that still reaches the viewer is a bug this makes visible.

Returns void
TypeScript
viewer.destroy();
await doc.close();

Static

showLoadingIndicatorstatic methodsince 1.0.0
static showLoadingIndicator(container: HTMLElement, options?: { loader?: LoaderConfig; locale?: LocaleConfig }): () => void
Show a loading spinner before a viewer exists. Involves no document. A loader put in the container stays over the viewer until you remove it: show the loader, open, construct, await viewer.whenReady(), hide.
ParameterTypeDescription
containerHTMLElementThe element the viewer will be rendered into.
options?{ loader?: LoaderConfig; locale?: LocaleConfig }options.loader is a LoaderConfig and options.locale a LocaleConfig — pass the ones you give the viewer.
Returns () => void A cleanup function.
TypeScript
const hide = PDFViewer.showLoadingIndicator(container, { locale: { lang: 'de' } });
const doc = await PDFDocument.fromUrl('/report.pdf');
const viewer = new PDFViewer(container, doc, { locale: { lang: 'de' } });
await viewer.whenReady();
hide();