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 stamps 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 an 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.

Once you generate your license key, it will automatically be included in your sample code below.

License Key

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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales