Easily calculate area dimensions, measure between lines or trace perimeters in engineering drawings in browser or app.
This demo allows you to:
Implementation steps
To add measurement tools capability to WebViewer:
Step 1: Choose your preferred web stack
Step 2: Download any required modules listed in the Demo Dependencies section below
Step 3: Add the ES6 JavaScript sample code provided in this guide
Demo Dependencies
This sample uses the following:
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 DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales