Add and Edit Watermark Showcase Demo Code Sample

Requirements
View Demo

Easily add and edit watermarks on PDF documents. Watermark text is displayed as an overlay text on top of the original document.

This demo allows you to:

  • Choose your own PDF file
  • Add and edit watermark text
  • Customize styles of the watermark text

Implementation steps
To add watermarking capability with WebViewer:

Step 1: Follow get started guide in your preferred web stack for WebViewer
Step 2: Add the ES6 JavaScript sample code provided in this guide

1// ES6 Compliant Syntax
2// GitHub Copilot v1.0, GPT-4.1, September 22, 2025
3// File: index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7// Add Watermark Demo
8//
9// This code demonstrates the document watermarking capability in WebViewer, a JavaScript-based PDF SDK for web applications.
10// It allows the user to update the text and easily place an overlay text on top of the original document.
11//
12
13const defaultDoc = 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/arrest-warrant-signed.pdf';
14
15// Default colors for color picker
16const DEFAULT_COLORS = [
17 '#EB7532', // Orange
18 '#F7D038', // Yellow
19 '#A3E048', // Green
20 '#519A64', // Dark Green
21 '#33BBE6', // Blue
22 '#4355DB', // Dark Blue
23 '#D23BE7', // Purple
24 '#E6261F', // Red
25];
26
27// Default watermark properties
28let text = 'Hello World!';
29let size = 60;
30let opacity = 50;
31let color = DEFAULT_COLORS[0];
32let showTimestamp = true;
33
34// Customize UI section.
35const customizeUI = async (instance) => {
36 // Load default document.
37 await instance.Core.documentViewer.loadDocument(defaultDoc, {
38 extension: 'pdf'
39 });
40
41 drawWatermark(instance);
42};
43
44const drawWatermark = async (instance) => {
45 let watermarks = {
46 diagonal: {
47 text: text,
48 fontSize: size,
49 color: color,
50 opacity: opacity,
51 },
52 header: {
53 center: '',
54 fontSize: 10,
55 color: color,
56 opacity: 100,
57 }
58 };
59
60 if (showTimestamp) {
61 watermarks.header.center = `${new Date(Date.UTC(2000,0,0,0,0,0,0)).toTimeString()}`;
62 }
63
64 instance.Core.documentViewer.setWatermark({
65 ...watermarks,
66 });
67
68 instance.Core.documentViewer.refreshAll();
69 instance.Core.documentViewer.updateView();
70};
71
72
73// WebViewer section
74//
75// This code initializes the WebViewer with the basic settings
76// that are found in the default showcase WebViewer.
77//
78
79const searchParams = new URLSearchParams(window.location.search);
80const history = window.history || window.parent.history || window.top.history;
81const licenseKey = 'YOUR_LICENSE_KEY';
82const element = document.getElementById('viewer');
83
84WebViewer({
85 path: '/lib',
86 licenseKey: licenseKey,
87}, element).then((instance) => {
88 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool.
89 const cloudyTools = [
90 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
91 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
92 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
93 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
94 ];
95 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
96 instance.UI.disableTools(cloudyTools);
97
98 // Set default toolbar group to Annotate.
99 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
100
101 // Set default tool on mobile devices to Pan.
102 if (isMobileDevice()) {
103 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
104 }
105
106 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
107 if (searchParams.has('file')) {
108 searchParams.delete('file');
109 history.replaceState(null, '', '?' + searchParams.toString());
110 }
111 });
112
113 instance.Core.annotationManager.enableAnnotationNumbering();
114
115 instance.UI.NotesPanel.enableAttachmentPreview();
116
117 // Add the demo-specific functionality.
118 customizeUI(instance).then(() => {
119 // Create UI controls after demo is initialized.
120 createUIControls(instance);
121 });
122});
123
124// Function to check if the user is on a mobile device.
125const isMobileDevice = () => {
126 return (
127 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(
128 window.navigator.userAgent
129 ) ||
130 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
131 window.navigator.userAgent.substring(0, 4)
132 )
133 );
134}
135
136// Cleanup function for when the demo is closed or page is unloaded.
137const cleanup = (instance) => {
138 if (typeof instance !== 'undefined' && instance.UI) {
139 // Clean up any resources, if needed.
140 console.log('Cleaning up complex-generation demo');
141 }
142};
143
144// Register cleanup for page unload.
145window.addEventListener('beforeunload', () => cleanup(instance));
146window.addEventListener('unload', () => cleanup(instance));
147
148// UI section
149//
150// Helper code to add controls to the viewer holding the buttons.
151// This code creates a container for the buttons, styles them, and adds them to the viewer.
152//
153
154// Watermark text input
155const watermarkTextSection = (instance) => {
156 const section = document.createElement('div');
157 section.className = 'watermark-Text-Section';
158
159 const label = document.createElement('label');
160 label.textContent = 'Text';
161
162 section.appendChild(label);
163
164 const input = document.createElement('input');
165 input.className = 'watermark-text-input';
166 input.type = 'text';
167 input.value = text;
168
169 input.addEventListener('input', (e) => {
170 text = e.target.value;
171 drawWatermark(instance);
172 });
173
174 section.appendChild(input);
175
176 return section;
177};
178
179// Font Size slider
180const fontSizeSlider = (instance) => {
181 const section = document.createElement('div');
182 section.className = 'font-size-slider-section';
183
184 const label = document.createElement('label');
185 label.textContent = 'Font Size';
186
187 section.appendChild(label);
188
189 const slider = document.createElement('input');
190 slider.className = 'font-size-slider';
191 slider.type = 'range';
192 slider.defaultValue = '48';
193 slider.value = '48';
194 slider.min = '14';
195 slider.max = '200';
196 slider.step = '1';
197
198 slider.addEventListener('input', (e) => {
199 size = parseInt(e.target.value, 10);
200 drawWatermark(instance);
201 });
202
203 section.appendChild(slider);
204
205
206 return section;
207};
208
209// Opacity slider
210const opacitySlider = (instance) => {
211 const section = document.createElement('div');
212 section.className = 'opacity-slider-section';
213
214 const label = document.createElement('label');
215 label.textContent = 'Opacity';
216
217 section.appendChild(label);
218
219 const slider = document.createElement('input');
220 slider.className = 'font-size-slider';
221 slider.type = 'range';
222 slider.defaultValue = '48';
223 slider.value = '48';
224 slider.min = '0';
225 slider.max = '100';
226 slider.step = '1';
227
228 slider.addEventListener('input', (e) => {
229 opacity = parseInt(e.target.value, 10);
230 drawWatermark(instance);
231 });
232
233
234 section.appendChild(slider);
235
236 return section;
237};
238
239// Color picker
240const colorPicker = (instance) => {
241 const wrapper = document.createElement('div');
242
243 const colorDisplay = document.createElement('div');
244 colorDisplay.className = 'color-display';
245
246 const colorDisplayLabel = document.createElement('p');
247 colorDisplayLabel.className = 'color-display-label';
248 colorDisplayLabel.textContent = 'Color:';
249
250 const colorDisplayInput = document.createElement('input');
251 colorDisplayInput.className = 'color-display-input';
252 colorDisplayInput.type = 'text';
253 colorDisplayInput.readOnly = true;
254
255 colorDisplayInput.addEventListener('input', (e) => {
256 color = e.target.value;
257 drawWatermark(instance);
258 });
259
260 colorDisplay.appendChild(colorDisplayLabel);
261 colorDisplay.appendChild(colorDisplayInput);
262
263 wrapper.appendChild(colorDisplay);
264
265 const checkboxContainer = document.createElement('div');
266 checkboxContainer.className = 'checkbox-container';
267
268 DEFAULT_COLORS.forEach((default_color) => {
269 // Actual radio/checkbox input
270 const checkbox = document.createElement('input');
271 checkbox.className = 'checkbox-input';
272 checkbox.name = 'watermark-color';
273 checkbox.id = `checkbox-watermark-color-${default_color.replace('#', '')}`;
274 checkbox.type = 'radio';
275 checkbox.value = default_color;
276
277 if (default_color === color) {
278 checkbox.checked = true;
279 colorDisplayInput.value = `${default_color}`;
280 }
281
282 checkbox.onchange = (e) => {
283 if (e.target.checked) {
284 colorDisplayInput.value = `${e.target.value}`;
285 colorDisplayInput.dispatchEvent(new Event('input'));
286 }
287 };
288
289 // Hide to use custom checkbox instead.
290 checkbox.style.display = 'none';
291
292 checkboxContainer.appendChild(checkbox);
293
294 // Custom checkbox
295 const span = document.createElement('span');
296 span.className = 'checkbox-checkmark';
297 span.id = `checkmark-watermark-color-${default_color.replace('#', '')}`;
298
299 if (checkbox.checked) {
300 span.textContent = '✓'; // U+2713
301 }
302
303 span.onclick = () => {
304
305 // Update all checkmarks of the same group.
306 document.querySelectorAll(`input[name=watermark-color]`).forEach((cb) => {
307 const checkmark = document.getElementById(`checkmark-watermark-color-${cb.value.replace('#', '')}`);
308 if (checkmark) checkmark.textContent = '';
309 });
310 document.getElementById(`checkmark-watermark-color-${default_color.replace('#', '')}`).textContent = '✓'; // U+2713
311
312 checkbox.checked = true;
313 checkbox.onchange({ target: checkbox });
314 };
315
316 // Set each checkbox's color to a color in the default colors array.
317 span.style.backgroundColor = default_color;
318 span.style.border = `3px solid ${default_color}`;
319
320 checkboxContainer.appendChild(span);
321 });
322
323 wrapper.appendChild(checkboxContainer);
324
325 return wrapper;
326};
327
328
329// Timestamp checkbox
330const timestampCheckbox = (instance) => {
331 const section = document.createElement('div');
332 section.className = 'timestamp-checkbox-section';
333
334 const checkbox = document.createElement('input');
335 checkbox.className = 'timestamp-checkbox';
336 checkbox.type = 'checkbox';
337 checkbox.checked = showTimestamp;
338
339 checkbox.onchange = (e) => {
340 showTimestamp = e.target.checked;
341 drawWatermark(instance);
342 };
343
344 checkbox.onchange = (e) => {
345 showTimestamp = e.target.checked;
346 drawWatermark(instance);
347 };
348
349 section.appendChild(checkbox);
350
351 const label = document.createElement('label');
352 label.className = 'timestamp-label';
353 label.textContent = 'Show timestamp';
354
355 section.appendChild(label);
356
357 return section;
358};
359
360const createUIControls = (instance) => {
361 // Create a container for all controls (label, dropdown, and buttons).
362 const controlsContainer = document.createElement('div');
363 controlsContainer.className = 'controls-container';
364
365 controlsContainer.appendChild(watermarkTextSection(instance));
366 controlsContainer.appendChild(fontSizeSlider(instance));
367 controlsContainer.appendChild(opacitySlider(instance));
368 controlsContainer.appendChild(colorPicker(instance));
369 controlsContainer.appendChild(timestampCheckbox(instance));
370
371 // Add the controls container to the viewer.
372 element.insertBefore(controlsContainer, element.firstChild);
373};

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales