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
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