Easily create a visualization tool for AutoCAD's DWG and DXF files. View, edit, and annotate those files as PDF files from the browser.
This demo allows you to:
Don't need the WebViewer UI component or need a different server language? Check out the Server SDK CAD Conversion sample code.
To add CAD Viewer capability in Node.js with Server and WebViewer:
Step 1: Get started with Server SDK in Node.js
Step 2: Download the CAD Module
Step 3: Get started in your preferred web stack for WebViewer
Step 4: 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.
Apryse collects some data regarding your usage of the SDK for product improvement.
The data that Apryse collects include:
For clarity, no other data is collected by the SDK and Apryse has no access to the contents of your documents.
If you wish to continue without data collection, contact us and we will email you a no-tracking trial key for you to get started.
1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, September 29, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// CAD Viewer Demo
8//
9// This code demonstrates how to view CAD files directly in the browser with the Apryse JavaScript document SDK.
10// It shows how to create a display and visualization tool for AutoCAD floor plans and drawings.
11// Allowing users to view, edit, and annotate AutoCAD DWG CAD files from the browser using the WebViewer Server.
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 /cad-viewer/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/cad` packages.
19
20// Initialize WebViewer with the specified settings
21function initializeWebViewer() {
22
23 // This code initializes the WebViewer with the basic settings
24 WebViewer({
25 path: '/lib',
26 licenseKey: 'YOUR_LICENSE_KEY',
27 enableFilePicker: false,
28 }, element).then((instance) => {
29 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
30 const cloudyTools = [
31 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
32 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
33 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
34 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
35 ];
36 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
37 instance.UI.disableTools(cloudyTools);
38
39 // Set default toolbar group to Annotate
40 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
41
42 // Set default tool on mobile devices to Pan.
43 if (UIElements.isMobileDevice()) {
44 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
45 }
46
47 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
48 if (searchParams.has('file')) {
49 searchParams.delete('file');
50 history.replaceState(null, '', '?' + searchParams.toString());
51 }
52 });
53
54 instance.Core.annotationManager.enableAnnotationNumbering();
55 instance.UI.NotesPanel.enableAttachmentPreview();
56
57 // Add the demo-specific functionality
58 customizeUI(instance).then(() => {
59 // Create UI controls after demo is initialized
60 UIElements.createUIControls(instance);
61 });
62 });
63}
64
65// CAD files, Key is the file name, value is the URL
66const CAD_FILES = {
67 'DWG': 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/construction%20drawings%20color.dwg',
68 'DXF': 'PROVIDE_URL_HERE/*.dxf',
69};
70
71const defaultDoc = CAD_FILES['DWG']; // Default CAD file to load
72
73// Default styles for measurement tools
74const DEFAULT_FONT_SIZE = 16;
75window.DEFAULT_FONT_SIZE = DEFAULT_FONT_SIZE;
76const DEFAULT_STROKE_THICKNESS = 2;
77window.DEFAULT_STROKE_THICKNESS = DEFAULT_STROKE_THICKNESS;
78const MEASUREMENT_TOOLS = [
79 'AnnotationCreateDistanceMeasurement',
80 'AnnotationCreatePerimeterMeasurement',
81 'AnnotationCreateAreaMeasurement',
82];
83
84window.MEASUREMENT_TOOLS = MEASUREMENT_TOOLS;
85
86const customizeUI = async (instance) => {
87 const { Feature } = instance.UI;
88 const { Annotations, documentViewer } = instance.Core;
89
90 instance.UI.enableFeatures([Feature.Measurement]);
91 instance.UI.enableTools(MEASUREMENT_TOOLS);
92 instance.UI.openElements(['tabPanel']);
93 instance.UI.setActiveLeftPanel('layersPanel');
94
95 // Update default tool styles
96 MEASUREMENT_TOOLS.forEach((tool, index) => {
97 documentViewer.getTool(tool).setStyles({
98 StrokeThickness: DEFAULT_STROKE_THICKNESS / documentViewer.getZoomLevel(),
99 StrokeColor: new Annotations.Color(
100 255 * Number(index === 0),
101 255 * Number(index === 1),
102 255 * Number(index === 2),
103 ),
104 });
105 });
106
107 // Update font size to be larger
108 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] = DEFAULT_FONT_SIZE / documentViewer.getZoomLevel() + 'px';
109 Annotations.LineAnnotation.prototype['constant']['TEXT_COLOR'] = '#FF0000';
110 documentViewer.addEventListener('zoomUpdated', (zoom) => UIElements.zoomUpdated(instance, zoom));
111
112 // Using the Apryse CAD Module, Convert and Load default document
113 const cadUrl = defaultDoc;
114 const cadFilename = cadUrl.split('/').pop();
115 const response = await fetch(defaultDoc);
116 if (!response.ok) {
117 throw new Error(`Failed to fetch CAD: ${response.status}`);
118 }
119 const cadBuffer = await response.arrayBuffer();
120 const pdfBuffer = await convertCadtoPdf(cadBuffer, cadFilename);
121 instance.UI.loadDocument(pdfBuffer, {
122 extension: 'pdf',
123 });
124};
125
126const convertCadtoPdf = async (cadBuffer, cadFilename) => {
127 // Send the CAD to the server to be converted to PDF
128 console.log('Sending CAD to server for conversion...');
129 const cadBlob = new Blob([cadBuffer]);
130 const formData = new FormData();
131 formData.append('cadfile', cadBlob, cadFilename);
132
133 const postResponse = await fetch('http://localhost:5050/server/handler.js', {
134 method: 'POST',
135 body: formData,
136 });
137
138 if (postResponse.status !== 200) {
139 throw new Error(`Server error during CAD upload: ${postResponse.status}`);
140 }
141 const buffer = await postResponse.arrayBuffer();
142 return buffer;
143};
144
145// Make convertCadtoPdf globally available
146window.convertCadtoPdf = convertCadtoPdf;
147const searchParams = new URLSearchParams(window.location.search);
148const history = window.history || window.parent.history || window.top.history;
149const element = document.getElementById('viewer');
150
151// Cleanup function for when the demo is closed or page is unloaded
152const cleanup = (instance) => {
153 const { Feature } = instance.UI;
154
155 if (typeof instance !== 'undefined' && instance.UI) {
156 instance.UI.disableTools(MEASUREMENT_TOOLS);
157 instance.UI.disableFeatures([Feature.Measurement]);
158 instance.UI.closeElements(['leftPanel']);
159 console.log('Cleaning up cad-viewer demo');
160 }
161};
162
163// Register cleanup for page unload
164window.addEventListener('beforeunload', () => cleanup(instance));
165window.addEventListener('unload', () => cleanup(instance));
166
167//helper function to load the ui-elements.js script
168function loadUIElementsScript() {
169 return new Promise((resolve, reject) => {
170 if (window.UIElements) {
171 console.log('UIElements already loaded');
172 resolve();
173 return;
174 }
175
176 const script = document.createElement('script');
177 script.src = '/showcase-demos/cad-viewer/client/ui-elements.js';
178 script.onload = function () {
179 console.log('✅ UIElements script loaded successfully');
180 resolve();
181 };
182 script.onerror = function () {
183 console.error('Failed to load UIElements script');
184 reject(new Error('Failed to load ui-elements.js'));
185 };
186 document.head.appendChild(script);
187 });
188}
189
190// Load UIElements script first, then initialize WebViewer
191loadUIElementsScript().then(() => {
192 initializeWebViewer();
193}).catch((error) => {
194 console.error('Failed to load UIElements:', error);
195});
196
1// UI Elements class to create and manage custom UI controls
2//
3// Helper code to add controls to the viewer holding the buttons
4// This code creates a container for the buttons, styles them, and adds them to the viewer
5//
6class UIElements {
7
8 // Function to check if the user is on a mobile device
9 static isMobileDevice = () => {
10 return (
11 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
12 window.navigator.userAgent
13 ) ||
14 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
15 window.navigator.userAgent.substring(0, 4)
16 )
17 );
18 }
19
20 // Choose File button
21 static filePicker = (instance) => {
22 const button = document.createElement('button');
23 button.className = 'btn-filepicker';
24 button.textContent = 'Choose File';
25 button.onclick = () => {
26 const input = document.createElement('input');
27 input.type = 'file';
28 input.accept = '.dwg,.dxf,.dwf,.dgn,.rvt'; // Supported CAD file formats
29 input.onchange = async (event) => {
30 try {
31 const file = event.target.files[0];
32 if (file) {
33 const arrayBuffer = await file.arrayBuffer();
34 const pdfBuffer = await window.convertCadtoPdf(arrayBuffer, file.name);
35 instance.UI.loadDocument(pdfBuffer, {
36 extension: 'pdf',
37 });
38 }
39 } catch (e) {
40 console.error(e);
41 }
42 };
43 input.click();
44 };
45
46 return button;
47 }
48
49 static createUIControls = (instance) => {
50 // Create a container for all controls
51 const controlsContainer = document.createElement('div');
52 controlsContainer.className = 'button-container';
53
54 // Add the file picker and Import/Export buttons to the controls container
55 controlsContainer.appendChild(this.filePicker(instance));
56
57 // Add the controls container to the viewer element
58 const element = document.getElementById('viewer');
59 element.insertBefore(controlsContainer, element.firstChild);
60 };
61
62 static zoomUpdated = (instance, zoom) => {
63 const { Annotations, documentViewer } = instance.Core;
64 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] = window.DEFAULT_FONT_SIZE / zoom + 'px';
65
66 window.MEASUREMENT_TOOLS.forEach((tool) => {
67 documentViewer.getTool(tool).setStyles({
68 StrokeThickness: window.DEFAULT_STROKE_THICKNESS / zoom,
69 });
70 });
71 };
72}
1/* CSS standard Compliant Syntax */
2/* GitHub Copilot v1.0, GPT-4.1, September 29, 2025 */
3/* File: index.css */
4
5.btn-filepicker {
6 background-color: #007bff;
7 margin: 10px;
8 padding: 5px 10px;
9 border: 1px solid #ccc;
10 border-radius: 4px;
11 cursor: pointer;
12 font-size: 14px;
13 font-weight: bold;
14 transition: all 0.2s ease;
15 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
16 color: white;
17}
18
19.btn-filepicker:hover {
20 background-color: #0056b3;
21 transform: translateY(-1px);
22 box-shadow: 0 4px 8px rgba(0,0,0,0.2);
23}
24
25.btn-filepicker:active {
26 transform: translateY(1px);
27 box-shadow: 0 1px 2px rgba(0,0,0,0.2);
28}
29
30/* Button Container */
31.button-container {
32 display: flex;
33 flex-direction: row;
34 align-items: center;
35 gap: 15px;
36 margin: 5px 0;
37 padding: 16px;
38 padding-bottom: 5px;
39 border-bottom: 1px solid #DFE1E6;
40 background-color: rgba(112, 198, 255, 0.2);
41}
1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, September 29, 2025
3// File: handler.js
4// This file will handle CAD file conversion requests.
5
6const fs = require('fs');
7const { PDFNet } = require('@pdftron/pdfnet-node');
8
9// **Important**
10// 1. You must get a license key from Apryse for the server to run.
11// A trial key can be obtained from:
12// https://docs.apryse.com/core/guides/get-started/trial-key
13//
14// 2. You need to also run the `npm install` command at /cad-viewer/server/ location to install the `@pdftron/pdfnet-node` and `@pdftron/cad` packages.
15const licenseKey = 'YOUR_LICENSE_KEY';
16const multer = require('multer');
17const storage = multer.diskStorage({
18 destination: function (req, file, cb) {
19 cb(null, 'sentCads/')
20 },
21 filename: function (req, file, cb) {
22 // Save with original filename and extension
23 cb(null, file.originalname)
24 }
25});
26const upload = multer({ storage: storage });
27const { response } = require('express');
28const serverFolder = 'server';
29const sentCads = 'sentCads';
30const serverHandler = `/${serverFolder}/handler.js`;
31
32module.exports = async (app) => {
33
34 async function initializePDFNet() {
35 // Create folder sentCads that will hold the sent CAD format files, if it doesn't exist
36 if (!fs.existsSync(sentCads))
37 fs.mkdirSync(sentCads);
38
39 // Initialize PDFNet
40 await PDFNet.initialize(licenseKey);
41
42 // Specify the CAD library path
43 await PDFNet.addResourceSearchPath('./node_modules/@pdftron/cad/lib/');
44
45 // Check if the Apryse SDK CAD module is available.
46 if (await PDFNet.CADModule.isModuleAvailable())
47 console.log('Apryse SDK CAD module is available.');
48 else
49 console.log('Unable to run: Apryse SDK CAD module not available.');
50 }
51
52 // Handle POST request sent to '/server/handler.js'
53 // This endpoint receives the CAD file URL to be loaded in the Apryse webviewer, then saves it to the server
54 app.post(serverHandler, upload.single('cadfile'), async (request, response) => {
55 try {
56 const cadFilename = request.file.originalname;
57 const fullFilename = request.file.path;
58
59 // Convert the CAD file to PDF and get the buffer
60 const buffer = await convertCadToPdfBuffer(fullFilename);
61
62 console.log(`Conversion complete, sending PDF back to client...`);
63
64 // Set headers to indicate a PDF file attachment and send the buffer
65 await response.setHeader('Content-Type', 'application/pdf');
66 await response.setHeader('Content-Disposition', `attachment; filename="${cadFilename.replace(/\.[^/.]+$/, ".pdf")}"`);
67 response.status(200).send(buffer);
68 } catch (e) {
69 response.status(500).send(`Error processing CAD file: ${e.message}`);
70 } finally {
71 // Cleanup: remove the sent CAD file
72 const cadPath = request.file.path;
73 fs.unlink(cadPath, (err) => {
74 if (err) {
75 console.error(`Error removing CAD file ${cadPath}: ${err.message}`);
76 }
77 });
78 }
79 });
80
81 const convertCadToPdfBuffer = async (fullFilename) => {
82 try {
83 // Create a new PDF document and convert the CAD file to PDF
84 const doc = await PDFNet.PDFDoc.create();
85 console.log('Converting CAD to PDF. Filename and Extension:', fullFilename);
86
87 const options = new PDFNet.Convert.CADConvertOptions();
88 options.setPageWidth(800);
89 options.setPageHeight(600);
90 options.setRasterDPI(150);
91
92 await PDFNet.Convert.fromCAD(doc, fullFilename, options);
93
94
95 // Initialize security handler and lock the document
96 doc.initSecurityHandler();
97 doc.lock();
98
99 // Save the PDF document to a memory buffer
100 console.log('After Conversion and Stored in PDFDoc Full filename:', doc.fullFilename);
101 const uint8Array = await doc.saveMemoryBuffer(PDFNet.SDFDoc.SaveOptions.e_linearized);
102 const buffer = Buffer.from(uint8Array);
103
104 // Unlock the document
105 doc.unlock();
106
107 // Return the PDF buffer
108 return buffer;
109 }
110 catch (err) {
111 console.log(err);
112 throw new Error(err);
113 }
114 };
115
116 // Initialize PDFNet
117 PDFNet.runWithoutCleanup(initializePDFNet, licenseKey).then(
118 function onFulfilled() {
119 response.status(200);
120 },
121 function onRejected(error) {
122 // log error and close response
123 console.error('Error initializing PDFNet', error);
124 response.status(503).send();
125 }
126 );
127};
128
1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, September 29, 2025
3// File: server.js
4// This file is to run a server in localhost.
5
6const express = require('express');
7const fs = require('fs');
8const bodyParser = require('body-parser');
9const handler = require('./handler.js');
10const port = process.env.PORT || 5050;
11const app = express();
12const sentPdfs = 'sentPdfs';
13
14// CORS middleware to allow cross-origin requests from the playground
15app.use((req, res, next) => {
16 res.header('Access-Control-Allow-Origin', '*');
17 res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
18 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
19
20 // Handle preflight OPTIONS requests
21 if (req.method === 'OPTIONS') {
22 res.sendStatus(200);
23 } else {
24 next();
25 }
26});
27
28app.use(bodyParser.text());
29app.use('/client', express.static('../client')); // For statically serving 'client' folder at '/'
30
31handler(app);
32
33// Run server
34const server = app.listen(port, 'localhost', (err) => {
35 if (err) {
36 console.error(err);
37 } else {
38 console.info(`Server is listening at http://localhost:${port}`);
39
40 }
41});
42
43// Server shutdown and cleanup
44function shutdown() {
45 console.log('Cleanup started...');
46
47 // Example: Close server
48 server.close(() => {
49 console.log('Server closed.');
50
51 // Removes sent PDFs folder
52 if (fs.existsSync(sentPdfs))
53 fs.rmdirSync(sentPdfs, { recursive: true });
54
55 // If no async cleanup, exit directly
56 process.exit(0);
57 });
58}
59
60// Handle shutdown signals
61process.on('SIGINT', shutdown); // Ctrl+C
62process.on('SIGTERM', shutdown); // kill command or Docker stop
63process.on('uncaughtException', (err) => {
64 console.error('Uncaught Exception:', err);
65 shutdown();
66});
1{
2 "name": "cad-viewer-server",
3 "version": "1.0.0",
4 "description": "CAD Viewer Demo Server Component",
5 "main": "server.js",
6 "scripts": {
7 "start": "node server.js",
8 "dev": "node server.js"
9 },
10 "dependencies": {
11 "@pdftron/cad": "^11.7.0",
12 "@pdftron/pdfnet-node": "^11.7.0",
13 "body-parser": "^1.20.2",
14 "express": "^4.18.2",
15 "multer": "^1.4.4",
16 "open": "^9.1.0"
17 },
18 "keywords": [
19 "cad-viewer",
20 "pdf",
21 "server",
22 "pdftron",
23 "webviewer"
24 ],
25 "author": "Apryse",
26 "license": "MIT"
27}
28
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales