// ES6 Compliant Syntax
// GitHub Copilot - GPT-4 Model - August 17, 2025
// File: index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
const DEFAULT_TOOL = 'AnnotationCreateDistanceMeasurement';
const MEASUREMENT_TOOLS = [
'AnnotationCreateDistanceMeasurement',
'AnnotationCreatePerimeterMeasurement',
'AnnotationCreateAreaMeasurement',
'AnnotationCreateRectangularAreaMeasurement',
'AnnotationCreateEllipseMeasurement',
'AnnotationCreateCountMeasurement',
'AnnotationCreateArcMeasurement',
];
// IMPORTANT:
// The order in this BUTTONS_TEXT array should be similar to the MEASUREMENT_TOOLS array to ensure
// that the buttons correspond to the correct measurement tools when they're created later.
const BUTTONS_TEXT = [
'Distance',
'Perimeter',
'Area',
'Rectangular Area',
'Ellipse Area',
'Count',
'Arc',
];
const HIDDEN_TOOLBARS = [
'toolbarGroup-Shapes',
'toolbarGroup-Edit',
'toolbarGroup-Insert',
'toolbarGroup-Forms',
'toolbarGroup-View',
'toolbarGroup-Annotate',
'toolbarGroup-FillAndSign',
'toolbarGroup-Redact',
];
const MEASUREMENT_TOOLS_DEFAULT_COLORS = [
[225, 0, 0],
[0, 255, 0],
[0, 0, 255],
[0, 255, 225],
[255, 0, 255],
[255, 255, 0],
[255, 165, 0],
];
const DEFAULT_FONT_SIZE = 16;
const DEFAULT_STROKE_THICKNESS = 2;
// Function to set WebViewer 'loading' state
function setLoading(isLoading) {
if (isLoading) {
theInstance.UI.openElements(['loadingModal']);
} else {
theInstance.UI.closeElements(['loadingModal']);
}
}
let initializing = true;
let snapState = false;
function onDocumentLoaded(){
setLoading(true);
// initialization logic executed when the very first document is loaded
if(initializing) {
initializing = false;
snapState = true; // default snap state
setSnapMode({ snap: true, toolName: DEFAULT_TOOL });
// set snap color and size
theInstance.Core.annotationManager.setSnapDefaultOptions({
indicatorColor: '#00a5e4',
indicatorSize: 18,
radiusThreshold: 20,
});
theInstance.UI.disableElements(HIDDEN_TOOLBARS);
theInstance.UI.enableFeatures([theInstance.UI.Feature.Measurement]);
const { documentViewer, annotationManager } = theInstance.Core;
annotationManager.addEventListener('annotationChanged', annotationChanged);
// update default tool styles
const Annotations = theInstance.Core.Annotations;
MEASUREMENT_TOOLS.forEach((tool, index) => {
const currentTool = documentViewer.getTool(tool);
currentTool.setStyles({
StrokeThickness: DEFAULT_STROKE_THICKNESS / documentViewer.getZoomLevel(),
StrokeColor: new Annotations.Color(...MEASUREMENT_TOOLS_DEFAULT_COLORS[index]),
});
if (currentTool.setDrawMode) {
currentTool.setDrawMode(theInstance.Core.Tools.LineCreateTool.DrawModes.TWO_CLICKS);
}
});
// update font size to be larger
Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
DEFAULT_FONT_SIZE / documentViewer.getZoomLevel() + 'px';
Annotations.LineAnnotation.prototype['constant']['TEXT_COLOR'] = '#FF0000';
documentViewer.addEventListener('zoomUpdated', zoomUpdated);
}
// Wait a couple of seconds to let snapping points completely load
setTimeout(() => {
setLoading(false);
theInstance.UI.setToolbarGroup('toolbarGroup-Measure');
theInstance.UI.enableTools(MEASUREMENT_TOOLS);
theInstance.UI.setToolMode(DEFAULT_TOOL);
}, 2500);
}
const element = document.getElementById('viewer');
let theInstance = null;
const onLoad = async (instance) => {
theInstance = instance;
initializing = true;
theInstance.Core.documentViewer.addEventListener('documentLoaded', () => {
onDocumentLoaded();
});
};
function zoomUpdated(zoom) {
if (!theInstance) return;
const { Annotations, documentViewer } = theInstance.Core;
Annotations.LineAnnotation.prototype['constant']['FONT_SIZE'] =
DEFAULT_FONT_SIZE / zoom + 'px';
MEASUREMENT_TOOLS.forEach((tool) => {
documentViewer.getTool(tool).setStyles({
StrokeThickness: DEFAULT_STROKE_THICKNESS / zoom,
});
});
}
function annotationChanged(ann, action, { imported }) {
console.log('annotationChanged', ann, action, imported);
if (action === 'add' && !imported && ann.length === 1 && ann[0].Measure) {
theInstance.UI.openElements(['notesPanel']);
}
}
// Initialize WebViewer and load default document
WebViewer(
{
path: '/lib',
licenseKey: licenseKey,
initialDoc: 'https://apryse.s3.amazonaws.com/public/files/samples/floorplan.pdf',
fullAPI: true, // Enable full API for measurement tools
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
},
element
).then((instance) => {
onLoad(instance);
});
console.log('before activeTool');
let activeTool = DEFAULT_TOOL;
function setActiveHelper(active) {
activeTool = active;
theInstance.UI.setToolMode(active);
setSnapMode({ snap: snapState, toolName: active });
buttonAddScale.disabled = (activeTool === 'AnnotationCreateCountMeasurement');
}
function setSnapMode({ snap, toolName }) {
if (!theInstance) return;
const defaultMode = theInstance.Core.Tools.SnapModes.DEFAULT;
const snapMode = snap ? defaultMode : null;
const tool = theInstance.Core.documentViewer.getTool(toolName);
if (tool.setSnapMode) {
tool.setSnapMode(snapMode);
}
}
// UI section
//
// Helper code to add controls to the viewer holding the buttons and dropdown
// Create a container for all controls (label, checkbox and buttons)
const controlsContainer = document.createElement('div');
// Create a button to add new scale
const buttonAddScale = document.createElement('button');
buttonAddScale.textContent = 'Add New Scale';
buttonAddScale.className = 'btn-style';
buttonAddScale.onclick = async () => {
theInstance.UI.openElements(['scaleModal']);
};
controlsContainer.appendChild(buttonAddScale);
// Create a checkbox to toggle snapping
const snapCheckbox = document.createElement('input');
snapCheckbox.type = 'checkbox';
snapCheckbox.id = 'snapCheckbox';
snapCheckbox.checked = true;
snapCheckbox.onchange = (e) => {
snapState = e.target.checked;
setSnapMode({ snap: snapState, toolName: activeTool });
};
controlsContainer.appendChild(snapCheckbox);
const snapLabel = document.createElement('label');
snapLabel.textContent = 'Enable Snapping';
snapLabel.htmlFor = 'snapCheckbox';
controlsContainer.appendChild(snapLabel);
// Create buttons for each measurement tool
MEASUREMENT_TOOLS.forEach((tool, index) => {
const button = document.createElement('button');
// IMPORTANT: The order in BUTTONS_TEXT array is the same as in MEASUREMENT_TOOLS array
button.textContent = BUTTONS_TEXT[index];
button.className = 'btn-style';
button.onclick = async () => {
console.log('Button clicked for tool:', tool);
setActiveHelper(tool);
};
controlsContainer.appendChild(button);
});
const uploadLabel = document.createElement('label');
uploadLabel.textContent = 'Use the Open File command in the WebViewer UI menu to upload a document';
// Apply classes for styling using CSS
uploadLabel.className = 'label-class';
controlsContainer.className = 'control-container';
// Append elements to the controls container
controlsContainer.appendChild(uploadLabel);
element.insertBefore(controlsContainer, element.firstChild);