PDF Stamps Showcase Demo Code Sample

Requirements
View Demo

Easily add stamps to your documents. The viewer includes several sample stamps, and you can also create custom ones using text or images.

This demo allows you to:

  • Load default stamps and add them to a document
  • Create custom stamps:
    • Text
    • Image
  • Resize stamps
  • Download edited document with annotated stamps

Implementation steps
To add stamping capability with WebViewer:

Step 1: Choose your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide

1// ES6 Compliant Syntax
2// Copilot name: GitHub Copilot, version: 1.0.0, model: GPT-4, version: 2024-06, date: 2025-10-23
3// File: showcase-demos/edit-table-of-contents/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// State variables for demo
8const dropPoint = {};
9const customStampDemoFile = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/sales-invoice.pdf';
10
11function initializeWebViewer() {
12 WebViewer(
13 {
14 path: '/lib',
15 initialDoc: customStampDemoFile,
16 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
17 licenseKey: 'YOUR_LICENSE_KEY', // Replace with your license key
18 },
19 document.getElementById('viewer')
20 ).then((instance) => {
21
22 createUIElements();
23
24 // Set toolbar group immediately after WebViewer loads
25 instance.UI.setToolbarGroup(['toolbarGroup-Insert']);
26
27 // Wait for document to load before setting tool mode (more reliable)
28 instance.Core.documentViewer.addEventListener('documentLoaded', () => {
29
30 // Set the rubber stamp tool mode
31 instance.UI.setToolMode('AnnotationCreateRubberStamp');
32
33 // Add a small delay before opening the panel
34 setTimeout(() => {
35 instance.UI.openElements(['rubberStampPanel']);
36 }, 150);
37
38 // Register events after the page is loaded
39 demoComplete();
40 registerDragEvents();
41 });
42 });
43};
44
45const getIframeWindow = () => {
46 return (
47 document.getElementById('viewer')?.getElementsByTagName('iframe')?.[0]?.contentWindow || window
48 );
49};
50
51const getIframeContext = () => {
52 return (
53 document.getElementById('viewer')?.getElementsByTagName('iframe')?.[0]?.contentWindow
54 .instance || window.WebViewer.getInstance()
55 );
56};
57
58// Drag and drop handlers
59const dragOver = (e) => {
60 e.preventDefault();
61 //const imgData = e.dataTransfer.getData('text/plain');
62 e.target.style.opacity = 1;
63 e.preventDefault();
64};
65
66// Update drop event to adjust dropPoint to fit document coordinates
67const drop = (e) => {
68 console.log('File dropped:', e);
69 e.preventDefault();
70 e.stopPropagation();
71 const documentViewer = getIframeContext().Core.documentViewer;
72 const scrollElement = documentViewer.getScrollViewElement();
73 const scrollLeft = scrollElement.scrollLeft || 0;
74 const scrollTop = scrollElement.scrollTop || 0;
75 const zoomLevel = documentViewer.getZoomLevel();
76 dropPoint.current = { x: (e.pageX + scrollLeft), y: (e.pageY + scrollTop) };
77 const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
78 const doc = documentViewer.getDocument();
79
80 const documentWidth = doc.getPageInfo(1).width;
81 const documentHeight = doc.getPageInfo(1).height;
82 dropPoint.current.x = Math.min(Math.max(dropPoint.current.x, 0), documentWidth);
83 dropPoint.current.y = Math.min(Math.max(dropPoint.current.y, 0), documentHeight);
84 const page = displayMode.getSelectedPages(dropPoint.current, dropPoint.current);
85
86 if (!page.first) {
87 console.error('128 Invalid page location for point:', dropPoint.current);
88
89 // Log dimensions of all pages for debugging
90 const pageCount = doc.getPageCount();
91 for (let i = 1; i <= pageCount; i++) {
92 const pageInfo = doc.getPageInfo(i);
93 console.log(`Page ${i} bounds:`, pageInfo);
94 }
95
96 return;
97 }
98
99 const pageInfo = doc.getPageInfo(page.first);
100 console.log('Selected page bounds:', pageInfo);
101
102 let pagePoint = displayMode.windowToPage(dropPoint.current, page.first);
103 console.log('Mapped pagePoint before clamping:', pagePoint);
104
105 if (!pagePoint || pagePoint.x < 0 || pagePoint.y < 0 || pagePoint.x > pageInfo.width || pagePoint.y > pageInfo.height) {
106 console.error('Invalid page coordinates after mapping:', pagePoint);
107 pagePoint = {
108 x: Math.min(Math.max(pagePoint.x || 0, 0), pageInfo.width),
109 y: Math.min(Math.max(pagePoint.y || 0, 0), pageInfo.height),
110 };
111 }
112
113 const imgData = e.dataTransfer.getData('text/plain');
114 if (!imgData) {
115 console.error('No imgData found in dataTransfer');
116 return;
117 }
118
119 console.log('Dimensions of image dataTransfer:', e.dataTransfer);
120 let stampSize = { width: 100, height: 25 }; //Default size
121 let img = new Image();
122 img.src = imgData; // Set the base64 string as the source
123
124 img.onload = () => {
125 document.body.appendChild(img); // Append the image to the DOM temporarily
126 const renderedWidth = img.offsetWidth; // Rendered width of the image
127 const renderedHeight = img.offsetHeight; // Rendered height of the image
128 document.body.removeChild(img); // Remove the image from the DOM
129
130 // You can now use these dimensions to set the stamp size
131 stampSize.width = renderedWidth;
132 stampSize.height = renderedHeight;
133
134 //find slider and get the value for width of image
135 const widthSlider = document.getElementById('width-slider');
136 if (widthSlider) {
137 const sliderValue = parseFloat(widthSlider.value);
138 if (!isNaN(sliderValue) && sliderValue > 0) {
139 stampSize.width = sliderValue;
140 const aspectRatio = renderedWidth / renderedHeight;
141 stampSize.height = sliderValue / aspectRatio;
142 }
143 }
144
145 const viewFactor = 0.66; // Adjust this factor as needed, used to scale the stamp appropriately
146 let stampRect = { width: stampSize.width * zoomLevel * viewFactor, height: stampSize.height * zoomLevel * viewFactor };
147
148 try {
149 addStamp(imgData, pagePoint, stampRect, page.first);
150 console.log('Stamp added at point:', pagePoint, 'with rect:', stampRect);
151 } catch (error) {
152 console.error('Error while adding stamp:', error);
153 console.error('Parameters passed to addStamp:', {
154 imgData,
155 point: pagePoint,
156 rect: stampRect,
157 pageNumber: page.first,
158 });
159 }
160 };
161
162 img.onerror = () => {
163 console.error('Failed to load image from data URL');
164 };
165};
166
167function demoComplete() {
168 //let defaultDocument = customStampDemoFile;
169 //console.log('Demo initialization complete');
170 const documentViewer = getIframeContext().Core.documentViewer;
171 documentViewer.addEventListener('toolModeUpdated', toolModeUpdated);
172 const iframeDoc = getIframeWindow().document.body;
173 iframeDoc.addEventListener('dragover', dragOver);
174 iframeDoc.addEventListener('drop', drop);
175
176 // Allow annotations outside page bounds
177 getIframeContext().Core.Tools.Tool.ALLOW_ANNOTS_OUTSIDE_PAGE = true;
178};
179
180const toolModeUpdated = (type) => {
181 const { id } = type;
182 if (id) {
183 // Check if stamps object exists and has the id
184 if (window.stamps && window.stamps[id]) {
185 data = window.stamps[id];
186 name = id;
187 } else {
188 data = null;
189 }
190 }
191};
192
193const stampPanelWidthOffset = 146;
194
195const addStamp = (
196 imgData,
197 point = {},
198 rect = { height: undefined, width: undefined }
199) => {
200 const { Annotations } = getIframeContext().Core;
201 const { documentViewer, annotationManager } = getIframeContext().Core;
202 const doc = documentViewer.getDocument();
203 const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
204
205 console.log('Adding stamp at point:', point, 'with rect:', rect);
206
207 //if stamp panel is open add points to x coordinate
208 if (getIframeContext().UI.isElementOpen('rubberStampPanel')) {
209 point.x += stampPanelWidthOffset + (rect.width < 50 ? rect.width + 50 : rect.width); // add width of stamp panel + width
210 console.log('326 Adjusted point for open stamp panel:', point);
211 }
212
213 const page = displayMode.getSelectedPages(point, point);
214 const page_num = page.first || documentViewer.getCurrentPage();
215 if (!page_num || typeof page_num !== 'number') {
216 console.error('Invalid page number:', page_num);
217 return;
218 }
219
220 const page_info = doc.getPageInfo(page_num);
221 const page_point = displayMode.windowToPage(point, page_num);
222 const zoom = documentViewer.getZoomLevel();
223 const stampAnnot = new Annotations.StampAnnotation();
224 stampAnnot.PageNumber = page_num;
225 const rotation = documentViewer.getCompleteRotation(page_num) * 90;
226 stampAnnot.Rotation = rotation;
227 if (rotation === 270 || rotation === 90) {
228 stampAnnot.Width = rect.height / zoom;
229 stampAnnot.Height = rect.width / zoom;
230 } else {
231 stampAnnot.Width = rect.width / zoom;
232 stampAnnot.Height = rect.height / zoom;
233 }
234
235 stampAnnot.X = (page_point.x || page_info.width / 2) - stampAnnot.Width / 2;
236 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
237 stampAnnot.Y = (page_point.y || page_info.height / 2) - stampAnnot.Height / 2;
238 stampAnnot.setImageData(imgData);
239 stampAnnot.Author = annotationManager.getCurrentUser();
240 annotationManager.deselectAllAnnotations();
241 annotationManager.addAnnotation(stampAnnot, true);
242 annotationManager.redrawAnnotation(stampAnnot);
243 annotationManager.selectAnnotation(stampAnnot);
244};
245
246// Register drag and drop events
247function registerDragEvents() {
248 // Required:
249 // target is 'viewer'
250 const dropTarget = document.getElementById('viewer');
251
252 // Just adds the '+' to show while dragging to the drop target
253 dropTarget.addEventListener('dragover', (e) => {
254 e.preventDefault();
255 e.dataTransfer.dropEffect = 'copy'; // with copy, shows '+' symbol during drag
256 });
257
258 dropTarget.addEventListener('drop', (e) => {
259 const imgData = e.dataTransfer.getData('text/plain'); // Retrieve using custom data type
260 if (!imgData) {
261 console.error('No imgData found in dataTransfer');
262 return;
263 }
264
265 const documentViewer = getIframeContext().Core.documentViewer;
266 const scrollElement = documentViewer.getScrollViewElement();
267 const scrollLeft = scrollElement.scrollLeft || 0;
268 const scrollTop = scrollElement.scrollTop || 0;
269 dropPoint.current = { x: e.pageX + scrollLeft, y: e.pageY + scrollTop };
270 const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
271 const page = displayMode.getSelectedPages(dropPoint.current, dropPoint.current);
272 if (!page.first) {
273 return;
274 }
275 });
276
277 // Custom annotation behavior checkbox handler
278 const setCustomAnnotationBehavior = () => {
279 const isChecked = document.getElementById('custom-annotation-behavior').checked;
280 getIframeContext().Core.Tools.Tool.ALLOW_ANNOTS_OUTSIDE_PAGE = isChecked;
281 };
282
283 // Register event listener for custom annotation behavior checkbox
284 const customAnnotationCheckbox = document.getElementById('custom-annotation-behavior');
285 if (customAnnotationCheckbox) {
286 customAnnotationCheckbox.addEventListener('click', setCustomAnnotationBehavior);
287 }
288
289 // Open custom stamp modal on button click
290 const setTextStamp = () => {
291 getIframeContext().UI.openElements(['customStampModal']);
292 };
293
294 // Register event listener for custom annotation behavior checkbox
295 const onClickCreateTextStamp = document.getElementById('choose-file-button-text');
296 if (onClickCreateTextStamp) {
297 onClickCreateTextStamp.addEventListener('click', setTextStamp);
298 }
299}
300
301function createUIElements() {
302 // Create a container for all controls (label, dropdown, and buttons)
303 // Dynamically load ui-elements.js if not already loaded
304 if (!window.SidePanel) {
305 const script = document.createElement('script');
306 script.src = '/showcase-demos/pdf-stamps/ui-elements.js';
307 script.onload = () => {
308 UIElements.init('viewer');
309
310 };
311 document.head.appendChild(script);
312 }
313}
314
315function loadObservableUtils() {
316 if (!window.registerObservable) {
317 const script = document.createElement('script');
318 script.src = '/showcase-demos/pdf-stamps/observable-utils.js';
319 script.onload = () => {
320 registerObservable();
321 };
322 document.head.appendChild(script);
323 } else {
324 registerObservable();
325 }
326}
327
328initializeWebViewer();
329loadObservableUtils();
330

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales