Measurement Tools Showcase Demo Code Sample

Requirements
View Demo

Easily calculate area dimensions, measure between lines or trace perimeters in engineering drawings in browser or app.

This demo allows you to:

  • Load PDFs containing engineering drawings
  • Add measurement tools and measure objects directly within the document
  • Download the updated PDF with your measurements included

Implementation steps

To add measurement tools capability to WebViewer:
Step 1: Get started with WebViewer in your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

1
2// ES6 Compliant Syntax
3// GitHub Copilot - GPT-4 Model - August 17, 2025
4// File: index.js
5
6import WebViewer from '@pdftron/webviewer';
7
8const DEFAULT_TOOL = 'AnnotationCreateDistanceMeasurement';
9
10const MEASUREMENT_TOOLS = [
11 'AnnotationCreateDistanceMeasurement',
12 'AnnotationCreatePerimeterMeasurement',
13 'AnnotationCreateAreaMeasurement',
14 'AnnotationCreateRectangularAreaMeasurement',
15 'AnnotationCreateEllipseMeasurement',
16 'AnnotationCreateCountMeasurement',
17 'AnnotationCreateArcMeasurement',
18];
19
20// IMPORTANT:
21// The order in this BUTTONS_TEXT array should be similar to the MEASUREMENT_TOOLS array to ensure
22// that the buttons correspond to the correct measurement tools when they're created later.
23const BUTTONS_TEXT = [
24 'Distance',
25 'Perimeter',
26 'Area',
27 'Rectangular Area',
28 'Ellipse Area',
29 'Count',
30 'Arc',
31];
32
33const HIDDEN_TOOLBARS = [
34 'toolbarGroup-Shapes',
35 'toolbarGroup-Edit',
36 'toolbarGroup-Insert',
37 'toolbarGroup-Forms',
38 'toolbarGroup-View',
39 'toolbarGroup-Annotate',
40 'toolbarGroup-FillAndSign',
41 'toolbarGroup-Redact',
42];
43
44const MEASUREMENT_TOOLS_DEFAULT_COLORS = [
45 [225, 0, 0],
46 [0, 255, 0],
47 [0, 0, 255],
48 [0, 255, 225],
49 [255, 0, 255],
50 [255, 255, 0],
51 [255, 165, 0],
52];
53
54const DEFAULT_FONT_SIZE = 16;
55const DEFAULT_STROKE_THICKNESS = 2;
56
57// Function to set WebViewer 'loading' state
58function setLoading(isLoading) {
59 if (isLoading) {
60 theInstance.UI.openElements(['loadingModal']);
61 } else {
62 theInstance.UI.closeElements(['loadingModal']);
63 }
64}
65
66let initializing = true;
67let snapState = false;
68
69function onDocumentLoaded(){
70 setLoading(true);
71 // initialization logic executed when the very first document is loaded
72 if(initializing) {
73 initializing = false;
74 snapState = true; // default snap state
75 setSnapMode({ snap: true, toolName: DEFAULT_TOOL });
76 // set snap color and size
77 theInstance.Core.annotationManager.setSnapDefaultOptions({
78 indicatorColor: '#00a5e4',
79 indicatorSize: 18,
80 radiusThreshold: 20,
81 });
82
83 theInstance.UI.disableElements(HIDDEN_TOOLBARS);
84 theInstance.UI.disableTools();
85 theInstance.UI.enableFeatures([theInstance.UI.Feature.Measurement]);
86
87 const { documentViewer, annotationManager } = theInstance.Core;
88
89 annotationManager.addEventListener('annotationChanged', annotationChanged);
90
91 // update default tool styles
92 const Annotations = theInstance.Core.Annotations;
93 MEASUREMENT_TOOLS.forEach((tool, index) => {
94 const currentTool = documentViewer.getTool(tool);
95 currentTool.setStyles({
96 StrokeThickness: DEFAULT_STROKE_THICKNESS / documentViewer.getZoomLevel(),
97 StrokeColor: new Annotations.Color(...MEASUREMENT_TOOLS_DEFAULT_COLORS[index]),
98 });
99
100 if (currentTool.setDrawMode) {
101 currentTool.setDrawMode(theInstance.Core.Tools.LineCreateTool.DrawModes.TWO_CLICKS);
102 }
103 });
104 // update font size to be larger
105 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
106 DEFAULT_FONT_SIZE / documentViewer.getZoomLevel() + 'px';
107 Annotations.LineAnnotation.prototype['constant']['TEXT_COLOR'] = '#FF0000';
108
109 documentViewer.addEventListener('zoomUpdated', zoomUpdated);
110 }
111 // Wait a couple of seconds to let snapping points completely load
112 setTimeout(() => {
113 setLoading(false);
114 theInstance.UI.setToolbarGroup('toolbarGroup-Measure');
115 theInstance.UI.enableTools(MEASUREMENT_TOOLS);
116 theInstance.UI.setToolMode(DEFAULT_TOOL);
117 }, 2500);
118}
119
120const element = document.getElementById('viewer');
121let theInstance = null;
122const onLoad = async (instance) => {
123 theInstance = instance;
124 initializing = true;
125 theInstance.Core.documentViewer.addEventListener('documentLoaded', () => {
126 onDocumentLoaded();
127 });
128};
129
130function zoomUpdated(zoom) {
131 if (!theInstance) return;
132 const { Annotations, documentViewer } = theInstance.Core;
133 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
134 DEFAULT_FONT_SIZE / zoom + 'px';
135
136 MEASUREMENT_TOOLS.forEach((tool) => {
137 documentViewer.getTool(tool).setStyles({
138 StrokeThickness: DEFAULT_STROKE_THICKNESS / zoom,
139 });
140 });
141}
142
143function annotationChanged(ann, action, { imported }) {
144 console.log('annotationChanged', ann, action, imported);
145 if (action === 'add' && !imported && ann.length === 1 && ann[0].Measure) {
146 theInstance.UI.openElements(['notesPanel']);
147 }
148}
149
150// Initialize WebViewer and load default document
151WebViewer(
152 {
153 path: '/lib',
154 licenseKey: 'YOUR_LICENSE_KEY',
155 initialDoc: 'https://apryse.s3.amazonaws.com/public/files/samples/floorplan.pdf',
156 fullAPI: true, // Enable full API for measurement tools
157 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
158 },
159 element
160).then((instance) => {
161 onLoad(instance);
162});
163console.log('before activeTool');
164let activeTool = DEFAULT_TOOL;
165
166function setActiveHelper(active) {
167 activeTool = active;
168 theInstance.UI.setToolMode(active);
169 setSnapMode({ snap: snapState, toolName: active });
170 buttonAddScale.disabled = (activeTool === 'AnnotationCreateCountMeasurement');
171}
172
173function setSnapMode({ snap, toolName }) {
174 if (!theInstance) return;
175 const defaultMode = theInstance.Core.Tools.SnapModes.DEFAULT;
176 const snapMode = snap ? defaultMode : null;
177 const tool = theInstance.Core.documentViewer.getTool(toolName);
178 if (tool.setSnapMode) {
179 tool.setSnapMode(snapMode);
180 }
181}
182
183// UI section
184//
185// Helper code to add controls to the viewer holding the buttons and dropdown
186
187// Create a container for all controls (label, checkbox and buttons)
188const controlsContainer = document.createElement('div');
189
190// Create a button to add new scale
191const buttonAddScale = document.createElement('button');
192buttonAddScale.textContent = 'Add New Scale';
193buttonAddScale.className = 'btn-style';
194buttonAddScale.onclick = async () => {
195 theInstance.UI.openElements(['scaleModal']);
196};
197
198controlsContainer.appendChild(buttonAddScale);
199
200// Create a checkbox to toggle snapping
201const snapCheckbox = document.createElement('input');
202snapCheckbox.type = 'checkbox';
203snapCheckbox.id = 'snapCheckbox';
204snapCheckbox.checked = true;
205snapCheckbox.onchange = (e) => {
206 snapState = e.target.checked;
207 setSnapMode({ snap: snapState, toolName: activeTool });
208};
209controlsContainer.appendChild(snapCheckbox);
210
211const snapLabel = document.createElement('label');
212snapLabel.textContent = 'Enable Snapping';
213snapLabel.htmlFor = 'snapCheckbox';
214controlsContainer.appendChild(snapLabel);
215
216// Create buttons for each measurement tool
217MEASUREMENT_TOOLS.forEach((tool, index) => {
218 const button = document.createElement('button');
219 // IMPORTANT: The order in BUTTONS_TEXT array is the same as in MEASUREMENT_TOOLS array
220 button.textContent = BUTTONS_TEXT[index];
221 button.className = 'btn-style';
222 button.onclick = async () => {
223 console.log('Button clicked for tool:', tool);
224 setActiveHelper(tool);
225 };
226 controlsContainer.appendChild(button);
227});
228
229const uploadLabel = document.createElement('label');
230uploadLabel.textContent = 'Use the Open File command in the WebViewer UI menu to upload a document';
231
232// Apply classes for styling using CSS
233uploadLabel.className = 'label-class';
234controlsContainer.className = 'control-container';
235
236// Append elements to the controls container
237controlsContainer.appendChild(uploadLabel);
238element.insertBefore(controlsContainer, element.firstChild);
239

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales