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
7const licenseKey = 'YOUR_WEBVIEWER_LICENSE_KEY';
8
9function initializeWebViewer() {
10
11 console.log('Initializing WebViewer...');
12 const viewerElement = document.getElementById('viewer');
13 if (!viewerElement) {
14 console.error('❌ Viewer element not found. Please ensure there is a div with id "viewer" in your HTML.');
15 return;
16 }
17
18 WebViewer({
19 path: '/lib',
20 initialDoc: UIElements.demoFilesPath + UIElements.ocrdemofiles['eng'],
21 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
22 enableMeasurement: false,
23 loadAsPDF: true,
24 licenseKey: licenseKey,
25 }, viewerElement).then(instance => {
26
27 // Once the PDF document is loaded, send it to the server.
28 // The sent PDF document will be processed by the server,
29 // by extracting form fields JSON data when the user clicks the "Detect Form Fields" button.
30 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
31 if (!UIElements.loadingFromList) {
32 UIElements.loadedFromPicker = true;
33 UIElements.loadLabelText = ' Use file picker to load your own file';
34 }
35
36 // if next time we load from list, this will be set to true again
37 UIElements.loadingFromList = false;
38
39 // Customize the main webviewer left panel after loading completes
40 UIElements.customizeUI(instance, performOCR);
41 });
42 console.log('✅ WebViewer loaded successfully.');
43 }).catch((error) => {
44 console.error('Failed to initialize WebViewer:', error);
45 });
46}
47
48// Perform OCR by sending the current PDF page to the server
49const performOCR = async (instance) => {
50 // Reset JSON data
51 UIElements.jsonData = null;
52 let resultText = '';
53
54 // Preparation of the PDF blob to be sent to the server
55 const doc = instance.Core.documentViewer.getDocument();
56 const currentPage = instance.Core.documentViewer.getCurrentPage();
57 const xfdfString = await instance.Core.annotationManager.exportAnnotations(); // obtaining annotations in the loaded document
58 const data = await doc.getFileData({ xfdfString });
59 const arr = new Uint8Array(data);
60 const blob = new Blob([arr], { type: 'application/pdf' });
61 const formData = new FormData();
62 formData.append(doc.filename, blob, doc.filename);
63 // Send the PDF blob to the server for processing
64 new Promise(function (resolve, reject) {
65 console.log('🚀 Sending PDF to server for processing...');
66 fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}¤tPage=${currentPage}&lang=${UIElements.selectedLanguage}`, {
67 method: 'POST',
68 body: formData,
69 }).then(function (response) {
70
71 if (response.status === 200) {
72 response.text().then(function (json) {
73 UIElements.jsonData = json;
74 const ocrResult = JSON.parse(UIElements.jsonData);
75
76 for (const page of ocrResult.Page) {
77 for (const para of page.Para) {
78 for (const line of para.Line) {
79 for (const word of line.Word) {
80 resultText += word.text + ' ';
81 }
82 resultText += '\n';
83 }
84 }
85 }
86 resolve();
87 })
88 } else {
89 const errorText = `❌ Server responded with status: ${response.status}`;
90 resultText = errorText + resultText;
91 console.error(resultText);
92 reject(new Error(`Server error: ${response.status}`));
93 }
94 }).catch(function (error) {
95 let errorText = '❌ Failed to connect to server: ' + error;
96 errorText += '\n📍 Attempted URL: http://localhost:5050/server/handler.js';
97 errorText += '\n🔍 This likely means the OCR server is not running on port 5050';
98 console.error(errorText);
99 resultText = errorText + resultText;
100 reject(error);
101 });
102 }).catch(function (error) {
103 const errorText = '❌ Error in PDF upload promise: ' + error;
104 console.error(errorText);
105 resultText = errorText + resultText;
106 }).finally(function () {
107 UIElements.ocrText.textContent = resultText;
108 UIElements.setLoading(instance, false);
109 });
110}
111
112
113function loadUIElementsScript() {
114 return new Promise((resolve, reject) => {
115 if (window.UIElements) {
116 console.log('UIElements already loaded');
117 resolve();
118 return;
119 }
120
121 const script = document.createElement('script');
122 script.src = '/showcase-demos/ocr-module/client/ui-elements.js';
123 script.onload = function () {
124 console.log('✅ UIElements script loaded successfully');
125 resolve();
126 };
127 script.onerror = function () {
128 console.error('Failed to load UIElements script');
129 reject(new Error('Failed to load ui-elements.js'));
130 };
131 document.head.appendChild(script);
132 });
133}
134
135// Load UIElements script first, then initialize WebViewer
136loadUIElementsScript().then(() => {
137 initializeWebViewer();
138}).catch((error) => {
139 console.error('Failed to load UIElements:', error);
140});
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_SERVER_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
Did you find this helpful?
Trial setup questions?
Ask experts on DiscordNeed other help?
Contact SupportPricing or product questions?
Contact Sales