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