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.
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
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
9const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
10// Global variables
11const element = document.getElementById('viewer');
12let instance = null;
13
14// Initialize WebViewer
15function initializeWebViewer() {
16 if (!element) {
17 console.error('Viewer div not found.');
18 return;
19 }
20 // Initialize WebViewer
21 WebViewer({
22 path: '/lib',
23 licenseKey: licenseKey,
24 initialDoc: 'https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/WebviewerDemoDoc.pdf',
25 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
26 }, element).then((inst) => {
27 instance = inst;
28 const { documentViewer} = instance.Core;
29 documentViewer.addEventListener('documentLoaded', () => {
30 const doc = documentViewer.getDocument();
31 console.log(`Document loaded with ${doc.getPageCount()} pages.`);
32 createUIElements().then(() => {
33 // Now initialize rotateAngle options values (after UI elements are created)
34 if (window.rotateAngle) {
35 window.rotateAngle.children[0].value = instance.Core.PageRotation.E_90;
36 window.rotateAngle.children[1].value = instance.Core.PageRotation.E_180;
37 window.rotateAngle.children[2].value = instance.Core.PageRotation.E_270;
38 } else {
39 console.error('rotateAngle element not found after UI creation');
40 }
41 // activate thumbnails panel
42 instance.UI.openElements(['tabPanel']);
43 instance.UI.setActiveTabInPanel({ tabPanel: 'tabPanel', tabName: 'thumbnailsPanel' });
44 });
45 });
46 });
47} // Close initializeWebViewer function
48
49// validate page number input
50const validPageNumber = (pageNum, doc) => {
51 if (pageNum > doc.getPageCount() || pageNum < 1) {
52 alert('Please enter a valid page number.');
53 return false;
54 }
55 return true;
56}
57
58// convert a string like "1,2,5-7" to an array of numbers [1,2,5,6,7]
59const parseStringToNumberArray = (val, maxVal) => {
60 // check for valid number, comma, or dash pattern
61 if (!/^[0-9]+([,-][0-9]+)*$/.test(val)) {
62 return [];
63 }
64 const numbers = [];
65 val.split(',').forEach((group) => {
66 const range = group.split('-');
67 const start = Number(range[0]);
68 let end = Number(range.length === 1 ? range[0] : range[1]);
69 if (end > maxVal) {
70 end = maxVal;
71 }
72 for (let i = start; i <= end; i++) {
73 numbers.push(i);
74 }
75 });
76 return numbers;
77}
78
79// delete specified pages from document
80const deletePages = () => {
81 const doc = instance.Core.documentViewer.getDocument();
82 const pageNumbers = pagesRange.value;
83 const result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
84 if (!validPageNumber(pageNumbers, doc)) return;
85 if (result.length === doc.getPageCount()) {
86 alert('Cannot delete all pages in the document.');
87 return;
88 }
89 if (result.length > 0) {
90 doc.removePages(result);
91 } else {
92 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
93 }
94}
95
96// rotate specified pages in document
97const rotatePages = () => {
98 const pageNumbers = pagesRange.value;
99 const rotation = Number(rotateAngle.value);
100 const doc = instance.Core.documentViewer.getDocument();
101 if (!validPageNumber(pageNumbers, doc)) return;
102 let result = [];
103 if (pageNumbers) {
104 result = parseStringToNumberArray(pageNumbers, doc.getPageCount());
105 } else {
106 // No page numbers provided, rotating all pages
107 for (let i = 1; i <= doc.getPageCount(); i++) {
108 result.push(i);
109 }
110 }
111 if (result.length > 0) {
112 doc.rotatePages(result, rotation);
113 } else {
114 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
115 }
116}
117
118// move a page to a new location in the document
119const movePage = () => {
120 const doc = instance.Core.documentViewer.getDocument();
121 const pageOne = Number(pageInput.value);
122 let pageTwo = Number(locationInput.value);
123 if (!validPageNumber(pageOne, doc) || !validPageNumber(pageTwo, doc)) return;
124 if (pageOne < pageTwo) pageTwo++; // adjust for removing the "from" page
125 doc.movePages([pageOne], pageTwo);
126}
127
128// insert a blank page at the specified location
129const insertBlankPage = () => {
130 let pageNumber = Number(pageInput.value);
131 const doc = instance.Core.documentViewer.getDocument();
132 if (pageNumber === -1) { // append to end
133 pageNumber = doc.getPageCount() + 1;
134 } else if (!validPageNumber(pageNumber, doc)) return;
135 const pageSize = doc.getPageInfo(1);
136 doc.insertBlankPages([pageNumber], pageSize.width, pageSize.height);
137}
138
139// extract specified pages from document and save as a new file
140const extractPages = async () => {
141 const pageNumbers = pagesRange.value;
142 const doc = instance.Core.documentViewer.getDocument();
143 const pageCount = doc.getPageCount();
144 if (!validPageNumber(pageNumbers, doc)) return;
145 let pagesToExtract = [];
146 if (pageNumbers) {
147 pagesToExtract = parseStringToNumberArray(pageNumbers, pageCount);
148 } else {
149 for (let i = 1; i <= pageCount; i++) {
150 pagesToExtract.push(i);
151 }
152 }
153 if (pagesToExtract.length > 0) {
154 const annotList = instance.Core.annotationManager.getAnnotationsList().filter((annot) => pagesToExtract.indexOf(annot.PageNumber) > -1);
155 const xfdfString = await instance.Core.annotationManager.exportAnnotations({ annotationList: annotList });
156 doc.extractPages(pagesToExtract, xfdfString).then((data) => {
157 const arr = new Uint8Array(data);
158 const blob = new Blob([arr], { type: 'application/pdf' });
159 saveAs(blob, 'extracted.pdf');
160 });
161 } else {
162 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
163 }
164}
165
166// crop the specified page by the specified pixel amounts from 4 sides
167const cropPage = () => {
168 const pageNumber = Number(pageInput.value);
169 const doc = instance.Core.documentViewer.getDocument();
170 if (!validPageNumber(pageNumber, doc)) return;
171 const top = Number(cropTop.value);
172 const bottom = Number(cropBottom.value);
173 const left = Number(cropLeft.value);
174 const right = Number(cropRight.value);
175 doc.cropPages([pageNumber], top, bottom, left, right);
176}
177
178// merge an uploaded document into the current document at the specified location
179const mergeDocuments = () => {
180 if (!window.newDoc) {
181 alert('Please upload a document to merge first');
182 return;
183 }
184 const pageNumbers = pagesRange.value;
185 let pagesToInsert = [];
186 if (pageNumbers) {
187 pagesToInsert = parseStringToNumberArray(pageNumbers, window.newDoc.getPageCount());
188 } else {
189 for (let i = 1; i <= window.newDoc.getPageCount(); i++) {
190 pagesToInsert.push(i);
191 }
192 }
193 const locationToInsert = Number(locationInput.value);
194 if (pagesToInsert.length > 0) {
195 instance.Core.documentViewer.getDocument().insertPages(window.newDoc, pagesToInsert, locationToInsert);
196 } else {
197 alert('Please enter a comma or dash separated list of valid page numbers ex 1,2,3-5 for the first 5 pages');
198 }
199}
200
201// Expose functions to global scope for UI elements
202window.deletePages = deletePages;
203window.rotatePages = rotatePages;
204window.movePage = movePage;
205window.insertBlankPage = insertBlankPage;
206window.extractPages = extractPages;
207window.cropPage = cropPage;
208window.mergeDocuments = mergeDocuments;
209window.parseStringToNumberArray = parseStringToNumberArray;
210window.validPageNumber = validPageNumber;
211
212// UI Elements
213// Function to create and initialize UI elements
214function createUIElements() {
215 return new Promise((resolve) => {
216 // Create a container for all controls (label, dropdown, and buttons)
217 // Dynamically load ui-elements.js if not already loaded
218 if (typeof UIElements === 'undefined') {
219 const script = document.createElement('script');
220 script.src = '/showcase-demos/pdf-page-manipulation-api/ui-elements.js';
221 script.onload = () => {
222 console.log('ui-elements.js loaded successfully');
223 UIElements.init(instance);
224 resolve(); // Resolve when UI elements are created
225 };
226 script.onerror = () => {
227 console.error('Failed to load ui-elements.js');
228 resolve(); // Resolve even on error to prevent hanging
229 };
230 document.head.appendChild(script);
231 } else {
232 console.log('UIElements already available, initializing...');
233 UIElements.init(instance);
234 resolve(); // Resolve immediately if UIElements is already loaded
235 }
236 });
237}
238
239// Initialize WebViewer when the window loads
240initializeWebViewer();
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