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.

If starting with a CAD file, we can also help you convert that to PDF. Please check out our CAD File Conversion Sample Code for more.

Implementation steps

To add CAD Title Block Data Extraction capability with WebViewer:
Step 1: Get started with WebViewer in your preferred web stack.
Step 2: Add the ES6 JavaScript sample code provided in this guide.

Once you generate your license key, it will automatically be included in your sample code below.

License Key

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
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9// CAD Title Block Data Extraction Demo
10//
11// This code demonstrates how to extract key-value data pairs from CAD drawings with well formed title blocks
12//
13// **Important**
14// 1. You must get a license key from Apryse for the server to run.
15// A trial key can be obtained from:
16// https://docs.apryse.com/core/guides/get-started/trial-key
17//
18// 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.
19
20function initializeWebViewer() {
21
22 // This code initializes the WebViewer with the basic settings
23 WebViewer({
24 path: '/lib',
25 licenseKey: licenseKey,
26 }, document.getElementById('viewer')).then((instance) => {
27 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
28 const cloudyTools = [
29 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
30 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
31 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
32 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
33 ];
34 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
35 instance.UI.disableTools(cloudyTools);
36 // Set default toolbar group to Annotate
37 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
38 // Set default tool on mobile devices to Pan.
39 if (UIElements.isMobileDevice()) {
40 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
41 }
42
43 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
44 if (searchParams.has('file')) {
45 searchParams.delete('file');
46 history.replaceState(null, '', '?' + searchParams.toString());
47 }
48 });
49
50 instance.Core.annotationManager.enableAnnotationNumbering();
51 instance.UI.NotesPanel.enableAttachmentPreview();
52 // Add the demo-specific functionality
53 customizeUI(instance).then(() => {
54 // Create UI controls after demo is initialized
55 UIElements.createUIControls(instance);
56 });
57 });
58}
59
60const searchParams = new URLSearchParams(globalThis.location.search);
61const history = globalThis.history || globalThis.parent?.history || globalThis.top?.history;
62
63// Starting page for extraction
64let startPage = 1;
65
66// Global variable to hold result data
67globalThis.resultData = null;
68
69const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/cad_floor_plan.pdf';
70
71const customizeUI = async (instance) => {
72 // Customize the UI for the title-block-data-extraction demo
73 instance.UI.setToolbarGroup('toolbarGroup-View');
74 instance.UI.disableElements(['thumbnailControl']);
75
76 // Reset variables when new document is loaded
77 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
78 globalThis.resultData = null;
79 startPage = 1;
80
81 // Reset the JSON display area and Color Legend
82 UIElements.resetUI(instance);
83 });
84
85 // Load the default CAD file for demonstration
86 if (defaultDoc) {
87 await loadCadDocument(instance, defaultDoc);
88 }
89};
90
91// Load a CAD document, converting to PDF if necessary
92const loadCadDocument = async (instance, cadUrl) => {
93 // Get the file name and extension
94 const cadFilename = cadUrl.split('/').pop();
95 const extension = cadFilename.split('.').pop().toLowerCase();
96
97 console.log(`Preparing to load document: ${cadFilename}`);
98 console.log(`File extension detected: ${extension}`);
99
100 // If the file is a CAD format, convert it to PDF first
101 if (cadUrl && extension) {
102 if (['dwg', 'dxf', 'dgn', 'rvt'].includes(extension)) {
103 console.log(`Loading CAD file: ${cadFilename}`);
104 const response = await fetch(cadUrl);
105 if (!response.ok) {
106 throw new Error(`Failed to fetch CAD: ${response.status}`);
107 }
108 const cadBuffer = await response.arrayBuffer();
109 const pdfBuffer = await convertCadtoPdf(cadBuffer, cadFilename);
110 instance.UI.loadDocument(pdfBuffer, {
111 extension: 'pdf',
112 });
113 } else {
114 console.log(`Loading document: ${cadFilename}`);
115 instance.UI.loadDocument(cadUrl);
116 }
117 }
118};
119
120// Function to convert CAD ArrayBuffer to PDF ArrayBuffer via server
121const convertCadtoPdf = async (cadBuffer, cadFilename) => {
122 // Send the CAD to the server to be converted to PDF
123 console.log('Sending CAD to server for conversion...');
124 const cadBlob = new Blob([cadBuffer]);
125 const formData = new FormData();
126 formData.append('cadfile', cadBlob, cadFilename);
127
128 const postResponse = await fetch('http://localhost:5050/server/handler.js', {
129 method: 'POST',
130 body: formData,
131 });
132
133 if (postResponse.status !== 200) {
134 throw new Error(`Server error during CAD upload: ${postResponse.status}`);
135 }
136 const buffer = await postResponse.arrayBuffer();
137 return buffer;
138};
139globalThis.convertCadtoPdf = convertCadtoPdf; // Make convertCadtoPdf globally available so that the UIElements module can access it
140
141// Function to extract key-value pairs from title block via server
142const extractKeyValuePairs = async (instance) => {
143 const doc = instance.Core.documentViewer.getDocument();
144 if (doc) {
145 const pdfBuffer = await doc.getFileData({ flags: instance.Core.SaveOptions.LINEARIZED });
146 console.log('Sending PDF to server for key-value extraction...');
147 const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
148 const formData = new FormData();
149 formData.append('pdffile', pdfBlob, 'viewerDocument.pdf');
150
151 // Send the PDF to the server to extract key-value pairs
152 const postResponse = await fetch('http://localhost:5050/server/handler.js/extract-key-value-pairs', {
153 method: 'POST',
154 body: formData,
155 });
156
157 if (postResponse.status !== 200) {
158 throw new Error(`Server error during PDF upload: ${postResponse.status}`);
159 }
160
161 // Retrieve and parse the JSON response
162 const jsonResponse = await postResponse.json();
163 const docStructureData = JSON.parse(jsonResponse);
164 globalThis.resultData = JSON.stringify(docStructureData, null, 2);
165
166 // Draw annotations on the document based on extracted data
167 drawAnnotations(docStructureData, instance);
168 }
169}
170globalThis.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 (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
242globalThis.addEventListener('beforeunload', () => cleanup(instance));
243globalThis.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 (globalThis.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-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