// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude Sonnet 4, November 13, 2025
// File: index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
// Key-Value Extraction Demo
// This code demonstrates how to extract key-value pairs from documents using WebViewer
//
// **Important**
// 1. You must get a license key from Apryse for the server to run.
// A trial key can be obtained from:
// https://docs.apryse.com/core/guides/get-started/trial-key
//
// 2. You need to also run the `npm install` command at /key-value-extraction/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/data-extraction` packages.
function initializeWebViewer() {
// This code initializes the WebViewer with the basic settings
WebViewer({
path: '/lib',
licenseKey: licenseKey,
enableFilePicker: true,
}, document.getElementById('viewer')).then((instance) => {
// Add the demo-specific functionality
customizeUI(instance).then(() => {
// Create UI controls after demo is initialized
UIElements.createUIControls(instance);
});
});
}
// Starting page for extraction
let startPage = 1;
// Global variable to hold result data
globalThis.resultData = null;
// Custom File factory function to hold file metadata (not to be confused with browser's File API)
function FileMetadata(options) {
return {
name: options.name,
displayName: options.displayName,
path: options.path,
extension: options.extension,
displayExtension: options.displayExtension,
id: options.id,
};
}
const files = {
DRIVERS_LICENSE: FileMetadata({
name: 'sample-license.pdf',
displayName: 'Driver\'s License',
path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sample-license.pdf',
extension: 'pdf',
id: 100
}),
SALES_INVOICE: FileMetadata({
name: 'sales-invoice.pdf',
displayName: 'Sales Invoice',
path: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sales-invoice.pdf',
extension: 'pdf',
id: 14
})
}
const sampleDocuments = [
files.DRIVERS_LICENSE,
files.SALES_INVOICE
];
globalThis.sampleDocuments = sampleDocuments;
const defaultFile = sampleDocuments[1].path; // SALES_INVOICE
const customizeUI = async (instance) => {
// Customize the UI for the key value extraction demo
instance.UI.setToolbarGroup('toolbarGroup-View');
instance.UI.disableElements(['thumbnailControl']);
// Reset variables when new document is loaded
instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
globalThis.resultData = null;
startPage = 1;
// Reset the JSON display area and Color Legend
UIElements.resetUI(instance);
});
// Load the default file for demonstration
if (defaultFile) {
instance.UI.loadDocument(defaultFile);
}
};
// Function to extract key-value pairs from title block via server
const extractKeyValuePairs = async (instance) => {
const doc = instance.Core.documentViewer.getDocument();
if (doc) {
const pdfBuffer = await doc.getFileData({ flags: instance.Core.SaveOptions.LINEARIZED });
console.log('Sending PDF to server for key-value extraction...');
const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
const formData = new FormData();
formData.append('pdffile', pdfBlob, 'viewerDocument.pdf');
// Send the PDF to the server to extract key-value pairs
const postResponse = await fetch('http://localhost:5050/server/handler.js/extract-key-value-pairs', {
method: 'POST',
body: formData,
});
if (postResponse.status !== 200) {
throw new Error(`Server error during PDF upload: ${postResponse.status}`);
}
// Retrieve and parse the JSON response
const jsonResponse = await postResponse.json();
const docStructureData = JSON.parse(jsonResponse);
globalThis.resultData = JSON.stringify(docStructureData, null, 2);
// Draw annotations on the document based on extracted data
drawAnnotations(docStructureData, instance);
}
}
globalThis.extractKeyValuePairs = extractKeyValuePairs; // Make extractKeyValuePairs globally available so that the UIElements module can access it
// Function to draw annotations on the document based on extracted key-value data
const drawAnnotations = (docStructureData, instance) => {
const { annotationManager, Annotations } = instance.Core;
// Retrieve the first page's data
const page = docStructureData.pages[startPage - 1];
const pageNumber = page?.properties?.pageNumber;
console.log(`Processing Page ${pageNumber} for annotations...`);
for (const kv of page.keyValueElements ?? []) {
const valueRect = kv?.rect;
const keyRect = kv?.key?.rect;
const hasValueWords = (kv?.words?.length ?? 0) > 0;
// Only draw if value has words
if (!hasValueWords) {
console.log('Skipping annotation for key-value pair with no value words.');
continue;
}
// value: blue
const valueAnnot = new Annotations.RectangleAnnotation({
PageNumber: pageNumber,
X: valueRect[0],
Y: valueRect[1],
Width: valueRect[2] - valueRect[0],
Height: valueRect[3] - valueRect[1],
StrokeColor: new Annotations.Color(0, 0, 255),
StrokeThickness: 1,
});
annotationManager.addAnnotation(valueAnnot);
annotationManager.redrawAnnotation(valueAnnot);
// key: red
const keyAnnot = new Annotations.RectangleAnnotation({
PageNumber: pageNumber,
X: keyRect[0],
Y: keyRect[1],
Width: keyRect[2] - keyRect[0],
Height: keyRect[3] - keyRect[1],
StrokeColor: new Annotations.Color(255, 0, 0),
StrokeThickness: 1,
});
annotationManager.addAnnotation(keyAnnot);
annotationManager.redrawAnnotation(keyAnnot);
// Green connector
const line = new Annotations.LineAnnotation();
line.pageNumber = pageNumber;
line.StrokeColor = new Annotations.Color(0, 255, 0);
line.StrokeThickness = 1;
line.Start = topLeftPoint(valueRect, instance);
line.End = topLeftPoint(keyRect, instance);
annotationManager.addAnnotation(line);
annotationManager.redrawAnnotation(line);
}
};
// Helper function to get top-left point of a rectangle
const topLeftPoint = ([x1, y1, x2, y2], instance) => {
return new instance.Core.Math.Point(Math.min(x1, x2), Math.min(y1, y2));
};
// Cleanup function for when the demo is closed or page is unloaded
const cleanup = (instance) => {
if (instance !== undefined && instance.UI) {
if (instance.Core.documentViewer.getDocument()) {
// Insert any other cleanup code here
}
console.log('Cleaning up title-block-data-extraction demo');
}
};
// Register cleanup for page unload
globalThis.addEventListener('beforeunload', () => cleanup(instance));
globalThis.addEventListener('unload', () => cleanup(instance));
// Helper function to load the ui-elements.js script
function loadUIElementsScript() {
return new Promise((resolve, reject) => {
if (globalThis.UIElements) {
console.log('UIElements already loaded');
resolve();
return;
}
const script = document.createElement('script');
script.src = '/showcase-demos/key-value-extraction/client/ui-elements.js';
script.onload = function () {
console.log('✅ UIElements script loaded successfully');
resolve();
};
script.onerror = function () {
console.error('Failed to load UIElements script');
reject(new Error('Failed to load ui-elements.js'));
};
document.head.appendChild(script);
});
}
// Load UIElements script first, then initialize WebViewer
loadUIElementsScript().then(() => {
initializeWebViewer();
}).catch((error) => {
console.error('Failed to load UIElements:', error);
});