CAD Title Block Data Extraction Showcase Demo Code Sample

Requirements
View Demo

Extracts key-value pairs from CAD drawings in PDF format with well-structured title blocks. Outputs the data as JSON and adds visual annotations to the document to illustrate the relationships.

This demo allows you to:

  • Upload a PDF file
  • Extract key-value pairs from title blocks and export the data as JSON
  • Automatically generate visual annotations to highlight key-value relationships
  • Download the annotated document

Implementation steps
To add CAD Title Block Data Extraction capability with WebViewer:

Step 1: Choose your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, Claude Sonnet 4, October 22, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// CAD Title Block Data Extraction Demo
8//
9// This code demonstrates how to extract key-value data pairs from CAD drawings with well formed title blocks
10//
11// **Important**
12// 1. You must get a license key from Apryse for the server to run.
13// A trial key can be obtained from:
14// https://docs.apryse.com/core/guides/get-started/trial-key
15//
16// 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.
17
18function initializeWebViewer() {
19
20 // This code initializes the WebViewer with the basic settings
21 WebViewer({
22 path: '/lib',
23 licenseKey: 'YOUR_LICENSE_KEY',
24 }, document.getElementById('viewer')).then((instance) => {
25 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
26 const cloudyTools = [
27 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
28 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
29 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
30 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
31 ];
32 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
33 instance.UI.disableTools(cloudyTools);
34 // Set default toolbar group to Annotate
35 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
36 // Set default tool on mobile devices to Pan.
37 if (UIElements.isMobileDevice()) {
38 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
39 }
40
41 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
42 if (searchParams.has('file')) {
43 searchParams.delete('file');
44 history.replaceState(null, '', '?' + searchParams.toString());
45 }
46 });
47
48 instance.Core.annotationManager.enableAnnotationNumbering();
49 instance.UI.NotesPanel.enableAttachmentPreview();
50 // Add the demo-specific functionality
51 customizeUI(instance).then(() => {
52 // Create UI controls after demo is initialized
53 UIElements.createUIControls(instance);
54 });
55 });
56}
57
58const searchParams = new URLSearchParams(window.location.search);
59const history = window.history || window.parent.history || window.top.history;
60
61// Starting page for extraction
62let startPage = 1;
63
64// Global variable to hold result data
65window.resultData = null;
66
67const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/cad_floor_plan.pdf';
68
69const customizeUI = async (instance) => {
70 // Customize the UI for the title-block-data-extraction demo
71 instance.UI.setToolbarGroup('toolbarGroup-View');
72 instance.UI.disableElements(['thumbnailControl']);
73
74 // Reset variables when new document is loaded
75 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
76 window.resultData = null;
77 startPage = 1;
78
79 // Reset the JSON display area and Color Legend
80 UIElements.resetUI(instance);
81 });
82
83 // Load the default CAD file for demonstration
84 if (defaultDoc) {
85 await loadCadDocument(instance, defaultDoc);
86 }
87};
88
89// Load a CAD document, converting to PDF if necessary
90const loadCadDocument = async (instance, cadUrl) => {
91 // Get the file name and extension
92 const cadFilename = cadUrl.split('/').pop();
93 const extension = cadFilename.split('.').pop().toLowerCase();
94
95 console.log(`Preparing to load document: ${cadFilename}`);
96 console.log(`File extension detected: ${extension}`);
97
98 // If the file is a CAD format, convert it to PDF first
99 if (cadUrl && extension) {
100 if (['dwg', 'dxf', 'dgn', 'rvt'].includes(extension)) {
101 console.log(`Loading CAD file: ${cadFilename}`);
102 const response = await fetch(cadUrl);
103 if (!response.ok) {
104 throw new Error(`Failed to fetch CAD: ${response.status}`);
105 }
106 const cadBuffer = await response.arrayBuffer();
107 const pdfBuffer = await convertCadtoPdf(cadBuffer, cadFilename);
108 instance.UI.loadDocument(pdfBuffer, {
109 extension: 'pdf',
110 });
111 } else {
112 console.log(`Loading document: ${cadFilename}`);
113 instance.UI.loadDocument(cadUrl);
114 }
115 }
116};
117
118// Function to convert CAD ArrayBuffer to PDF ArrayBuffer via server
119const convertCadtoPdf = async (cadBuffer, cadFilename) => {
120 // Send the CAD to the server to be converted to PDF
121 console.log('Sending CAD to server for conversion...');
122 const cadBlob = new Blob([cadBuffer]);
123 const formData = new FormData();
124 formData.append('cadfile', cadBlob, cadFilename);
125
126 const postResponse = await fetch('http://localhost:5050/server/handler.js', {
127 method: 'POST',
128 body: formData,
129 });
130
131 if (postResponse.status !== 200) {
132 throw new Error(`Server error during CAD upload: ${postResponse.status}`);
133 }
134 const buffer = await postResponse.arrayBuffer();
135 return buffer;
136};
137window.convertCadtoPdf = convertCadtoPdf; // Make convertCadtoPdf globally available so that the UIElements module can access it
138
139// Function to extract key-value pairs from title block via server
140const extractKeyValuePairs = async (instance) => {
141 const doc = instance.Core.documentViewer.getDocument();
142 if (doc) {
143 const pdfBuffer = await doc.getFileData({ flags: instance.Core.SaveOptions.LINEARIZED });
144 console.log('Sending PDF to server for key-value extraction...');
145 const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
146 const formData = new FormData();
147 formData.append('pdffile', pdfBlob, 'viewerDocument.pdf');
148
149 // Send the PDF to the server to extract key-value pairs
150 const postResponse = await fetch('http://localhost:5050/server/handler.js/extract-key-value-pairs', {
151 method: 'POST',
152 body: formData,
153 });
154
155 if (postResponse.status !== 200) {
156 throw new Error(`Server error during PDF upload: ${postResponse.status}`);
157 }
158
159 // Retrieve and parse the JSON response
160 const jsonResponse = await postResponse.json();
161 const docStructureData = JSON.parse(jsonResponse);
162 resultData = JSON.stringify(docStructureData, null, 2);
163
164 // Draw annotations on the document based on extracted data
165 drawAnnotations(docStructureData, instance);
166
167 return;
168 }
169}
170window.extractKeyValuePairs = extractKeyValuePairs; // Make extractKeyValuePairs globally available so that the UIElements module can access it
171
172// Function to draw annotations on the document based on extracted key-value data
173const drawAnnotations = (docStructureData, instance) => {
174 const { annotationManager, Annotations } = instance.Core;
175
176 // Retrieve the first page's data
177 const page = docStructureData.pages[startPage - 1];
178 const pageNumber = page?.properties?.pageNumber;
179 console.log(`Processing Page ${pageNumber} for annotations...`);
180 for (const kv of page.keyValueElements ?? []) {
181 const valueRect = kv?.rect;
182 const keyRect = kv?.key?.rect;
183 const hasValueWords = (kv?.words?.length ?? 0) > 0;
184
185 // Only draw if value has words
186 if (!hasValueWords) continue;
187
188 // value: blue
189 const valueAnnot = new Annotations.RectangleAnnotation({
190 PageNumber: pageNumber,
191 X: valueRect[0],
192 Y: valueRect[1],
193 Width: valueRect[2] - valueRect[0],
194 Height: valueRect[3] - valueRect[1],
195 StrokeColor: new Annotations.Color(0, 0, 255),
196 StrokeThickness: 1,
197 });
198 annotationManager.addAnnotation(valueAnnot);
199 annotationManager.redrawAnnotation(valueAnnot);
200
201 // key: red
202 const keyAnnot = new Annotations.RectangleAnnotation({
203 PageNumber: pageNumber,
204 X: keyRect[0],
205 Y: keyRect[1],
206 Width: keyRect[2] - keyRect[0],
207 Height: keyRect[3] - keyRect[1],
208 StrokeColor: new Annotations.Color(255, 0, 0),
209 StrokeThickness: 1,
210 });
211 annotationManager.addAnnotation(keyAnnot);
212 annotationManager.redrawAnnotation(keyAnnot);
213
214 // Green connector
215 const line = new Annotations.LineAnnotation();
216 line.pageNumber = pageNumber;
217 line.StrokeColor = new Annotations.Color(0, 255, 0);
218 line.StrokeThickness = 1;
219 line.Start = topLeftPoint(valueRect, instance);
220 line.End = topLeftPoint(keyRect, instance);
221 annotationManager.addAnnotation(line);
222 annotationManager.redrawAnnotation(line);
223 }
224};
225
226// Helper function to get top-left point of a rectangle
227const topLeftPoint = ([x1, y1, x2, y2], instance) => {
228 return new instance.Core.Math.Point(Math.min(x1, x2), Math.min(y1, y2));
229};
230
231// Cleanup function for when the demo is closed or page is unloaded
232const cleanup = (instance) => {
233 if (typeof instance !== 'undefined' && instance.UI) {
234 if (instance.Core.documentViewer.getDocument()) {
235 // Insert any other cleanup code here
236 }
237 console.log('Cleaning up title-block-data-extraction demo');
238 }
239};
240
241// Register cleanup for page unload
242window.addEventListener('beforeunload', () => cleanup(instance));
243window.addEventListener('unload', () => cleanup(instance));
244
245// Helper function to load the ui-elements.js script
246function loadUIElementsScript() {
247 return new Promise((resolve, reject) => {
248 if (window.UIElements) {
249 console.log('UIElements already loaded');
250 resolve();
251 return;
252 }
253 const script = document.createElement('script');
254 script.src = '/showcase-demos/title-block-data-extraction/client/ui-elements.js';
255 script.onload = function () {
256 console.log('✅ UIElements script loaded successfully');
257 resolve();
258 };
259 script.onerror = function () {
260 console.error('Failed to load UIElements script');
261 reject(new Error('Failed to load ui-elements.js'));
262 };
263 document.head.appendChild(script);
264 });
265}
266
267// Load UIElements script first, then initialize WebViewer
268loadUIElementsScript().then(() => {
269 initializeWebViewer();
270}).catch((error) => {
271 console.error('Failed to load UIElements:', error);
272});
273

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales