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
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9// CAD Viewer Demo
10//
11// This code demonstrates how to view CAD files directly in the browser with the Apryse JavaScript document SDK.
12// It shows how to create a display and visualization tool for AutoCAD floor plans and drawings.
13// Allowing users to view, edit, and annotate AutoCAD DWG CAD files from the browser using the WebViewer Server.
14//
15// **Important**
16// 1. You must get a license key from Apryse for the server to run.
17// A trial key can be obtained from:
18// https://docs.apryse.com/core/guides/get-started/trial-key
19//
20// 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.
21
22// Initialize WebViewer with the specified settings
23function initializeWebViewer() {
24
25 // This code initializes the WebViewer with the basic settings
26 WebViewer({
27 path: '/lib',
28 licenseKey: licenseKey,
29 enableFilePicker: false,
30 }, element).then((instance) => {
31 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
32 const cloudyTools = [
33 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
34 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
35 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
36 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
37 ];
38 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
39 instance.UI.disableTools(cloudyTools);
40
41 // Set default toolbar group to Annotate
42 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
43
44 // Set default tool on mobile devices to Pan.
45 if (UIElements.isMobileDevice()) {
46 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
47 }
48
49 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
50 if (searchParams.has('file')) {
51 searchParams.delete('file');
52 history.replaceState(null, '', '?' + searchParams.toString());
53 }
54 });
55
56 instance.Core.annotationManager.enableAnnotationNumbering();
57 instance.UI.NotesPanel.enableAttachmentPreview();
58
59 // Add the demo-specific functionality
60 customizeUI(instance).then(() => {
61 // Create UI controls after demo is initialized
62 UIElements.createUIControls(instance);
63 });
64 });
65}
66
67// CAD files, Key is the file name, value is the URL
68const CAD_FILES = {
69 'DWG': 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/construction%20drawings%20color.dwg',
70 'DXF': 'PROVIDE_URL_HERE/*.dxf',
71};
72
73const defaultDoc = CAD_FILES['DWG']; // Default CAD file to load
74
75// Default styles for measurement tools
76const DEFAULT_FONT_SIZE = 16;
77window.DEFAULT_FONT_SIZE = DEFAULT_FONT_SIZE;
78const DEFAULT_STROKE_THICKNESS = 2;
79window.DEFAULT_STROKE_THICKNESS = DEFAULT_STROKE_THICKNESS;
80const MEASUREMENT_TOOLS = [
81 'AnnotationCreateDistanceMeasurement',
82 'AnnotationCreatePerimeterMeasurement',
83 'AnnotationCreateAreaMeasurement',
84];
85
86window.MEASUREMENT_TOOLS = MEASUREMENT_TOOLS;
87
88const customizeUI = async (instance) => {
89 const { Feature } = instance.UI;
90 const { Annotations, documentViewer } = instance.Core;
91
92 instance.UI.enableFeatures([Feature.Measurement]);
93 instance.UI.enableTools(MEASUREMENT_TOOLS);
94 instance.UI.openElements(['tabPanel']);
95 instance.UI.setActiveLeftPanel('layersPanel');
96
97 // Update default tool styles
98 MEASUREMENT_TOOLS.forEach((tool, index) => {
99 documentViewer.getTool(tool).setStyles({
100 StrokeThickness: DEFAULT_STROKE_THICKNESS / documentViewer.getZoomLevel(),
101 StrokeColor: new Annotations.Color(
102 255 * Number(index === 0),
103 255 * Number(index === 1),
104 255 * Number(index === 2),
105 ),
106 });
107 });
108
109 // Update font size to be larger
110 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] = DEFAULT_FONT_SIZE / documentViewer.getZoomLevel() + 'px';
111 Annotations.LineAnnotation.prototype['constant']['TEXT_COLOR'] = '#FF0000';
112 documentViewer.addEventListener('zoomUpdated', (zoom) => UIElements.zoomUpdated(instance, zoom));
113
114 // Using the Apryse CAD Module, Convert and Load default document
115 const cadUrl = defaultDoc;
116 const cadFilename = cadUrl.split('/').pop();
117 const response = await fetch(defaultDoc);
118 if (!response.ok) {
119 throw new Error(`Failed to fetch CAD: ${response.status}`);
120 }
121 const cadBuffer = await response.arrayBuffer();
122 const pdfBuffer = await convertCadtoPdf(cadBuffer, cadFilename);
123 instance.UI.loadDocument(pdfBuffer, {
124 extension: 'pdf',
125 });
126};
127
128const convertCadtoPdf = async (cadBuffer, cadFilename) => {
129 // Send the CAD to the server to be converted to PDF
130 console.log('Sending CAD to server for conversion...');
131 const cadBlob = new Blob([cadBuffer]);
132 const formData = new FormData();
133 formData.append('cadfile', cadBlob, cadFilename);
134
135 const postResponse = await fetch('http://localhost:5050/server/handler.js', {
136 method: 'POST',
137 body: formData,
138 });
139
140 if (postResponse.status !== 200) {
141 throw new Error(`Server error during CAD upload: ${postResponse.status}`);
142 }
143 const buffer = await postResponse.arrayBuffer();
144 return buffer;
145};
146
147// Make convertCadtoPdf globally available
148window.convertCadtoPdf = convertCadtoPdf;
149const searchParams = new URLSearchParams(window.location.search);
150const history = window.history || window.parent.history || window.top.history;
151const element = document.getElementById('viewer');
152
153// Cleanup function for when the demo is closed or page is unloaded
154const cleanup = (instance) => {
155 const { Feature } = instance.UI;
156
157 if (typeof instance !== 'undefined' && instance.UI) {
158 instance.UI.disableTools(MEASUREMENT_TOOLS);
159 instance.UI.disableFeatures([Feature.Measurement]);
160 instance.UI.closeElements(['leftPanel']);
161 console.log('Cleaning up cad-viewer demo');
162 }
163};
164
165// Register cleanup for page unload
166window.addEventListener('beforeunload', () => cleanup(instance));
167window.addEventListener('unload', () => cleanup(instance));
168
169//helper function to load the ui-elements.js script
170function loadUIElementsScript() {
171 return new Promise((resolve, reject) => {
172 if (window.UIElements) {
173 console.log('UIElements already loaded');
174 resolve();
175 return;
176 }
177
178 const script = document.createElement('script');
179 script.src = '/showcase-demos/cad-viewer/client/ui-elements.js';
180 script.onload = function () {
181 console.log('✅ UIElements script loaded successfully');
182 resolve();
183 };
184 script.onerror = function () {
185 console.error('Failed to load UIElements script');
186 reject(new Error('Failed to load ui-elements.js'));
187 };
188 document.head.appendChild(script);
189 });
190}
191
192// Load UIElements script first, then initialize WebViewer
193loadUIElementsScript().then(() => {
194 initializeWebViewer();
195}).catch((error) => {
196 console.error('Failed to load UIElements:', error);
197});
198
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_SERVER_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