// ES6 Compliant Syntax
// GitHub Copilot v1.0, Claude Sonnet 4, October 22, 2025
// File: index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
// CAD Title Block Data Extraction Demo
//
// This code demonstrates how to extract key-value data pairs from CAD drawings with well formed title blocks
//
// **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 /title-block-data-extraction/server/ location to install the `@pdftron/pdfnet-node`, `@pdftron/cad`, and `@pdftron/data-extraction` packages.
function initializeWebViewer() {
// This code initializes the WebViewer with the basic settings
WebViewer({
path: '/lib',
licenseKey: licenseKey,
}, document.getElementById('viewer')).then((instance) => {
// Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
const cloudyTools = [
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
];
instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
instance.UI.disableTools(cloudyTools);
// Set default toolbar group to Annotate
instance.UI.setToolbarGroup('toolbarGroup-Annotate');
// Set default tool on mobile devices to Pan.
if (UIElements.isMobileDevice()) {
instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
}
instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
if (searchParams.has('file')) {
searchParams.delete('file');
history.replaceState(null, '', '?' + searchParams.toString());
}
});
instance.Core.annotationManager.enableAnnotationNumbering();
instance.UI.NotesPanel.enableAttachmentPreview();
// Add the demo-specific functionality
customizeUI(instance).then(() => {
// Create UI controls after demo is initialized
UIElements.createUIControls(instance);
});
});
}
const searchParams = new URLSearchParams(globalThis.location.search);
const history = globalThis.history || globalThis.parent?.history || globalThis.top?.history;
// Starting page for extraction
let startPage = 1;
// Global variable to hold result data
globalThis.resultData = null;
const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/cad_floor_plan.pdf';
const customizeUI = async (instance) => {
// Customize the UI for the title-block-data-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 CAD file for demonstration
if (defaultDoc) {
await loadCadDocument(instance, defaultDoc);
}
};
// Load a CAD document, converting to PDF if necessary
const loadCadDocument = async (instance, cadUrl) => {
// Get the file name and extension
const cadFilename = cadUrl.split('/').pop();
const extension = cadFilename.split('.').pop().toLowerCase();
console.log(`Preparing to load document: ${cadFilename}`);
console.log(`File extension detected: ${extension}`);
// If the file is a CAD format, convert it to PDF first
if (cadUrl && extension) {
if (['dwg', 'dxf', 'dgn', 'rvt'].includes(extension)) {
console.log(`Loading CAD file: ${cadFilename}`);
const response = await fetch(cadUrl);
if (!response.ok) {
throw new Error(`Failed to fetch CAD: ${response.status}`);
}
const cadBuffer = await response.arrayBuffer();
const pdfBuffer = await convertCadtoPdf(cadBuffer, cadFilename);
instance.UI.loadDocument(pdfBuffer, {
extension: 'pdf',
});
} else {
console.log(`Loading document: ${cadFilename}`);
instance.UI.loadDocument(cadUrl);
}
}
};
// Function to convert CAD ArrayBuffer to PDF ArrayBuffer via server
const convertCadtoPdf = async (cadBuffer, cadFilename) => {
// Send the CAD to the server to be converted to PDF
console.log('Sending CAD to server for conversion...');
const cadBlob = new Blob([cadBuffer]);
const formData = new FormData();
formData.append('cadfile', cadBlob, cadFilename);
const postResponse = await fetch('http://localhost:5050/server/handler.js', {
method: 'POST',
body: formData,
});
if (postResponse.status !== 200) {
throw new Error(`Server error during CAD upload: ${postResponse.status}`);
}
const buffer = await postResponse.arrayBuffer();
return buffer;
};
globalThis.convertCadtoPdf = convertCadtoPdf; // Make convertCadtoPdf globally available so that the UIElements module can access it
// 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) 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/title-block-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);
});