Comprehensive Guide for Developers: UI & SDK Integration, Configuration, and API Reference
Configure callback functions to respond to editor events and customize file operations.
Monitor loading state changes to show/hide loading indicators.
Type signature:
onLoadingStateChange?: (isLoading: boolean) => void
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onLoadingStateChange: (isLoading: boolean) => {
const loader = document.getElementById('loading-spinner');
if (loader) {
loader.style.display = isLoading ? 'block' : 'none';
}
},
});
Respond to configuration changes. Triggered when layout, theme, language, branding, default font, or units & guides settings change. See ui.IConfigChangeData.
Type signature:
onConfigChange?: (config: IConfigChangeData) => void
IConfigChangeData interface:
interface IConfigChangeData {
themeConfig?: IThemeConfig;
defaultFont?: IDefaultFontConfig;
unitsAndGuides?: IUnitsAndGuidesConfig;
languageConfig?: ILanguageConfig;
brandingConfig?: IBrandingConfig;
toolsLayout?: LabelAlignment;
ocrAdvancedConfig?: IOcrAdvancedConfig;
openDocumentsInNewTab?: boolean;
restoreLastViewSettings?: boolean;
sidebarCollapsed?: boolean;
sidebarExpandOnHover?: boolean;
}
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onConfigChange: async (config) => {
console.log('Configuration changed:', config);
// Save user preferences to localStorage
localStorage.setItem('pdfUserPreferences', JSON.stringify(config));
// Or sync to backend
await fetch('/api/user/preferences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
},
});
Handle errors that occur during PDF operations. This callback receives standard JavaScript Error objects.
Type signature:
onError?: (error: Error) => void
Example:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onError: (error: Error) => {
console.error('PDF Editor Error:', error);
// Show user-friendly error message
showNotification({
type: 'error',
title: 'PDF Error',
message: error.message,
});
},
});
Integrate with error tracking services like Sentry, LogRocket, or custom logging:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
import * as Sentry from '@sentry/browser';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onError: (error: Error) => {
// Log to Sentry
Sentry.captureException(error, {
tags: {
component: 'pdf-editor',
action: 'pdf-operation',
},
extra: {
timestamp: new Date().toISOString(),
userId: currentUser?.id,
},
});
// Display user-friendly message
showToast({
type: 'error',
message: 'An error occurred while processing the PDF. Please try again.',
});
},
});
Handle password-protected files with a custom UI. The callback receives the error details and returns the password. The viewer waits for the returned promise, so a modal can take as long as it needs.
Type signature:
onPasswordRequired?: IPasswordRequiredCallback;
type IPasswordRequiredCallback = (error: IPasswordRequiredError) => Promise<string>;
Exports:
import type { IPasswordRequiredCallback, IPasswordRequiredError } from '@avanquest/pdf-web-viewer';
IPasswordRequiredError carries documentName, message, errorCode, operation and
documentId. On the first prompt message is the engine's own text; on a retry after a wrong
password it is the localized "Incorrect password".
Example: Custom password modal with error message
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPasswordRequired: async (error: IPasswordRequiredError) => {
const password = await showPasswordModal({
title: 'Password required',
fileName: error.documentName,
message: error.message,
});
// Return the password to retry, or '' to cancel the open.
return password ?? '';
},
});
When onPasswordRequired is provided, the built-in password dialog is suppressed.
Retrying is automatic. The viewer re-opens the document with whatever the callback returns;
if that password is wrong it calls the callback again, with message set to "Incorrect
password". Keep returning passwords to keep retrying — there is no separate retry call to make.
To cancel, return an empty string or reject the promise. The open then fails with a
PasswordDeclinedError, which is treated as a cancel rather than a failure: no error message is
shown, and the file keeps its place in the recent-files list.
The callback covers every password prompt in the viewer, not just opening: the owner-password requests raised by Compress, Convert, Organize, Document Properties and Merge route through it too.
Opening without the UI. PdfSdk.openDocument() sits below the viewer and never prompts —
onPasswordRequired does not apply to it. Pass a known password with the file, or catch the
rejected PdfEditorPasswordError and handle the prompt yourself:
import { PdfSdk } from '@avanquest/pdf-web-viewer/sdk';
const document = await PdfSdk.openDocument({ file, password: 'secret' });
Replace the default file picker with a custom implementation. When provided, this callback is invoked instead of the default file input behavior — from every open entry point, including "Open document" in a side-by-side pane: the file you then pass to pdfWebService.openDocument(file) lands in the pane the gesture pointed at.
Type signature:
onOpenFile?: () => Promise<void>
Example: Custom File Picker
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onOpenFile: async () => {
// Show custom file picker
const file = await customFilePicker({
accept: '.pdf',
multiple: false,
});
if (file) {
// Open the selected file using the service
await editor.ui.pdfWebService.openDocument(file);
}
},
});
Example: Cloud Storage Integration
Integrate with Google Drive, Dropbox, OneDrive, or other cloud storage:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onOpenFile: async () => {
try {
// Show cloud file picker (e.g., Google Drive Picker)
const fileUrl = await googleDrivePicker.pick({
mimeTypes: ['application/pdf'],
});
if (fileUrl) {
// Fetch file from cloud storage
const response = await fetch(fileUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const blob = await response.blob();
const file = new File([blob], 'document.pdf', { type: 'application/pdf' });
// Open the file
await editor.ui.pdfWebService.openDocument(file);
}
} catch (error) {
console.error('Failed to open file from cloud:', error);
showToast({ type: 'error', message: 'Failed to open file from cloud storage' });
}
},
});
Which file formats the open UI offers. The viewer reads these itself, and they are always offered:
| Extension | accept sent to the picker |
|---|---|
pdf |
application/pdf |
wwf |
.wwf |
png |
image/png |
tiff / tif |
image/tiff / image/tif |
jpeg / jpg |
image/jpeg / image/jpg |
bmp |
image/bmp |
gif |
image/gif |
txt |
text/plain, .txt |
Office documents are offered too: docx, dotx, doc, rtf, pptx, potx, ppt, xlsx,
xltx, xls, xlt, csv, odt, ott, odp and ods (each with its MIME type plus a
.ext accept token). The viewer converts them to PDF through the Developers API
Office to PDF endpoint before opening, so the call goes to
apiToolsUrl and is authorized per
apiAuthMode — by default with your license key.
Files over 100 MB are rejected by the API.
Anything else is greyed out: the file picker filters it, and a cloud-drive listing shows it
but refuses the click. openFormatsConfig.additional widens that set — for formats your
code opens.
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
openFormatsConfig: {
additional: [
{ ext: 'epub', accept: ['.epub', 'application/epub+zip'] },
{ ext: 'md' }, // accept defaults to ['.md']
],
},
// …and convert them before the viewer sees them.
onOpenFile: async () => {
const file = await myPicker();
const pdf = /\.(epub|md)$/i.test(file.name) ? await myConverter.toPdf(file) : file;
await editor.ui.pdfWebService.openDocument(pdf);
},
});
Type signature:
openFormatsConfig?: {
additional?: Array<{ ext: string; accept?: string[] }>;
};
ext has no leading dot and matches case-insensitively; accept takes MIME types, .ext
tokens, or both, exactly as <input type="file" accept> does, and defaults to ['.<ext>'].
An entry without a usable ext is skipped with a console error rather than corrupting the
picker's filter. The list is replaced on each config change, so dropping it drops the formats.
Declaring a format only makes it selectable — it does not teach the viewer to read it.
Convert the file in onOpenFile (or in your provider's getFileAsFile) before handing it
over. One that reaches the viewer's own processing unconverted is rejected like any
unreadable file, on the standard open-failure notification:
Failed to open file "book.epub": File format is not supported
Customize how files leave the viewer. The callback receives a File object with the filename already set, plus the claims of the operations that produced its contents.
This is the single exit point for every export — the document itself (Download/Save, Ctrl+S), converted files, extracted images, split/compressed results, attachments, form data and snapshots all flow through it.
Type signature:
onExportFile?: (file: File, claims: readonly string[]) => void
claims is the full provenance of the file, not just its last step: the claims of the document
it came from plus the operation that produced this artefact. A compressed export of a merged,
OCR'd document reports all three, oldest first —
['Create.CombineFiles', 'Ocr', 'Edit.Optimize.ReduceFileSize'].
The order is the order things happened, so it is deliberately not sorted; sort it yourself if you
need a canonical form. The same list reaches deviceConfig.saveOver / saveInto, the
customProviders save methods, and the drives' isSaveAllowed guard.
Tip: one-way on purpose — it delivers a copy and reports nothing back, so it is not a save channel. Writing the document to the machine is
deviceConfig.saveOver/saveInto; the export is only what happens when neither is implemented. See Saving & Downloading.
Example: Custom Download with Timestamp
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onExportFile: (file: File) => {
// Add timestamp to filename
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const newFilename = `${file.name.replace('.pdf', '')}-${timestamp}.pdf`;
// Create download link
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = newFilename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
});
Example: Cloud Storage Upload
Save files to cloud storage instead of downloading to the user's device:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onExportFile: async (file: File) => {
try {
// Show upload progress
showToast({ type: 'info', message: 'Uploading to cloud...' });
// Upload to cloud storage
const formData = new FormData();
formData.append('file', file);
formData.append('userId', currentUser.id);
const response = await fetch('/api/documents/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('Upload failed');
}
const { url, documentId } = await response.json();
// Show success message
showToast({
type: 'success',
message: `File saved to cloud`,
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
// Update UI or navigate
console.log('Document ID:', documentId);
} catch (error) {
console.error('Upload failed:', error);
showToast({ type: 'error', message: 'Failed to upload file to cloud' });
}
},
});
Customize the print behavior. The callback receives the PDF file and an optional flag indicating whether to open in a new tab.
Type signature:
onPrint?: (file: File, newTab: boolean, claims: readonly string[]) => void
claims lists the operations the printed contents went through, the same list onExportFile
receives — a print hands a copy of the document over too. See
Saving & Downloading.
Example: Custom Print Handler
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPrint: (file: File, newTab?: boolean) => {
const printUrl = URL.createObjectURL(file);
if (newTab) {
// Open in new tab for printing
window.open(printUrl, '_blank');
} else {
// Print directly using hidden iframe
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.style.position = 'absolute';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
// Clean up after printing
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
});
Example: Print with Analytics
Track print operations:
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onPrint: (file: File, newTab?: boolean) => {
// Track print event
analytics.track('pdf_print', {
fileName: file.name,
fileSize: file.size,
printMode: newTab ? 'new-tab' : 'direct',
timestamp: Date.now(),
});
// Perform print
const printUrl = URL.createObjectURL(file);
if (newTab) {
window.open(printUrl, '_blank');
} else {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
});
Handle snapshot area captures with custom logic. When this callback is provided, the default snapshot preview dialog is bypassed, and the callback receives a Blob containing the PNG image data.
Type signature:
onSnapshotArea?: (blob: Blob, claims: readonly string[]) => void
claims is always just ['View.Snapshot']. A capture is a raster of a page region, not the
document, so what the document itself went through is not in the image and its claims do not
travel with it. See Saving & Downloading.
Example: Automatic Download
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: (blob: Blob) => {
// Automatically download the snapshot
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `snapshot-${Date.now()}.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showToast({
type: 'success',
message: 'Snapshot downloaded successfully',
});
},
});
Example: Upload to Server
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: async (blob: Blob) => {
try {
console.log('Snapshot captured:', blob.size, 'bytes');
// Upload to backend
const formData = new FormData();
formData.append('snapshot', blob, `snapshot-${Date.now()}.png`);
formData.append('userId', currentUser.id);
const response = await fetch('/api/snapshots/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
},
body: formData,
});
if (!response.ok) {
throw new Error('Upload failed');
}
const { snapshotId, url } = await response.json();
showToast({
type: 'success',
message: 'Snapshot uploaded successfully',
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
console.log('Snapshot ID:', snapshotId);
} catch (error) {
console.error('Failed to upload snapshot:', error);
showToast({
type: 'error',
message: 'Failed to upload snapshot',
});
}
},
});
Example: Copy to Clipboard
import { PdfEditor } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onSnapshotArea: async (blob: Blob) => {
try {
// Copy image to clipboard
await navigator.clipboard.write([
new ClipboardItem({
'image/png': blob,
}),
]);
showToast({
type: 'success',
message: 'Snapshot copied to clipboard',
});
} catch (error) {
console.error('Failed to copy snapshot:', error);
showToast({
type: 'error',
message: 'Failed to copy snapshot to clipboard',
});
}
},
});
Behavior:
Hand the viewer's Fullscreen button to the host. Use it when the viewer is drawn inside a
window it cannot fill by itself — a desktop shell (Tauri, Electron), an embedder with its own
chrome. With the callback set the viewer never calls the browser Fullscreen API: it asks, you
resize the window, and its layout follows what you report back through
setFullscreenLayout().
Type signature:
onFullscreenRequested?: (fullscreen: boolean) => void
Example: Desktop Shell Owning The Window
import { PdfEditor } from '@avanquest/pdf-web-viewer';
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onFullscreenRequested: (fullscreen) => appWindow.setFullscreen(fullscreen),
});
// Report every change back, including the ones the viewer never asked for — a native
// menu item, the window button, an OS shortcut — so its UI keeps matching the window.
appWindow.onResized(async () => {
editor.ui.pdfWebElement.setFullscreenLayout(await appWindow.isFullscreen());
});
Behavior:
fullscreenchange — the web default.setFullscreenLayout() is the
only thing that moves its UI (in fullscreen the header collapses and the view-options button
moves into a floating strip over the document).Override the built-in document context menu. When the user right-clicks (or long-presses) within the document, the callback receives the context menu type and the mouse position, so the host application can show its own menu or fall back to the default one.
Type signature:
onContextMenuRequested?: (data: IContextMenuRequestData) => boolean | Promise<boolean>
interface IContextMenuRequestData {
/** Which context menu the viewer would show (page background, selected object, text editor, …). */
menuType: ContextMenu;
/** Click position relative to the document render element. */
position: { x: number; y: number };
/** Bitmask of ContextMenuOperations flags allowed for the current selection. */
operations: ContextMenuOperations;
}
enum ContextMenuOperations {
AllowCopy = 1 << 0,
AllowPaste = 1 << 1,
AllowCut = 1 << 2,
AllowUndo = 1 << 3,
AllowRedo = 1 << 4,
AllowToBack = 1 << 5,
AllowToFront = 1 << 6,
AllowOptList = 1 << 7,
AllowMergeTextBlocks = 1 << 8,
ESignFieldRequired = 1 << 9,
}
The same mask drives the enabled/disabled state of the built-in menu items — test flags with a bitwise AND to decide which items of a custom menu to enable.
Example: Custom Context Menu
import { PdfEditor, ContextMenu, ContextMenuOperations, IContextMenuRequestData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onContextMenuRequested: (data: IContextMenuRequestData) => {
// Only override the plain page menu; keep the default for everything else
if (data.menuType !== ContextMenu.View) {
return false;
}
showMyContextMenu({
x: data.position.x,
y: data.position.y,
items: [
{ label: 'My Action', onClick: () => doSomething() },
{ label: 'Paste', disabled: (data.operations & ContextMenuOperations.AllowPaste) === 0, onClick: () => pasteHere(data.position) },
{ label: 'Add text', onClick: () => addTextHere(data.position) },
],
});
return true; // handled — the built-in menu is suppressed
},
});
Behavior:
true: The built-in menu is suppressed — the host application is expected to show its own UIfalse: The viewer falls back to the built-in context menudata.position is the click position relative to the document render element.Executing menu actions from a custom menu:
Items of the built-in menu map to DocumentCommands executed on the document view. From a custom menu, run them via doCommand, passing data.position back unchanged for position-based commands:
const view = editor.ui.pdfWebService.getPrimaryDocumentViewElement()?.documentView;
view?.doCommand(DocumentCommand.COPY);
view?.doCommand(DocumentCommand.PASTE, data.position); // paste at the click point
view?.doCommand(DocumentCommand.TYPE_TEXT, data.position); // start the add-text tool at the click point
view?.doCommand(DocumentCommand.INSERT_IMAGE, data.position); // open the image picker, insert at the click point
await view?.print();
Comprehensive example showing all callbacks working together:
import { PdfEditor, IConfigChangeData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
// State monitoring
onLoadingStateChange: (isLoading: boolean) => {
const spinner = document.getElementById('loading-spinner');
if (spinner) {
spinner.style.display = isLoading ? 'flex' : 'none';
}
},
onConfigChange: (config: IConfigChangeData) => {
// Persist user preferences
localStorage.setItem('pdfUserPreferences', JSON.stringify(config));
// Optionally sync to backend
fetch('/api/user/preferences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
}).catch((err) => console.error('Failed to sync preferences:', err));
},
// Error handling
onError: (error: Error) => {
console.error('PDF Editor Error:', error);
// Log to error tracking
if (window.Sentry) {
window.Sentry.captureException(error);
}
// Show user notification
showToast({
type: 'error',
message: error.message || 'An error occurred',
});
},
// File operations
onOpenFile: async () => {
try {
const file = await showCustomFilePicker({
accept: '.pdf',
multiple: false,
});
if (file) {
await editor.ui.pdfWebService.openDocument(file);
showToast({ type: 'success', message: 'File opened successfully' });
}
} catch (error) {
console.error('Failed to open file:', error);
showToast({ type: 'error', message: 'Failed to open file' });
}
},
onExportFile: async (file: File) => {
try {
// Upload to cloud storage
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/documents/save', {
method: 'POST',
body: formData,
});
if (response.ok) {
const { documentId } = await response.json();
showToast({
type: 'success',
message: 'File saved to cloud',
action: {
label: 'View',
onClick: () => window.open(`/documents/${documentId}`, '_blank'),
},
});
}
} catch (error) {
console.error('Failed to save file:', error);
showToast({ type: 'error', message: 'Failed to save file' });
}
},
onPrint: (file: File, newTab?: boolean) => {
const printUrl = URL.createObjectURL(file);
if (newTab) {
window.open(printUrl, '_blank');
} else {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = printUrl;
document.body.appendChild(iframe);
iframe.onload = () => {
iframe.contentWindow?.print();
setTimeout(() => {
document.body.removeChild(iframe);
URL.revokeObjectURL(printUrl);
}, 1000);
};
}
},
onSnapshotArea: async (blob: Blob) => {
try {
// Upload snapshot to server
const formData = new FormData();
formData.append('snapshot', blob, `snapshot-${Date.now()}.png`);
const response = await fetch('/api/snapshots/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
const { url } = await response.json();
showToast({
type: 'success',
message: 'Snapshot saved',
action: {
label: 'View',
onClick: () => window.open(url, '_blank'),
},
});
}
} catch (error) {
console.error('Failed to save snapshot:', error);
showToast({ type: 'error', message: 'Failed to save snapshot' });
}
},
});
Track user interactions and usage patterns:
import { PdfEditor, IConfigChangeData } from '@avanquest/pdf-web-viewer';
const editor = await PdfEditor({
license: 'YOUR_LICENSE_KEY',
container: document.getElementById('pdf-container'),
onLoadingStateChange: (isLoading: boolean) => {
if (isLoading) {
analytics.track('pdf_loading_started');
} else {
analytics.track('pdf_loading_completed');
}
},
onConfigChange: (config: IConfigChangeData) => {
analytics.track('pdf_config_changed', {
hasTheme: !!config.themeConfig,
hasLanguage: !!config.languageConfig,
hasBranding: !!config.brandingConfig,
timestamp: Date.now(),
});
},
onOpenFile: async () => {
analytics.track('pdf_open_initiated');
try {
const file = await showFilePicker();
if (file) {
analytics.track('pdf_file_selected', {
fileName: file.name,
fileSize: file.size,
fileType: file.type,
});
await editor.ui.pdfWebService.openDocument(file);
}
} catch (error) {
analytics.track('pdf_open_failed', { error: error.message });
}
},
onExportFile: (file: File) => {
analytics.track('pdf_downloaded', {
fileName: file.name,
fileSize: file.size,
timestamp: Date.now(),
});
// Proceed with download
const url = URL.createObjectURL(file);
const a = document.createElement('a');
a.href = url;
a.download = file.name;
a.click();
URL.revokeObjectURL(url);
},
onPrint: (file: File, newTab?: boolean) => {
analytics.track('pdf_printed', {
fileName: file.name,
fileSize: file.size,
printMode: newTab ? 'new-tab' : 'direct',
timestamp: Date.now(),
});
// Proceed with print
const url = URL.createObjectURL(file);
if (newTab) {
window.open(url, '_blank');
} else {
// ... iframe print logic
}
},
onSnapshotArea: async (blob: Blob) => {
analytics.track('pdf_snapshot_captured', {
blobSize: blob.size,
blobType: blob.type,
timestamp: Date.now(),
});
// Handle snapshot
try {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `snapshot-${Date.now()}.png`;
a.click();
URL.revokeObjectURL(url);
} catch (error) {
analytics.track('pdf_snapshot_failed', { error: error.message });
}
},
onError: (error: Error) => {
analytics.track('pdf_error_occurred', {
errorMessage: error.message,
errorName: error.name,
errorStack: error.stack,
timestamp: Date.now(),
});
// Show error to user
console.error('PDF Error:', error);
},
});