Generate searchable PDFs from scanned documents or images using OCR (Optical Character Recognition). The scanned file that contains characters is converted into machine-readable searchable and selectable text as a PDF document.
This sample code includes Server SDK processing in JavaScript, with UI provided by WebViewer. If a viewer is not needed, or you want to work with a different language or framework for the Server SDK, please check out our Server SDK OCR Sample Code.
This demo allows you to:
To add OCR Module capability with Server SDK, and view with WebViewer:
Step 1: Follow get-started in JavaScript for Server SDK
Step 2: Follow get-started in your preferred web stack for WebViewer
Step 3: Download OCR Module
Step 4: Add the ES6 JavaScript sample code provided in this guide
Note: Only the first page is processed in this demo.
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 - GPT-4 Model - September 24, 2024
3// File: ocr-module/client/index.js
4
5import WebViewer from '@pdftron/webviewer';
6
7function initializeWebViewer() {
8
9 console.log('Initializing WebViewer...');
10 const viewerElement = document.getElementById('viewer');
11 if (!viewerElement) {
12 console.error('❌ Viewer element not found. Please ensure there is a div with id "viewer" in your HTML.');
13 return;
14 }
15
16 WebViewer({
17 path: '/lib',
18 initialDoc: UIElements.demoFilesPath + UIElements.ocrdemofiles['eng'],
19 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File.
20 enableMeasurement: false,
21 loadAsPDF: true,
22 licenseKey: 'YOUR_LICENSE_KEY',
23 }, viewerElement).then(instance => {
24
25 // Once the PDF document is loaded, send it to the server.
26 // The sent PDF document will be processed by the server,
27 // by extracting form fields JSON data when the user clicks the Detect Form Fields button.
28 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
29 if (!UIElements.loadingFromList) {
30 UIElements.loadedFromPicker = true;
31 UIElements.loadLabelText = ' Use file picker to load your own file';
32 }
33
34 // If next time we load from list, this will be set to true again.
35 UIElements.loadingFromList = false;
36
37 // Customize the main webviewer left panel after loading completes.
38 UIElements.customizeUI(instance, performOCR);
39 });
40 console.log('✅ WebViewer loaded successfully.');
41 }).catch((error) => {
42 console.error('Failed to initialize WebViewer:', error);
43 });
44}
45
46// Perform OCR by sending the current PDF page to the server.
47const performOCR = async (instance) => {
48 // Reset JSON data
49 UIElements.jsonData = null;
50 let resultText = '';
51
52 // Preparation of the PDF blob to be sent to the server.
53 const doc = instance.Core.documentViewer.getDocument();
54 const currentPage = instance.Core.documentViewer.getCurrentPage();
55 const xfdfString = await instance.Core.annotationManager.exportAnnotations(); // obtaining annotations in the loaded document
56 const data = await doc.getFileData({ xfdfString });
57 const arr = new Uint8Array(data);
58 const blob = new Blob([arr], { type: 'application/pdf' });
59 const formData = new FormData();
60 formData.append(doc.filename, blob, doc.filename);
61 // Send the PDF blob to the server for processing.
62 new Promise(function (resolve, reject) {
63 console.log('🚀 Sending PDF to server for processing...');
64 fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}¤tPage=${currentPage}&lang=${UIElements.selectedLanguage}`, {
65 method: 'POST',
66 body: formData,
67 }).then(function (response) {
68
69 if (response.status === 200) {
70 response.text().then(function (json) {
71 UIElements.jsonData = json;
72 const ocrResult = JSON.parse(UIElements.jsonData);
73
74 for (const page of ocrResult.Page) {
75 for (const para of page.Para) {
76 for (const line of para.Line) {
77 for (const word of line.Word) {
78 resultText += word.text + ' ';
79 }
80 resultText += '\n';
81 }
82 }
83 }
84 resolve();
85 })
86 } else {
87 const errorText = `❌ Server responded with status: ${response.status}`;
88 resultText = errorText + resultText;
89 console.error(resultText);
90 reject(new Error(`Server error: ${response.status}`));
91 }
92 }).catch(function (error) {
93 let errorText = '❌ Failed to connect to server: ' + error;
94 errorText += '\n📍 Attempted URL: http://localhost:5050/server/handler.js';
95 errorText += '\n🔍 This likely means the OCR server is not running on port 5050';
96 console.error(errorText);
97 resultText = errorText + resultText;
98 reject(error);
99 });
100 }).catch(function (error) {
101 const errorText = '❌ Error in PDF upload promise: ' + error;
102 console.error(errorText);
103 resultText = errorText + resultText;
104 }).finally(function () {
105 UIElements.ocrText.textContent = resultText;
106 UIElements.setLoading(instance, false);
107 });
108}
109
110
111function loadUIElementsScript() {
112 return new Promise((resolve, reject) => {
113 if (window.UIElements) {
114 console.log('UIElements already loaded');
115 resolve();
116 return;
117 }
118
119 const script = document.createElement('script');
120 script.src = '/showcase-demos/ocr-module/client/ui-elements.js';
121 script.onload = function () {
122 console.log('✅ UIElements script loaded successfully');
123 resolve();
124 };
125 script.onerror = function () {
126 console.error('Failed to load UIElements script');
127 reject(new Error('Failed to load ui-elements.js'));
128 };
129 document.head.appendChild(script);
130 });
131}
132
133// Load UIElements script first, then initialize WebViewer.
134loadUIElementsScript().then(() => {
135 initializeWebViewer();
136}).catch((error) => {
137 console.error('Failed to load UIElements:', error);
138});
1const { PDFNet } = require('@pdftron/pdfnet-node');
2const path = require('path');
3const fs = require('fs');
4
5// **Important**
6// You must get a license key from Apryse for the server to run.
7// A trial key can be obtained from:
8// https://docs.apryse.com/core/guides/get-started/trial-key
9const licenseKey = 'YOUR_LICENSE_KEY';
10const multer = require('multer');
11const { response } = require('express');
12const upload = multer();
13const serverFolder = 'server';
14const sentImages = 'sentImages';
15const serverHandler = `/${serverFolder}/handler.js`;
16
17module.exports = async (app) => {
18
19 async function initializeServer() {
20 const ocrOK = await PDFNet.OCRModule.isModuleAvailable();
21 if (!ocrOK) {
22 console.log('\nUnable to run OCR Demo: Apryse SDK OCR module not available.');
23 console.log('---------------------------------------------------------------');
24 console.log('The OCR module is an optional add-on, available for download');
25 console.log('at https://docs.apryse.com/core/guides/info/modules#ocr-module . If you have already downloaded this');
26 console.log('module, ensure that the SDK is able to find the required files');
27 console.log('using the PDFNet.addResourceSearchPath() function.\n');
28 }
29 else {
30 console.log('Apryse SDK OCR module is available.');
31 if (!fs.existsSync(sentImages))
32 fs.mkdirSync(sentImages);
33 }
34 }
35
36 // Handle POST request sent to '/server/handler.js'.
37 // This endpoint receives the currently loaded PDF file in the Apryse webviewer, converts the current page to an image,
38 // recognizes text from the image, then sends it back to the client as JSON data.
39 app.post(serverHandler, upload.any(), async (request, response) => {
40 const sentPdf = path.resolve(__dirname, `./${sentImages.split('/').pop()}/${request.query.filename}`);
41 const currentPage = request.query.currentPage ? parseInt(request.query.currentPage) : 1;
42 const draw = await PDFNet.PDFDraw.create(); // PDFDraw class is used to rasterize PDF pages.
43 const doc = await PDFNet.PDFDoc.createFromBuffer(request.files[0].buffer);
44 draw.setDPI(300);
45 const ocrPage = await (await doc.getPageIterator(currentPage)).current();
46 const imageName = sentPdf + `_page${currentPage}.png`;
47 await draw.export(ocrPage, imageName);
48
49 const opts = new PDFNet.OCRModule.OCROptions();
50 const useIRIS = await PDFNet.OCRModule.isIRISModuleAvailable();
51 if(useIRIS) opts.setOCREngine('iris');
52 let json = null;
53 response.header('Content-Type', 'application/json');
54 try {
55 const doc = await PDFNet.PDFDoc.create();
56 opts.addLang(request.query.lang || 'eng');
57 json = await PDFNet.OCRModule.getOCRJsonFromImage(doc, imageName, opts);
58 await fs.promises.unlink(imageName); // Delete the image after OCR
59 //json = await PDFNet.OCRModule.getOCRJsonFromPDF(doc, opts);
60 response.status(200).send(json);
61 } catch (e) {
62 response.status(500).send(`Error extracting JSON text from PDF file ${request.query.filename}`);
63 }
64 });
65
66 // Initialize PDFNet.
67 PDFNet.runWithoutCleanup(initializeServer, licenseKey).then(
68 function onFulfilled() {
69 response.status(200);
70 },
71 function onRejected(error) {
72 // Log error and close response.
73 console.error('Error initializing PDFNet', error);
74 response.status(503).send();
75 }
76 );
77};
1
2const express = require('express');
3const fs = require('fs');
4const bodyParser = require('body-parser');
5const open = (...args) => import('open').then(({ default: open }) => open(...args));
6const handler = require('./handler.js');
7const port = process.env.PORT || 5050;
8const app = express();
9const sentPdfs = 'sentPdfs';
10
11// CORS middleware to allow cross-origin requests from the playground.
12app.use((req, res, next) => {
13 res.header('Access-Control-Allow-Origin', '*');
14 res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
15 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
16
17 // Handle preflight OPTIONS requests.
18 if (req.method === 'OPTIONS') {
19 res.sendStatus(200);
20 } else {
21 next();
22 }
23});
24
25app.use(bodyParser.text());
26app.use('/client', express.static('../client')); // For statically serving 'client' folder at '/'.
27
28handler(app);
29
30// Run server.
31const server = app.listen(port, 'localhost', (err) => {
32 if (err) {
33 console.error(err);
34 } else {
35 console.info(`Server is listening at http://localhost:${port}`);
36
37 }
38});
39
40// Server shutdown and cleanup.
41function shutdown() {
42 console.log('Cleanup started...');
43
44 // Example: Close server
45 server.close(() => {
46 console.log('Server closed.');
47
48 // Removes sent PDFs folder.
49 if (fs.existsSync(sentPdfs))
50 fs.rmdirSync(sentPdfs, { recursive: true });
51
52 // If no async cleanup, exit directly.
53 process.exit(0);
54 });
55}
56
57// Handle shutdown signals.
58process.on('SIGINT', shutdown); // Ctrl+C
59process.on('SIGTERM', shutdown); // Kill command or Docker stop
60process.on('uncaughtException', (err) => {
61 console.error('Uncaught Exception:', err);
62 shutdown();
63});
1// Class with static UI elements and related functions for the OCR demo.
2
3class UIElements {
4 static languages = {
5 eng: 'English',
6 fra: 'French',
7 spa: 'Spanish',
8 ita: 'Italian',
9 deu: 'German',
10 rus: 'Russian',
11 };
12
13 static demoFilesPath = "https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/";
14 static ocrdemofiles = {
15 eng: 'scanned_sublease_agreement.pdf',
16 fra: 'scanned_sublease_agreement_french.pdf',
17 spa: 'scanned_sublease_agreement_spanish.pdf',
18 ita: 'scanned_sublease_agreement_italian.pdf',
19 deu: 'scanned_sublease_agreement_german.pdf',
20 rus: 'scanned_sublease_agreement_russian.pdf',
21 };
22
23 static loadingFromList = true; // Initially true because we load the first document from the list
24 static loadedFromPicker = false; // to differentiate if the document is loaded from the list or from file picker.
25 static jsonData = null;
26 static loadLabelText = ' Select language to load sample';
27 static ocrText = null; // Element to show OCR result text.
28 static selectedLanguage = 'eng'; // Default language
29 static fileLabel = null;
30 static filesArray = []; // Files array for gallery picker.
31
32 // The list of registered panels in the main webviewer.
33 static viewerPanels = null;
34
35 // The Tab panel, representing the webviewer left panel.
36 static tabPanel = {
37 handle: null,
38 dataElement: 'tabPanel'
39 };
40
41 // The custom form sub-panel to be registered.
42 static formPanel = {
43 handle: null,
44 dataElement: 'formPanel',
45 render: null,
46 };
47
48 // Function to set WebViewer 'loading' state.
49 static setLoading(inst, isLoading) {
50 if (isLoading) {
51 inst.UI.openElements(['loadingModal']);
52 } else {
53 inst.UI.closeElements(['loadingModal']);
54 }
55 }
56
57 static setButtonStyle(button) {
58 button.style.margin = '10px';
59 button.style.padding = '5px 10px';
60 button.style.border = '1px solid #ccc';
61 button.style.borderRadius = '4px';
62 button.style.cursor = 'pointer';
63 button.style.fontSize = '14px';
64 button.style.fontWeight = 'bold';
65 button.style.transition = 'all 0.2s ease';
66 button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
67 button.style.color = 'white';
68 button.style.backgroundColor = '#007bff';
69 }
70
71 // Customize the main webviewer left panel after the load completion.
72 static customizeUI(instance, performOCR) {
73 const { UI } = instance;
74
75 // Close the tab panel (if it's open) for refreshment.
76 UI.closeElements([this.tabPanel.dataElement]);
77
78 // Get the list of registered panels in the main webviewer.
79 this.viewerPanels = UI.getPanels();
80
81 // Find the Tab Panel to modify. The form sub-panel will be added to this Tab panel.
82 this.tabPanel.handle = this.viewerPanels.find((panel) => panel.dataElement === this.tabPanel.dataElement);
83
84 // Register the custom form sub-panel.
85 this.RegisterFormPanel(instance);
86
87 const languageLabel = document.createElement('h3');
88 languageLabel.textContent = "Select Language:";
89 this.formPanel.render.appendChild(languageLabel);
90
91 const languageSelect = document.createElement('select');
92 languageSelect.id = 'languageSelect';
93 for (const [langCode, langName] of Object.entries(this.languages)) {
94 const option = document.createElement('option');
95 option.value = langCode;
96 option.textContent = langName;
97 languageSelect.appendChild(option);
98 }
99 languageSelect.value = this.selectedLanguage;
100 languageSelect.onchange = () => {
101 this.selectedLanguage = languageSelect.value;
102 if (this.loadedFromPicker) // If the document is loaded from file picker once in the past, do not load using list again.
103 return;
104 this.loadingFromList = true;
105 instance.UI.loadDocument(this.demoFilesPath + this.ocrdemofiles[this.selectedLanguage]);
106 };
107 this.formPanel.render.appendChild(languageSelect);
108
109 const loadLabel = document.createElement('label');
110 loadLabel.textContent = this.loadLabelText;
111 this.formPanel.render.appendChild(loadLabel);
112
113 this.fileLabel = document.createElement('h4');
114 this.fileLabel.textContent = `Current file: ${instance.Core.documentViewer.getDocument().getFilename()}`;
115 this.formPanel.render.appendChild(this.fileLabel);
116
117 // OCR button
118 const recognizeButton = document.createElement('button');
119 recognizeButton.textContent = 'Recognize Text';
120 recognizeButton.id = 'recognizeTextButton';
121 this.setButtonStyle(recognizeButton);
122
123 recognizeButton.onclick = async () => {
124 this.ocrText.textContent = "Performing OCR, please wait...";
125 this.setLoading(instance, true);
126 this.formPanel.render.style.cursor = "wait";
127 await performOCR(instance); // Perform text recognition.
128 this.formPanel.render.style.cursor = "default";
129 }
130 this.formPanel.render.appendChild(recognizeButton);
131
132 let jsonDiv = document.createElement('div');
133 jsonDiv.id = 'json';
134 const jsonTitle = document.createElement('h3');
135 jsonTitle.textContent = "Exportable text";
136 jsonDiv.appendChild(jsonTitle);
137 jsonDiv.appendChild(document.createElement('p'));
138
139 this.jsonData = null;
140 this.ocrText = document.createElement('textarea');
141 this.ocrText.style.width = "350px";
142 this.ocrText.style.height = "350px";
143 this.ocrText.style.whiteSpace = "nowrap";
144 this.ocrText.readOnly = true;
145 this.ocrText.style.width = "350px";
146 this.ocrText.style.height = "350px";
147 this.ocrText.style.whiteSpace = "nowrap";
148 this.ocrText.readOnly = true;
149 this.ocrText.textContent = "Try applying OCR to this document\nby pressing the button above!";
150 jsonDiv.appendChild(this.ocrText);
151
152 this.formPanel.render.appendChild(jsonDiv);
153
154 // Add the new custom form sub-panel to list of sub-panels under the Tab Panel.
155 this.formPanel.handle = { render: this.formPanel.dataElement };
156 this.tabPanel.handle.panelsList = [this.formPanel.handle, ...this.tabPanel.handle.panelsList];
157
158 UI.openElements([this.tabPanel.dataElement]);
159 UI.setPanelWidth(this.tabPanel.dataElement, 400);
160 };
161
162 // Register the custom form sub-panel.
163 static RegisterFormPanel(instance) {
164 this.formPanel.render = this.createFormPanelElements(instance);
165 instance.UI.addPanel({
166 dataElement: this.formPanel.dataElement,
167 location: 'left',
168 icon: '<svg width="18px" height="18px" viewBox="0 0 24 24" id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#080808;}</style></defs><title>form</title><path class="cls-1" d="M21,.5H3a2,2,0,0,0-2,2V22a2,2,0,0,0,2,2H21a2,2,0,0,0,2-2V2.5A2,2,0,0,0,21,.5Zm0,2v2H3v-2ZM3,22V6.5H21V22Z"/><path class="cls-1" d="M12.5,4H20a.5.5,0,0,0,0-1H12.5a.5.5,0,0,0,0,1Z"/><path class="cls-1" d="M4.5,4a.43.43,0,0,0,.19,0,.35.35,0,0,0,.16-.11A.47.47,0,0,0,5,3.5a.43.43,0,0,0,0-.19.36.36,0,0,0-.11-.16.5.5,0,0,0-.7,0A.35.35,0,0,0,4,3.31.43.43,0,0,0,4,3.5a.51.51,0,0,0,.5.5Z"/><path class="cls-1" d="M5.65,3.85A.36.36,0,0,0,5.81,4,.44.44,0,0,0,6,4a.47.47,0,0,0,.35-.15.36.36,0,0,0,.11-.16.6.6,0,0,0,0-.19.51.51,0,0,0-.15-.35A.49.49,0,0,0,5.81,3a.36.36,0,0,0-.16.11.47.47,0,0,0-.15.35.4.4,0,0,0,0,.19A.35.35,0,0,0,5.65,3.85Z"/><path class="cls-1" d="M8,8H4.5a1,1,0,0,0,0,2H8A1,1,0,0,0,8,8Z"/><path class="cls-1" d="M8,11.67H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,15.33H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M8,19H4.5a1,1,0,0,0,0,2H8a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,8H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,11.67H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,15.33H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M14,19H10.5a1,1,0,0,0,0,2H14a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,8h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,11.67h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,15.33h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/><path class="cls-1" d="M19.5,19h-3a1,1,0,0,0,0,2h3a1,1,0,0,0,0-2Z"/></svg>',
169 title: 'Form',
170 render: () => this.formPanel.render,
171 });
172 };
173
174 // Create the form panel elements.
175 static createFormPanelElements() {
176 let panelDiv = document.createElement('div');
177 panelDiv.id = 'form';
178 let paragraph = document.createTextNode('A demo of Apryse SDK OCR. Take a PDF and find text. (Note: This demo only processes the current page).');
179 panelDiv.appendChild(paragraph);
180
181 let dividerDiv = document.createElement('div');
182 dividerDiv.style.borderTop = '1px solid #ccc';
183 dividerDiv.style.margin = '10px 0';
184 panelDiv.appendChild(dividerDiv);
185
186 panelDiv.appendChild(document.createElement('p'));
187 return panelDiv;
188 }
189}
190
191
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales