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.disableTools();
87 theInstance.UI.enableFeatures([theInstance.UI.Feature.Measurement]);
88
89 const { documentViewer, annotationManager } = theInstance.Core;
90
91 annotationManager.addEventListener('annotationChanged', annotationChanged);
92
93 // update default tool styles
94 const Annotations = theInstance.Core.Annotations;
95 MEASUREMENT_TOOLS.forEach((tool, index) => {
96 const currentTool = documentViewer.getTool(tool);
97 currentTool.setStyles({
98 StrokeThickness: DEFAULT_STROKE_THICKNESS / documentViewer.getZoomLevel(),
99 StrokeColor: new Annotations.Color(...MEASUREMENT_TOOLS_DEFAULT_COLORS[index]),
100 });
101
102 if (currentTool.setDrawMode) {
103 currentTool.setDrawMode(theInstance.Core.Tools.LineCreateTool.DrawModes.TWO_CLICKS);
104 }
105 });
106 // update font size to be larger
107 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
108 DEFAULT_FONT_SIZE / documentViewer.getZoomLevel() + 'px';
109 Annotations.LineAnnotation.prototype['constant']['TEXT_COLOR'] = '#FF0000';
110
111 documentViewer.addEventListener('zoomUpdated', zoomUpdated);
112 }
113 // Wait a couple of seconds to let snapping points completely load
114 setTimeout(() => {
115 setLoading(false);
116 theInstance.UI.setToolbarGroup('toolbarGroup-Measure');
117 theInstance.UI.enableTools(MEASUREMENT_TOOLS);
118 theInstance.UI.setToolMode(DEFAULT_TOOL);
119 }, 2500);
120}
121
122const element = document.getElementById('viewer');
123let theInstance = null;
124const onLoad = async (instance) => {
125 theInstance = instance;
126 initializing = true;
127 theInstance.Core.documentViewer.addEventListener('documentLoaded', () => {
128 onDocumentLoaded();
129 });
130};
131
132function zoomUpdated(zoom) {
133 if (!theInstance) return;
134 const { Annotations, documentViewer } = theInstance.Core;
135 Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
136 DEFAULT_FONT_SIZE / zoom + 'px';
137
138 MEASUREMENT_TOOLS.forEach((tool) => {
139 documentViewer.getTool(tool).setStyles({
140 StrokeThickness: DEFAULT_STROKE_THICKNESS / zoom,
141 });
142 });
143}
144
145function annotationChanged(ann, action, { imported }) {
146 console.log('annotationChanged', ann, action, imported);
147 if (action === 'add' && !imported && ann.length === 1 && ann[0].Measure) {
148 theInstance.UI.openElements(['notesPanel']);
149 }
150}
151
152// Initialize WebViewer and load default document
153WebViewer(
154 {
155 path: '/lib',
156 licenseKey: licenseKey,
157 initialDoc: 'https://apryse.s3.amazonaws.com/public/files/samples/floorplan.pdf',
158 fullAPI: true, // Enable full API for measurement tools
159 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
160 },
161 element
162).then((instance) => {
163 onLoad(instance);
164});
165console.log('before activeTool');
166let activeTool = DEFAULT_TOOL;
167
168function setActiveHelper(active) {
169 activeTool = active;
170 theInstance.UI.setToolMode(active);
171 setSnapMode({ snap: snapState, toolName: active });
172 buttonAddScale.disabled = (activeTool === 'AnnotationCreateCountMeasurement');
173}
174
175function setSnapMode({ snap, toolName }) {
176 if (!theInstance) return;
177 const defaultMode = theInstance.Core.Tools.SnapModes.DEFAULT;
178 const snapMode = snap ? defaultMode : null;
179 const tool = theInstance.Core.documentViewer.getTool(toolName);
180 if (tool.setSnapMode) {
181 tool.setSnapMode(snapMode);
182 }
183}
184
185// UI section
186//
187// Helper code to add controls to the viewer holding the buttons and dropdown
188
189// Create a container for all controls (label, checkbox and buttons)
190const controlsContainer = document.createElement('div');
191
192// Create a button to add new scale
193const buttonAddScale = document.createElement('button');
194buttonAddScale.textContent = 'Add New Scale';
195buttonAddScale.className = 'btn-style';
196buttonAddScale.onclick = async () => {
197 theInstance.UI.openElements(['scaleModal']);
198};
199
200controlsContainer.appendChild(buttonAddScale);
201
202// Create a checkbox to toggle snapping
203const snapCheckbox = document.createElement('input');
204snapCheckbox.type = 'checkbox';
205snapCheckbox.id = 'snapCheckbox';
206snapCheckbox.checked = true;
207snapCheckbox.onchange = (e) => {
208 snapState = e.target.checked;
209 setSnapMode({ snap: snapState, toolName: activeTool });
210};
211controlsContainer.appendChild(snapCheckbox);
212
213const snapLabel = document.createElement('label');
214snapLabel.textContent = 'Enable Snapping';
215snapLabel.htmlFor = 'snapCheckbox';
216controlsContainer.appendChild(snapLabel);
217
218// Create buttons for each measurement tool
219MEASUREMENT_TOOLS.forEach((tool, index) => {
220 const button = document.createElement('button');
221 // IMPORTANT: The order in BUTTONS_TEXT array is the same as in MEASUREMENT_TOOLS array
222 button.textContent = BUTTONS_TEXT[index];
223 button.className = 'btn-style';
224 button.onclick = async () => {
225 console.log('Button clicked for tool:', tool);
226 setActiveHelper(tool);
227 };
228 controlsContainer.appendChild(button);
229});
230
231const uploadLabel = document.createElement('label');
232uploadLabel.textContent = 'Use the Open File command in the WebViewer UI menu to upload a document';
233
234// Apply classes for styling using CSS
235uploadLabel.className = 'label-class';
236controlsContainer.className = 'control-container';
237
238// Append elements to the controls container
239controlsContainer.appendChild(uploadLabel);
240element.insertBefore(controlsContainer, element.firstChild);
241
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales