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