Color Separation Showcase Demo Code Sample

Requirements
View Demo

Easily manipulate colors in PDF documents — all on the client side.

This demo lets you:

  • Process CMYK colors.
  • Upload your own PDF.
  • Show, hide, and isolate colors with ease.

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.

License Key

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

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales