Easily perform changes to PDF pages, including split, merge, append, replicate, reorder, and more.
This demo allows you to:
Implementation steps
To add PDF Page Manipulation 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
1
2// ES6 Compliant Syntax
3// GitHub Copilot - GPT-4 Model - October 16, 2025
4// File: pdf-page-manipulation-api/index.js
5
6import WebViewer from '@pdftron/webviewer';
7import { saveAs } from 'file-saver';
8// Global variables
9const element = document.getElementById('viewer');
10let instance = null;
11
12// Initialize WebViewer
13function initializeWebViewer() {
14 if (!element) {
15 console.error('Viewer div not found.');
16 return;
17 }
18 // Initialize WebViewer
19 WebViewer({
20 path: '/lib',
21 licenseKey: 'YOUR_LICENSE_KEY',
22 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
23 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
24 }, element).then((inst) => {
25 instance = inst;
26 const { documentViewer} = instance.Core;
27 documentViewer.addEventListener('documentLoaded', () => {
28 const doc = documentViewer.getDocument();
29 console.log(`Document loaded with ${doc.getPageCount()} pages.`);
30 createUIElements().then(() => {
31 // Now initialize rotateAngle options values (after UI elements are created)
32 if (window.rotateAngle) {
33 window.rotateAngle.children[0].value = instance.Core.PageRotation.E_90;
34 window.rotateAngle.children[1].value = instance.Core.PageRotation.E_180;
35 window.rotateAngle.children[2].value = instance.Core.PageRotation.E_270;
36 } else {
37 console.error('rotateAngle element not found after UI creation');
38 }
39 // activate thumbnails panel
40 instance.UI.openElements(['tabPanel']);
41 instance.UI.setActiveTabInPanel({ tabPanel: 'tabPanel', tabName: 'thumbnailsPanel' });
42 });
43 });
44 });
45} // Close initializeWebViewer function
46
47// validate page number input
48const validPageNumber = (pageNum, doc) => {
49 if (pageNum > doc.getPageCount() || pageNum < 1) {
50 alert('Please enter a valid page number.');
51 return false;
52 }
53 return true;
54}
55
56// convert a string like "1,2,5-7" to an array of numbers [1,2,5,6,7]
57const parseStringToNumberArray = (val, maxVal) => {
58 // check for valid number, comma, or dash pattern
59 if (!/^[0-9]+([,-][0-9]+)*$/.test(val)) {
60 return [];
61 }
62 const numbers = [];
63 val.split(',').forEach((group) => {
64 const range = group.split('-');
65 const start = Number(range[0]);
66 let end = Number(range.length === 1 ? range[0] : range[1]);
67 if (end > maxVal) {
68 end = maxVal;
69 }
70 for (let i = start; i <= end; i++) {
71 numbers.push(i);
72 }
73 });
74 return numbers;
75}
76
77// delete specified pages from document
78const deletePages = () => {
79 const doc = instance.Core.documentViewer.getDocument();
80 const pageNumbers = pagesRange.value;
81 const result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
82 if (!validPageNumber(pageNumbers, doc)) return;
83 if (result.length === doc.getPageCount()) {
84 alert('Cannot delete all pages in the document.');
85 return;
86 }
87 if (result.length > 0) {
88 doc.removePages(result);
89 } else {
90 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
91 }
92}
93
94// rotate specified pages in document
95const rotatePages = () => {
96 const pageNumbers = pagesRange.value;
97 const rotation = Number(rotateAngle.value);
98 const doc = instance.Core.documentViewer.getDocument();
99 if (!validPageNumber(pageNumbers, doc)) return;
100 let result = [];
101 if (pageNumbers) {
102 result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
103 } else {
104 // No page numbers provided, rotating all pages
105 for (let i = 1; i <= doc.getPageCount(); i++) {
106 result.push(i);
107 }
108 }
109 if (result.length > 0) {
110 doc.rotatePages(result, rotation);
111 } else {
112 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
113 }
114}
115
116// move a page to a new location in the document
117const movePage = () => {
118 const doc = instance.Core.documentViewer.getDocument();
119 const pageOne = Number(pageInput.value);
120 let pageTwo = Number(locationInput.value);
121 if (!validPageNumber(pageOne, doc) || !validPageNumber(pageTwo, doc)) return;
122 if (pageOne < pageTwo) pageTwo++; // adjust for removing the "from" page
123 doc.movePages([pageOne], pageTwo);
124}
125
126// insert a blank page at the specified location
127const insertBlankPage = () => {
128 let pageNumber = Number(pageInput.value);
129 const doc = instance.Core.documentViewer.getDocument();
130 if (pageNumber === -1) { // append to end
131 pageNumber = doc.getPageCount() + 1;
132 } else if (!validPageNumber(pageNumber, doc)) return;
133 const pageSize = doc.getPageInfo(1);
134 doc.insertBlankPages([pageNumber], pageSize.width, pageSize.height);
135}
136
137// extract specified pages from document and save as a new file
138const extractPages = async () => {
139 const pageNumbers = pagesRange.value;
140 const doc = instance.Core.documentViewer.getDocument();
141 const pageCount = doc.getPageCount();
142 if (!validPageNumber(pageNumbers, doc)) return;
143 let pagesToExtract = [];
144 if (pageNumbers) {
145 pagesToExtract = parseStringToNumberArray(pageNumbers, pageCount);
146 } else {
147 for (let i = 1; i <= pageCount; i++) {
148 pagesToExtract.push(i);
149 }
150 }
151 if (pagesToExtract.length > 0) {
152 const annotList = instance.Core.annotationManager.getAnnotationsList().filter((annot) => pagesToExtract.indexOf(annot.PageNumber) > -1);
153 const xfdfString = await instance.Core.annotationManager.exportAnnotations({ annotationList: annotList });
154 doc.extractPages(pagesToExtract, xfdfString).then((data) => {
155 const arr = new Uint8Array(data);
156 const blob = new Blob([arr], { type: 'application/pdf' });
157 saveAs(blob, 'extracted.pdf');
158 });
159 } else {
160 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
161 }
162}
163
164// crop the specified page by the specified pixel amounts from 4 sides
165const cropPage = () => {
166 const pageNumber = Number(pageInput.value);
167 const doc = instance.Core.documentViewer.getDocument();
168 if (!validPageNumber(pageNumber, doc)) return;
169 const top = Number(cropTop.value);
170 const bottom = Number(cropBottom.value);
171 const left = Number(cropLeft.value);
172 const right = Number(cropRight.value);
173 doc.cropPages([pageNumber], top, bottom, left, right);
174}
175
176// merge an uploaded document into the current document at the specified location
177const mergeDocuments = () => {
178 if (!window.newDoc) {
179 alert('Please upload a document to merge first');
180 return;
181 }
182 const pageNumbers = pagesRange.value;
183 let pagesToInsert = [];
184 if (pageNumbers) {
185 pagesToInsert = parseStringToNumberArray(pageNumbers, window.newDoc.getPageCount());
186 } else {
187 for (let i = 1; i <= window.newDoc.getPageCount(); i++) {
188 pagesToInsert.push(i);
189 }
190 }
191 const locationToInsert = Number(locationInput.value);
192 if (pagesToInsert.length > 0) {
193 instance.Core.documentViewer.getDocument().insertPages(window.newDoc, pagesToInsert, locationToInsert);
194 } else {
195 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
196 }
197}
198
199// Expose functions to global scope for UI elements
200window.deletePages = deletePages;
201window.rotatePages = rotatePages;
202window.movePage = movePage;
203window.insertBlankPage = insertBlankPage;
204window.extractPages = extractPages;
205window.cropPage = cropPage;
206window.mergeDocuments = mergeDocuments;
207window.parseStringToNumberArray = parseStringToNumberArray;
208window.validPageNumber = validPageNumber;
209
210// UI Elements
211// Function to create and initialize UI elements
212function createUIElements() {
213 return new Promise((resolve) => {
214 // Create a container for all controls (label, dropdown, and buttons)
215 // Dynamically load ui-elements.js if not already loaded
216 if (typeof UIElements === 'undefined') {
217 const script = document.createElement('script');
218 script.src = '/showcase-demos/pdf-page-manipulation-api/ui-elements.js';
219 script.onload = () => {
220 console.log('ui-elements.js loaded successfully');
221 UIElements.init(instance);
222 resolve(); // Resolve when UI elements are created
223 };
224 script.onerror = () => {
225 console.error('Failed to load ui-elements.js');
226 resolve(); // Resolve even on error to prevent hanging
227 };
228 document.head.appendChild(script);
229 } else {
230 console.log('UIElements already available, initializing...');
231 UIElements.init(instance);
232 resolve(); // Resolve immediately if UIElements is already loaded
233 }
234 });
235}
236
237// Initialize WebViewer when the window loads
238initializeWebViewer();
1// ES6 Compliant Syntax
2// GitHub Copilot, Claude Sonnet 4 (Preview), October 16, 2025
3// File: showcase-demos/pdf-page-manipulation-api/ui-elements.js
4
5class UIElements {
6
7 static init(instance) {
8 this.createUIElements(instance);
9 }
10
11 static createUIElements(instance) {
12 const element = document.getElementById('viewer');
13 let newDoc = null; // document to merge into the current document
14
15 // Check if controls container already exists
16 let controlsContainer = document.getElementById('controls-container');
17 if (controlsContainer) {
18 console.log('Controls container already exists, skipping creation');
19 return; // Exit early if container already exists
20 }
21
22 // UI section
23 // Create a container and insert the controls in it
24 controlsContainer = document.createElement('div');
25 const commandsList = document.createElement('select');
26 controlsContainer.appendChild(commandsList);
27 const uploadButton = document.createElement('input');
28 controlsContainer.appendChild(uploadButton);
29 const pagesRange = document.createElement('input');
30 controlsContainer.appendChild(pagesRange);
31 const rotateAngle = document.createElement('select');
32 window.rotateAngle = rotateAngle; // expose to global scope for function access
33 controlsContainer.appendChild(rotateAngle);
34 const topLabel = document.createElement('label');
35 topLabel.textContent = 'Top';
36 controlsContainer.appendChild(topLabel);
37 const cropTop = document.createElement('input');
38 controlsContainer.appendChild(cropTop);
39 const bottomLabel = document.createElement('label');
40 bottomLabel.textContent = 'Bottom';
41 controlsContainer.appendChild(bottomLabel);
42 const cropBottom = document.createElement('input');
43 controlsContainer.appendChild(cropBottom);
44 const leftLabel = document.createElement('label');
45 leftLabel.textContent = 'Left';
46 controlsContainer.appendChild(leftLabel);
47 const cropLeft = document.createElement('input');
48 controlsContainer.appendChild(cropLeft);
49 const rightLabel = document.createElement('label');
50 rightLabel.textContent = 'Right';
51 controlsContainer.appendChild(rightLabel);
52 const cropRight = document.createElement('input');
53 controlsContainer.appendChild(cropRight);
54 cropTop.type = cropBottom.type = cropLeft.type = cropRight.type = 'number';
55 cropTop.className = cropBottom.className = cropLeft.className = cropRight.className = 'elements-style';
56 cropTop.value = cropBottom.value = cropLeft.value = cropRight.value = 50;
57
58 const pageInput = document.createElement('input');
59 controlsContainer.appendChild(pageInput);
60 const locationInput = document.createElement('input');
61 controlsContainer.appendChild(locationInput);
62 const runCommandButton = document.createElement('button');
63 controlsContainer.appendChild(runCommandButton);
64 const br = document.createElement('br');
65 controlsContainer.appendChild(br);
66 const promptLabel = document.createElement('label');
67 controlsContainer.appendChild(promptLabel);
68 controlsContainer.className = 'control-container';
69
70 uploadButton.type = 'file';
71 uploadButton.accept = '.pdf,.jpg,.jpeg,.png';
72 uploadButton.onchange = async (e) => {
73 const file = e.target.files[0];
74 const split = file.name.split('.');
75 const ext = split[split.length - 1];
76 newDoc = await instance.Core.createDocument(file, {
77 extension: ext,
78 filename: file.name,
79 });
80
81 // Update the global reference so it's available for merge operations
82 window.newDoc = newDoc;
83 console.log('File uploaded and newDoc updated:', file.name);
84 }
85
86 rotateAngle.className = commandsList.className = 'list-style';
87 pagesRange.className = pageInput.className = locationInput.className = 'elements-style';
88 uploadButton.className = runCommandButton.className = 'btn';
89 pagesRange.type = 'text';
90 pagesRange.placeholder = '1,2,3-5';
91 [90, 180, 270].forEach(deg => { // create rotate angle UI elements
92 const option = document.createElement('option');
93 //option.value will be set after WebViewer is initialized because values are in instance.Core.PageRotation
94 option.textContent = deg.toString()+'\u00b0'; // degree symbol °
95 rotateAngle.appendChild(option);
96 });
97 pageInput.type = 'text';
98 pageInput.placeholder = 'Page #';
99 locationInput.type = 'text';
100 locationInput.placeholder = 'to page #';
101 runCommandButton.textContent = 'Run Command';
102
103
104
105 runCommandButton.onclick = () => commandsArray[commandsList.selectedIndex].executeCommand();
106 const commandsArray = [
107 { value: 'Select Command', executeCommand: () => alert('Select a command from the list'), visibleItems: [],
108 text: 'Select a command from the list' },
109 { value: 'Rotate pages', executeCommand: () => window.rotatePages(), visibleItems: [pagesRange, rotateAngle],
110 text: 'Enter a comma or dash separated list of page numbers to rotate (leave empty for all pages)' },
111 { value: 'Delete pages', executeCommand: () => window.deletePages(), visibleItems: [pagesRange],
112 text: 'Enter a comma or dash separated list of page numbers to delete' },
113 { value: 'Move page', executeCommand: () => window.movePage(), visibleItems: [pageInput, locationInput],
114 text: 'Enter page number to move then location to move it to' },
115 { value: 'Insert blank page', executeCommand: () => window.insertBlankPage(), visibleItems: [pageInput],
116 text: 'Enter a page number to add a blank page at (-1 to append at end)' },
117 { value: 'Split / extract pages', executeCommand: () => window.extractPages(), visibleItems: [pagesRange],
118 text: 'Enter a comma or dash separated list of page numbers to extract (leave empty for all pages)' },
119 { value: 'Crop pages', executeCommand: () => window.cropPage(), visibleItems: [pageInput, cropTop, cropBottom, cropLeft, cropRight, topLabel, bottomLabel, leftLabel, rightLabel],
120 text: 'Enter amount of pixels to crop, and then a page number (cropping cannot be undone)' },
121 { value: 'Merge documents', executeCommand: () => window.mergeDocuments(), visibleItems: [uploadButton, pagesRange, locationInput],
122 text: 'Upload document to merge then enter list of pages to insert (leave empty for all pages) and position to insert the pages at' },
123 ];
124 commandsArray.forEach(cmd => { // create a UI element in the list for each command
125 const option = document.createElement('option');
126 option.value = cmd.value;
127 option.textContent = cmd.value;
128 commandsList.appendChild(option);
129 });
130 const hideAllItems = () => [uploadButton, pagesRange, rotateAngle, pageInput, locationInput, cropTop, cropBottom, cropLeft, cropRight, topLabel, bottomLabel, leftLabel, rightLabel].forEach(item => item.style.display = 'none');
131 commandsList.onchange = (e) => {
132 const selectedIndex = e.target.selectedIndex;
133 promptLabel.textContent = commandsArray[selectedIndex].text;
134 hideAllItems();
135 commandsArray[selectedIndex].visibleItems.forEach(item => item.style.display = '');
136 }
137 commandsList.selectedIndex = 0;
138 promptLabel.textContent = commandsArray[0].text;
139 hideAllItems();
140 controlsContainer.id = 'controls-container';// set the id for CSS styling
141 element.insertBefore(controlsContainer, element.firstChild);
142
143 // Store elements globally for function access
144 window.pagesRange = pagesRange;
145 window.rotateAngle = rotateAngle;
146 window.pageInput = pageInput;
147 window.locationInput = locationInput;
148 window.cropTop = cropTop;
149 window.cropBottom = cropBottom;
150 window.cropLeft = cropLeft;
151 window.cropRight = cropRight;
152 window.newDoc = newDoc;
153 }
154}
155
156window.UIElements = UIElements; // Expose the class to the global scope
157
1/* Controls Container */
2.control-container {
3 align-items: center;
4 gap: 1px;
5 margin: 5px;
6 padding-bottom: 5px;
7 border-bottom: 1px solid #e0e0e0;
8 background-color: rgba(112, 198, 255, 0.2 /* Light blue background for contrast */);
9}
10
11.elements-style {
12 margin-right: 7px;
13 padding: 5px;
14 border: 1px solid #ccc;
15 border-radius: 4px;
16 font-size: 13px;
17 width: 70px;
18}
19
20.list-style {
21 margin-right: 7px;
22 padding: 5px;
23 border: 1px solid #ccc;
24 border-radius: 4px;
25 font-size: 13px;
26}
27
28/* General Button Styles */
29.btn {
30 background-color: #007bff;
31 margin: 0 10px;
32 padding: 5px 10px;
33 border: 1px solid #ccc;
34 border-radius: 4px;
35 cursor: pointer;
36 font-size: 14px;
37 transition: all 0.2s ease;
38 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
39 color: white;
40}
41
42.btn:hover {
43 background-color: #0056b3;
44 transform: translateY(-1px);
45 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
46}
47
48.btn:active {
49 transform: translateY(1px);
50 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
51}
52
53.btn:disabled {
54 background-color: #ccc;
55 cursor: not-allowed;
56 box-shadow: none;
57}
58
59/* Responsive Design */
60@media (max-width: 768px) {
61 .btn {
62 width: 100%;
63 margin: 5px 0;
64 }
65}
66
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales