// ES6 Compliant Syntax
// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-23
// File: showcase-demos/edit-table-of-contents/index.js
import WebViewer from '@pdftron/webviewer';
const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
// State variables for demo
const dropPoint = {};
const customStampDemoFile = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sales-invoice.pdf';
function initializeWebViewer() {
WebViewer(
{
path: '/lib',
initialDoc: customStampDemoFile,
enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
licenseKey: licenseKey, // Replace with your license key
},
document.getElementById('viewer')
).then((instance) => {
createUIElements();
// Set toolbar group immediately after WebViewer loads
instance.UI.setToolbarGroup(['toolbarGroup-Insert']);
// Wait for document to load before setting tool mode (more reliable)
instance.Core.documentViewer.addEventListener('documentLoaded', () => {
// Set the rubber stamp tool mode
instance.UI.setToolMode('AnnotationCreateRubberStamp');
// Add a small delay before opening the panel
setTimeout(() => {
instance.UI.openElements(['rubberStampPanel']);
}, 150);
// Register events after the page is loaded
demoComplete();
registerDragEvents();
});
});
};
const getIframeWindow = () => {
return (
document.getElementById('viewer')?.getElementsByTagName('iframe')?.[0]?.contentWindow || window
);
};
const getIframeContext = () => {
return (
document.getElementById('viewer')?.getElementsByTagName('iframe')?.[0]?.contentWindow
.instance || window.WebViewer.getInstance()
);
};
// Drag and drop handlers
const dragOver = (e) => {
e.preventDefault();
//const imgData = e.dataTransfer.getData('text/plain');
e.target.style.opacity = 1;
e.preventDefault();
};
// Update drop event to adjust dropPoint to fit document coordinates
const drop = (e) => {
console.log('File dropped:', e);
e.preventDefault();
e.stopPropagation();
const documentViewer = getIframeContext().Core.documentViewer;
const scrollElement = documentViewer.getScrollViewElement();
const scrollLeft = scrollElement.scrollLeft || 0;
const scrollTop = scrollElement.scrollTop || 0;
const zoomLevel = documentViewer.getZoomLevel();
dropPoint.current = { x: (e.pageX + scrollLeft), y: (e.pageY + scrollTop) };
const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
const doc = documentViewer.getDocument();
const documentWidth = doc.getPageInfo(1).width;
const documentHeight = doc.getPageInfo(1).height;
dropPoint.current.x = Math.min(Math.max(dropPoint.current.x, 0), documentWidth);
dropPoint.current.y = Math.min(Math.max(dropPoint.current.y, 0), documentHeight);
const page = displayMode.getSelectedPages(dropPoint.current, dropPoint.current);
if (!page.first) {
console.error('128 Invalid page location for point:', dropPoint.current);
// Log dimensions of all pages for debugging
const pageCount = doc.getPageCount();
for (let i = 1; i <= pageCount; i++) {
const pageInfo = doc.getPageInfo(i);
console.log(`Page ${i} bounds:`, pageInfo);
}
return;
}
const pageInfo = doc.getPageInfo(page.first);
console.log('Selected page bounds:', pageInfo);
let pagePoint = displayMode.windowToPage(dropPoint.current, page.first);
console.log('Mapped pagePoint before clamping:', pagePoint);
if (!pagePoint || pagePoint.x < 0 || pagePoint.y < 0 || pagePoint.x > pageInfo.width || pagePoint.y > pageInfo.height) {
console.error('Invalid page coordinates after mapping:', pagePoint);
pagePoint = {
x: Math.min(Math.max(pagePoint.x || 0, 0), pageInfo.width),
y: Math.min(Math.max(pagePoint.y || 0, 0), pageInfo.height),
};
}
const imgData = e.dataTransfer.getData('text/plain');
if (!imgData) {
console.error('No imgData found in dataTransfer');
return;
}
console.log('Dimensions of image dataTransfer:', e.dataTransfer);
let stampSize = { width: 100, height: 25 }; //Default size
let img = new Image();
img.src = imgData; // Set the base64 string as the source
img.onload = () => {
document.body.appendChild(img); // Append the image to the DOM temporarily
const renderedWidth = img.offsetWidth; // Rendered width of the image
const renderedHeight = img.offsetHeight; // Rendered height of the image
document.body.removeChild(img); // Remove the image from the DOM
// You can now use these dimensions to set the stamp size
stampSize.width = renderedWidth;
stampSize.height = renderedHeight;
//find slider and get the value for width of image
const widthSlider = document.getElementById('width-slider');
if (widthSlider) {
const sliderValue = parseFloat(widthSlider.value);
if (!isNaN(sliderValue) && sliderValue > 0) {
stampSize.width = sliderValue;
const aspectRatio = renderedWidth / renderedHeight;
stampSize.height = sliderValue / aspectRatio;
}
}
const viewFactor = 0.66; // Adjust this factor as needed, used to scale the stamp appropriately
let stampRect = { width: stampSize.width * zoomLevel * viewFactor, height: stampSize.height * zoomLevel * viewFactor };
try {
addStamp(imgData, pagePoint, stampRect, page.first);
console.log('Stamp added at point:', pagePoint, 'with rect:', stampRect);
} catch (error) {
console.error('Error while adding stamp:', error);
console.error('Parameters passed to addStamp:', {
imgData,
point: pagePoint,
rect: stampRect,
pageNumber: page.first,
});
}
};
img.onerror = () => {
console.error('Failed to load image from data URL');
};
};
function demoComplete() {
//let defaultDocument = customStampDemoFile;
//console.log('Demo initialization complete');
const documentViewer = getIframeContext().Core.documentViewer;
documentViewer.addEventListener('toolModeUpdated', toolModeUpdated);
const iframeDoc = getIframeWindow().document.body;
iframeDoc.addEventListener('dragover', dragOver);
iframeDoc.addEventListener('drop', drop);
// Allow annotations outside page bounds
getIframeContext().Core.Tools.Tool.ALLOW_ANNOTS_OUTSIDE_PAGE = true;
};
const toolModeUpdated = (type) => {
const { id } = type;
if (id) {
// Check if stamps object exists and has the id
if (window.stamps && window.stamps[id]) {
data = window.stamps[id];
name = id;
} else {
data = null;
}
}
};
const stampPanelWidthOffset = 146;
const addStamp = (
imgData,
point = {},
rect = { height: undefined, width: undefined }
) => {
const { Annotations } = getIframeContext().Core;
const { documentViewer, annotationManager } = getIframeContext().Core;
const doc = documentViewer.getDocument();
const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
console.log('Adding stamp at point:', point, 'with rect:', rect);
//if stamp panel is open add points to x coordinate
if (getIframeContext().UI.isElementOpen('rubberStampPanel')) {
point.x += stampPanelWidthOffset + (rect.width < 50 ? rect.width + 50 : rect.width); // add width of stamp panel + width
console.log('326 Adjusted point for open stamp panel:', point);
}
const page = displayMode.getSelectedPages(point, point);
const page_num = page.first || documentViewer.getCurrentPage();
if (!page_num || typeof page_num !== 'number') {
console.error('Invalid page number:', page_num);
return;
}
const page_info = doc.getPageInfo(page_num);
const page_point = displayMode.windowToPage(point, page_num);
const zoom = documentViewer.getZoomLevel();
const stampAnnot = new Annotations.StampAnnotation();
stampAnnot.PageNumber = page_num;
const rotation = documentViewer.getCompleteRotation(page_num) * 90;
stampAnnot.Rotation = rotation;
if (rotation === 270 || rotation === 90) {
stampAnnot.Width = rect.height / zoom;
stampAnnot.Height = rect.width / zoom;
} else {
stampAnnot.Width = rect.width / zoom;
stampAnnot.Height = rect.height / zoom;
}
stampAnnot.X = (page_point.x || page_info.width / 2) - stampAnnot.Width / 2;
stampAnnot.X += (rect.width < 50 ? rect.width + 50 : rect.width); // Shift right by rect width to account for drag offset, stamp is shifted to the right
stampAnnot.Y = (page_point.y || page_info.height / 2) - stampAnnot.Height / 2;
stampAnnot.setImageData(imgData);
stampAnnot.Author = annotationManager.getCurrentUser();
annotationManager.deselectAllAnnotations();
annotationManager.addAnnotation(stampAnnot, true);
annotationManager.redrawAnnotation(stampAnnot);
annotationManager.selectAnnotation(stampAnnot);
};
// Register drag and drop events
function registerDragEvents() {
// Required:
// target is 'viewer'
const dropTarget = document.getElementById('viewer');
// Just adds the '+' to show while dragging to the drop target
dropTarget.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy'; // with copy, shows '+' symbol during drag
});
dropTarget.addEventListener('drop', (e) => {
const imgData = e.dataTransfer.getData('text/plain'); // Retrieve using custom data type
if (!imgData) {
console.error('No imgData found in dataTransfer');
return;
}
const documentViewer = getIframeContext().Core.documentViewer;
const scrollElement = documentViewer.getScrollViewElement();
const scrollLeft = scrollElement.scrollLeft || 0;
const scrollTop = scrollElement.scrollTop || 0;
dropPoint.current = { x: e.pageX + scrollLeft, y: e.pageY + scrollTop };
const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
const page = displayMode.getSelectedPages(dropPoint.current, dropPoint.current);
if (!page.first) {
return;
}
});
// Custom annotation behavior checkbox handler
const setCustomAnnotationBehavior = () => {
const isChecked = document.getElementById('custom-annotation-behavior').checked;
getIframeContext().Core.Tools.Tool.ALLOW_ANNOTS_OUTSIDE_PAGE = isChecked;
};
// Register event listener for custom annotation behavior checkbox
const customAnnotationCheckbox = document.getElementById('custom-annotation-behavior');
if (customAnnotationCheckbox) {
customAnnotationCheckbox.addEventListener('click', setCustomAnnotationBehavior);
}
// Open custom stamp modal on button click
const setTextStamp = () => {
getIframeContext().UI.openElements(['customStampModal']);
};
// Register event listener for custom annotation behavior checkbox
const onClickCreateTextStamp = document.getElementById('choose-file-button-text');
if (onClickCreateTextStamp) {
onClickCreateTextStamp.addEventListener('click', setTextStamp);
}
}
function createUIElements() {
// Create a container for all controls (label, dropdown, and buttons)
// Dynamically load ui-elements.js if not already loaded
if (!window.SidePanel) {
const script = document.createElement('script');
script.src = '/showcase-demos/pdf-stamps/ui-elements.js';
script.onload = () => {
UIElements.init('viewer');
};
document.head.appendChild(script);
}
}
function loadObservableUtils() {
if (!window.registerObservable) {
const script = document.createElement('script');
script.src = '/showcase-demos/pdf-stamps/observable-utils.js';
script.onload = () => {
registerObservable();
};
document.head.appendChild(script);
} else {
registerObservable();
}
}
initializeWebViewer();
loadObservableUtils();