Easily manipulate colors in PDF documents — all on the client side.
This demo lets you:
Implementation steps
To add Color Separation capability with WebViewer:
Step 1: Get started with WebViewer in your preferred web stack
Step 2: Add the ES6 JavaScript sample code provided in this guide
1import WebViewer from '@pdftron/webviewer';
2
3// CMYK Color Separation Demo
4// This code demonstrates how to show, hide and separate colors in PDF with no external dependencies or servers required.
5
6// Global variable to store the reset loading handler for proper cleanup
7let resetLoadingHandler = null;
8
9function initializeWebViewer() {
10
11 // This code initializes the WebViewer with the basic settings
12 WebViewer({
13 path: '/lib',
14 licenseKey: 'YOUR_LICENSE_KEY',
15 enableFilePicker: true,
16 }, document.getElementById('viewer')).then((instance) => {
17 // Enable the measurement toolbar so it appears with all the other tools, and disable Cloudy rectangular tool
18 const cloudyTools = [
19 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT,
20 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT2,
21 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT3,
22 instance.Core.Tools.ToolNames.CLOUDY_RECTANGULAR_AREA_MEASUREMENT4,
23 ];
24 instance.UI.enableFeatures([instance.UI.Feature.Measurement, instance.UI.Feature.Initials]);
25 instance.UI.disableTools(cloudyTools);
26 // Set default toolbar group to Annotate
27 instance.UI.setToolbarGroup('toolbarGroup-Annotate');
28 // Set default tool on mobile devices to Pan.
29 if (UIElements.isMobileDevice()) {
30 instance.UI.setToolMode(instance.Core.Tools.ToolNames.PAN);
31 }
32
33 instance.Core.documentViewer.addEventListener('documentUnloaded', () => {
34 if (searchParams.has('file')) {
35 searchParams.delete('file');
36 history.replaceState(null, '', '?' + searchParams.toString());
37 }
38 });
39
40 instance.Core.annotationManager.enableAnnotationNumbering();
41 instance.UI.NotesPanel.enableAttachmentPreview();
42 // Add the demo-specific functionality
43 customizeUI(instance).then(() => {
44 // Create UI controls after demo is initialized
45 UIElements.createUIControls();
46 });
47 });
48}
49
50let colorValues = [];
51let loadingColors = [];
52const searchParams = new URLSearchParams(window.location.search);
53const history = window.history || window.parent.history || window.top.history;
54
55const customizeUI = async (instance) => {
56 // Load the default document
57 await instance.UI.loadDocument('https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/op_blend_test.pdf');
58 // Set allowed file types to load
59 const allowedFileTypes = [
60 'pdf', // PDF
61 'jpg', 'jpeg', 'png', // Images
62 'doc', 'docx', 'docm', 'dotx', 'dotm', 'dot', 'xls', 'xlsx', 'xlsm', 'xlt', 'xls', 'odt', 'txt', 'rtf', 'pptx', 'ppt', 'pptm', 'pps', 'pot' // MS Office
63 ];
64 instance.Core.setAllowedFileExtensions(allowedFileTypes);
65 await initColorSeparation(instance); // Initialize color separation
66};
67
68const initColorSeparation = async (instance) => {
69 const { documentViewer } = instance.Core;
70 documentViewer.addEventListener('documentLoaded', async () => {
71 // Clear checkbox container from all previous color separations
72 const checkboxContainer = document.querySelector('.checkbox-container');
73 if (checkboxContainer) {
74 checkboxContainer.innerHTML = '';
75 }
76 // Get the document object
77 const doc = documentViewer.getDocument();
78 // Enable color separations on the document
79 doc.enableColorSeparations();
80 // Event listener for when a new color separation is added
81 doc.addEventListener('colorSeparationAdded', (colorData) => {
82 // Add a new checkbox for the added color separation
83 const checkboxContainer = document.querySelector('.checkbox-container');
84 if (checkboxContainer) {
85 // Create a wrapper for the checkbox
86 const checkboxWrapper = document.createElement('div');
87 checkboxWrapper.className = 'checkbox-wrapper';
88 // Add a checkbox for the new color separation
89 const checkbox = document.createElement('input');
90 checkbox.type = 'checkbox';
91 checkbox.id = `color-checkbox-${colorData.name}`;
92 checkbox.name = 'colorSeparation';
93 checkbox.value = colorData.name;
94 checkbox.checked = colorData.enabled;
95 checkbox.onchange = (e) => {
96 toggleColor(instance, colorData, e.target.checked);
97 }
98 checkbox.style.display = 'none'; // Hide the default checkbox
99 // Custom checkbox
100 const span = document.createElement('span');
101 span.className = 'checkbox-checkmark';
102 span.id = `checkmark-${colorData.name}`;
103 if (checkbox.checked) {
104 span.textContent = '✓'; // U+2713
105 }
106 span.onclick = () => {
107 checkbox.checked = !checkbox.checked;
108 checkbox.onchange({ target: checkbox });
109 span.textContent = checkbox.checked ? '✓' : ''; // U+2713
110 }
111 span.style.backgroundColor = rgbToString(colorData.rgb);
112 span.style.border = `3px solid ${rgbToString(colorData.rgb)}`;
113 // Label for the checkbox
114 const label = document.createElement('label');
115 label.className = 'label';
116 label.textContent = colorData.name;
117 label.onclick = () => {
118 checkbox.checked = !checkbox.checked;
119 checkbox.onchange({ target: checkbox });
120 span.textContent = checkbox.checked ? '✓' : ''; // U+2713
121 }
122 // Percentage text
123 const percentage = document.createElement('span');
124 percentage.id = `percentage-${colorData.name}`;
125 percentage.className = 'label';
126 percentage.textContent = `0%`;
127 // Append elements to the wrapper and then to the container
128 checkboxWrapper.appendChild(checkbox);
129 checkboxWrapper.appendChild(span);
130 checkboxWrapper.appendChild(label);
131 checkboxWrapper.appendChild(percentage);
132 checkboxContainer.appendChild(checkboxWrapper);
133 }
134 });
135 });
136
137 // Helper function to convert RGB array to CSS rgb() string
138 const rgbToString = (rgb) => {
139 const [r, g, b] = rgb;
140 return `rgb(${r}, ${g}, ${b})`;
141 };
142
143 // Function to toggle color separation
144 const toggleColor = (instance, color, checked) => {
145 const documentViewer = instance.Core.documentViewer;
146 loadingColors = [...loadingColors, color.name];
147 console.log(`Toggling color separation for ${color.name}:`, checked);
148
149 // Create handler if it doesn't exist
150 if (!resetLoadingHandler) {
151 resetLoadingHandler = () => resetLoading(instance);
152 }
153
154 documentViewer.addEventListener('pageComplete', resetLoadingHandler);
155 documentViewer.getDocument().enableSeparation(color.name, checked);
156 documentViewer.refreshAll();
157 documentViewer.updateView();
158 };
159
160 // Function to update color values on mouse move
161 documentViewer.addEventListener('mouseMove', (event) => {
162 const mouseLocation = documentViewer.getToolMode().getMouseLocation(event);
163 const displayMode = documentViewer.getDisplayModeManager().getDisplayMode();
164 const pageNumber = displayMode.getSelectedPages(mouseLocation, mouseLocation).first;
165 if (pageNumber !== null) {
166 const pageCoordinate = displayMode.windowToPage(mouseLocation, pageNumber);
167 if (pageCoordinate) {
168 const results = documentViewer.getColorSeparationsAtPoint(pageNumber, pageCoordinate.x, pageCoordinate.y);
169 colorValues = results.reduce(function (map, obj) {
170 map[obj.name] = obj.value;
171 return map;
172 }, {});
173 }
174 }
175 // Update percentage text for each color
176 const percentageElements = document.querySelectorAll('.label[id^="percentage-"]');
177 percentageElements.forEach((el) => {
178 const colorName = el.id.replace('percentage-', '');
179 if (colorValues && colorValues[colorName] !== undefined) {
180 el.textContent = `${colorValues[colorName]}%`;
181 } else {
182 el.textContent = `0%`;
183 }
184 });
185 });
186};
187
188// Function to reset which separations are loaded in order to disable/enable specific separations
189const resetLoading = (instance) => {
190 loadingColors = [];
191 instance.Core.documentViewer.removeEventListener('pageComplete', resetLoadingHandler);
192};
193
194// Cleanup function for when the demo is closed or page is unloaded
195const cleanup = (instance) => {
196 if (typeof instance !== 'undefined' && instance.UI) {
197 if (instance.Core.documentViewer.getDocument()) {
198 instance.Core.documentViewer
199 .getDocument()
200 .removeEventListener('colorSeparationAdded', setColorData);
201 instance.Core.documentViewer.removeEventListener('pageComplete', resetLoadingHandler);
202 instance.Core.documentViewer.removeEventListener('mouseMove', updateMouseMove);
203 }
204 console.log('Cleaning up color-separation demo');
205 }
206};
207
208// Register cleanup for page unload
209window.addEventListener('beforeunload', () => cleanup(instance));
210window.addEventListener('unload', () => cleanup(instance));
211
212// Helper function to load the ui-elements.js script
213function loadUIElementsScript() {
214 return new Promise((resolve, reject) => {
215 if (window.UIElements) {
216 console.log('UIElements already loaded');
217 resolve();
218 return;
219 }
220 const script = document.createElement('script');
221 script.src = '/showcase-demos/color-separation/ui-elements.js';
222 script.onload = function () {
223 console.log('✅ UIElements script loaded successfully');
224 resolve();
225 };
226 script.onerror = function () {
227 console.error('Failed to load UIElements script');
228 reject(new Error('Failed to load ui-elements.js'));
229 };
230 document.head.appendChild(script);
231 });
232}
233
234// Load UIElements script first, then initialize WebViewer
235loadUIElementsScript().then(() => {
236 initializeWebViewer();
237}).catch((error) => {
238 console.error('Failed to load UIElements:', error);
239});
240
1// UI Elements class to create and manage custom UI controls
2//
3// Helper code to add controls to the viewer holding the buttons
4// This code creates a container for the buttons, styles them, and adds them to the viewer
5//
6class UIElements {
7 // Function to check if the user is on a mobile device
8 static isMobileDevice = () => {
9 return (
10 /(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(
11 window.navigator.userAgent
12 ) ||
13 /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(
14 window.navigator.userAgent.substring(0, 4)
15 )
16 );
17 }
18
19 static checkboxes = () => {
20 // Create a container for the checkboxes
21 const container = document.createElement('div');
22 container.className = 'checkbox-container';
23 return container;
24 }
25
26 static createUIControls = () => {
27 // Create a container for all controls
28 const controlsContainer = document.createElement('div');
29 controlsContainer.className = 'controls-container';
30
31 // Create and append the colors checkbox control
32 controlsContainer.appendChild(this.checkboxes());
33
34 // Add the controls container to the viewer element
35 const element = document.getElementById('viewer');
36 element.insertBefore(controlsContainer, element.firstChild);
37
38 console.log('✅ UI controls created');
39 };
40}
1/* CSS standard Compliant Syntax */
2/* GitHub Copilot v1.0, GPT-4.1, September 29, 2025 */
3/* File: index.css */
4
5/* Controls Container */
6.controls-container {
7 display: flex;
8 flex-direction: row;
9 align-items: center;
10 gap: 15px;
11 margin: 5px 0;
12 padding: 16px;
13 padding-bottom: 5px;
14 border-bottom: 1px solid #DFE1E6;
15 background-color: rgba(112, 198, 255, 0.2);
16}
17
18.checkbox-container {
19 display: flex;
20 flex-direction: row;
21 flex-wrap: wrap;
22 margin: 10px 0;
23 gap: 10px;
24}
25
26.checkbox-wrapper {
27 display: flex;
28 flex-direction: column;
29 align-items: center;
30 margin-right: 10px;
31}
32
33.label {
34 font-size: 14px;
35 line-height: 20px;
36 color: #485056;
37 letter-spacing: -0.3px;
38 margin-right: 5px;
39 font-weight: 700;
40 cursor: pointer;
41}
42
43.checkbox-checkmark {
44 border-radius: 6px;
45 width: 24px;
46 height: 24px;
47 margin: 3px;
48 cursor: pointer;
49 text-align: center;
50 line-height: 18px;
51 display: inline-block;
52 color: #FFFFFF;
53 /* The following will be set dynamically in JS:
54 background-color, border */
55}
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales