OCR - Showcase Demo

Requirements
View Demo

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, with the addition of showing the resulting file in WebViewer. If a viewer is not needed, please check out our Server SDK OCR Sample Code

Note: Only the first page is processed in this demo.

This demo allows you to:

  • Choose your own PDF or image file
  • Select default documents in various languages: English, French, Spanish, Italian, German, Russian
  • Convert into characters that are selectable and searchable
  • Extract the characters as string

Implementation steps

To add OCR Module capability with Server SDK, and view with WebViewer:

Step 1: Get started in Server SDK language or framework of your choice
Step 2: Follow get-started in your preferred web stack for WebViewer
Step 3: Download OCR Module
Step 4: Add the sample code provided in this guide

To use this feature in production, your license key will need theĀ OCR Package. Trial keys already include all packages.

1// ES6 Compliant Syntax
2// GitHub Copilot v1.0 - GPT-4 Model - September 24, 2024
3// File: ocr-module/client/index.js
4
5// **Important**
6// You must get a license key from Apryse to run the WebViewer Server SDK.
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 viewerElement = document.getElementById('viewer');
11
12const languages = {
13 eng: 'English',
14 fra: 'French',
15 spa: 'Spanish',
16 ita: 'Italian',
17 deu: 'German',
18 rus: 'Russian',
19};
20
21const demoFilesPath = "https://apryse.s3.us-west-1.amazonaws.com/public/files/samples/";
22const ocrdemofiles = {
23 eng: 'scanned_sublease_agreement.pdf',
24 fra: 'scanned_sublease_agreement_french.pdf',
25 spa: 'scanned_sublease_agreement_spanish.pdf',
26 ita: 'scanned_sublease_agreement_italian.pdf',
27 deu: 'scanned_sublease_agreement_german.pdf',
28 rus: 'scanned_sublease_agreement_russian.pdf',
29};
30
31let loadingFromList = true; // initially true because we load the first document from the list
32let loadedFromPicker = false; // to differentiate if the document is loaded from the list or from file picker
33let jsonData = null;
34let loadLabelText = ' Select language to load sample';
35let ocrText = null; // element to show OCR result text
36let selectedLanguage = 'eng'; // Default language
37let fileLabel = null;
38
39// The list of registered panels in the main webviewer
40let viewerPanels = null;
41
42// The tab panel, representing the webviewer left panel
43const tabPanel = {
44 handle: null,
45 dataElement: 'tabPanel'
46};
47
48// The custom form sub-panel to be registered
49const formPanel = {
50 handle: null,
51 dataElement: 'formPanel',
52 render: null,
53};
54
55// Function to set WebViewer 'loading' state
56function setLoading(inst, isLoading) {
57 if (isLoading) {
58 inst.UI.openElements(['loadingModal']);
59 } else {
60 inst.UI.closeElements(['loadingModal']);
61 }
62}
63
64function setButtonStyle(button) {
65 button.style.margin = '10px';
66 button.style.padding = '5px 10px';
67 button.style.border = '1px solid #ccc';
68 button.style.borderRadius = '4px';
69 button.style.cursor = 'pointer';
70 button.style.fontSize = '14px';
71 button.style.fontWeight = 'bold';
72 button.style.transition = 'all 0.2s ease';
73 button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
74 button.style.color = 'white';
75 button.style.backgroundColor = '#007bff';
76}
77
78// Customize the main webviewer left panel after the load completion
79const customizeUI = (instance) => {
80 const { UI } = instance;
81
82 // close the tab panel (if it's open) for refreshment.
83 UI.closeElements([tabPanel.dataElement]);
84
85 // Get the list of registered panels in the main webviewer
86 viewerPanels = UI.getPanels();
87
88 // Find the Tab Panel to modify. The form sub-panel will be added to this Tab panel.
89 tabPanel.handle = viewerPanels.find((panel) => panel.dataElement === tabPanel.dataElement);
90
91 // Register the custom form sub-panel
92 RegisterFormPanel(instance);
93
94 const languageLabel = document.createElement('h3');
95 languageLabel.textContent = "Select Language:";
96 formPanel.render.appendChild(languageLabel);
97
98 const languageSelect = document.createElement('select');
99 languageSelect.id = 'languageSelect';
100 for (const [langCode, langName] of Object.entries(languages)) {
101 const option = document.createElement('option');
102 option.value = langCode;
103 option.textContent = langName;
104 languageSelect.appendChild(option);
105 }
106 languageSelect.value = selectedLanguage;
107 languageSelect.onchange = () => {
108 selectedLanguage = languageSelect.value;
109 if(loadedFromPicker) // if the document is loaded from file picker once in the past, do not load using list again
110 return;
111 loadingFromList = true;
112 instance.UI.loadDocument(demoFilesPath + ocrdemofiles[selectedLanguage]);
113 };
114 formPanel.render.appendChild(languageSelect);
115
116 const loadLabel = document.createElement('label');
117 loadLabel.textContent = loadLabelText;
118 formPanel.render.appendChild(loadLabel);
119
120 fileLabel = document.createElement('h4');
121 fileLabel.textContent = `Current file: ${instance.Core.documentViewer.getDocument().getFilename()}`;
122 formPanel.render.appendChild(fileLabel);
123
124 // OCR button
125 const recognizeButton = document.createElement('button');
126 recognizeButton.textContent = 'Recognize Text';
127 recognizeButton.id = 'recognizeTextButton';
128 setButtonStyle(recognizeButton);
129
130 recognizeButton.onclick = async () => {
131 ocrText.textContent = "Performing OCR, please wait...";
132 setLoading(instance, true);
133 formPanel.render.style.cursor = "wait";
134 await performOCR(instance); // Perform text recognition
135 formPanel.render.style.cursor = "default";
136 }
137 formPanel.render.appendChild(recognizeButton);
138
139 let jsonDiv = document.createElement('div');
140 jsonDiv.id = 'json';
141 const jsonTitle = document.createElement('h3');
142 jsonTitle.textContent = "Exportable text";
143 jsonDiv.appendChild(jsonTitle);
144 jsonDiv.appendChild(document.createElement('p'));
145
146 jsonData = null;
147 ocrText = document.createElement('textarea');
148 ocrText.style.width = "350px";
149 ocrText.style.height = "350px";
150 ocrText.style.whiteSpace = "nowrap";
151 ocrText.readOnly = true;
152 ocrText.style.width = "350px";
153 ocrText.style.height = "350px";
154 ocrText.style.whiteSpace = "nowrap";
155 ocrText.readOnly = true;
156 ocrText.textContent = "Try applying OCR to this document\nby pressing the button above!";
157 jsonDiv.appendChild(ocrText);
158 const openDialogButton = document.createElement('button');
159 setButtonStyle(openDialogButton);
160
161 openDialogButton.textContent = "Open in Dialog";
162 openDialogButton.onclick = () => {
163 if (jsonData) {
164 openJsonDataDialog(ocrText.textContent);
165 }
166 };
167 jsonDiv.appendChild(openDialogButton);
168 formPanel.render.appendChild(jsonDiv);
169
170 // Add the new custom form sub-panel to list of sub-panels under the Tab Panel
171 formPanel.handle = { render: formPanel.dataElement };
172 tabPanel.handle.panelsList = [formPanel.handle, ...tabPanel.handle.panelsList];
173
174 UI.openElements([tabPanel.dataElement]);
175 UI.setPanelWidth(tabPanel.dataElement, 400);
176};
177
178// Register the custom form sub-panel
179const RegisterFormPanel = (instance) => {
180 formPanel.render = createFormPanelElements(instance);
181 instance.UI.addPanel({
182 dataElement: formPanel.dataElement,
183 location: 'left',
184 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>',
185 title: 'Form',
186 render: () => formPanel.render,
187 });
188};
189
190// Create the form panel elements.
191const createFormPanelElements = (instance) => {
192 let panelDiv = document.createElement('div');
193 panelDiv.id = 'form';
194 let paragraph = document.createTextNode('A demo of Apryse SDK OCR. Take a PDF and find text. (Note: This demo only processes the current page).');
195 panelDiv.appendChild(paragraph);
196
197 let dividerDiv = document.createElement('div');
198 dividerDiv.style.borderTop = '1px solid #ccc';
199 dividerDiv.style.margin = '10px 0';
200 panelDiv.appendChild(dividerDiv);
201
202 panelDiv.appendChild(document.createElement('p'));
203 return panelDiv;
204};
205
206// Open JSON data in a viewer with button
207// This viewer will be viewed on top of the PDF document viewer.
208const openJsonDataDialog = (jsonText) => {
209 const dialog = document.createElement('dialog');
210 dialog.style.width = "80%";
211 dialog.style.height = "80%";
212 dialog.style.padding = "10px";
213 dialog.style.border = "1px solid #ccc";
214 dialog.style.borderRadius = "8px";
215 dialog.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.1)";
216 dialog.style.display = "flex";
217 dialog.style.flexDirection = "column";
218 const content = document.createElement('pre');
219 content.style.flex = "1";
220 content.style.overflow = "auto";
221 content.textContent = jsonText;
222 dialog.appendChild(content);
223 const closeButton = document.createElement('button');
224 closeButton.textContent = "Close";
225 setButtonStyle(closeButton);
226 closeButton.onclick = () => {dialog.close(); dialog.remove(); }; //Not only close but also remove from DOM
227 dialog.appendChild(closeButton);
228 formPanel.render.appendChild(dialog);
229 dialog.showModal();
230}
231
232// Perform OCR by sending the current PDF page to the server
233const performOCR = async (instance) => {
234 // Reset JSON data
235 jsonData = null;
236 let resultText = '';
237
238 // Preparation of the PDF blob to be sent to the server
239 const doc = instance.Core.documentViewer.getDocument();
240 const currentPage = instance.Core.documentViewer.getCurrentPage();
241 const xfdfString = await instance.Core.annotationManager.exportAnnotations(); // obtaining annotations in the loaded document
242 const data = await doc.getFileData({ xfdfString });
243 const arr = new Uint8Array(data);
244 const blob = new Blob([arr], { type: 'application/pdf' });
245 const formData = new FormData();
246 formData.append(doc.filename, blob, doc.filename);
247 // Send the PDF blob to the server for processing
248 new Promise(function (resolve, reject) {
249 console.log('šŸš€ Sending PDF to server for processing...');
250 fetch(`http://localhost:5050/server/handler.js?filename=${doc.filename}&currentPage=${currentPage}&lang=${selectedLanguage}`, {
251 method: 'POST',
252 body: formData,
253 }).then(function (response) {
254
255 if (response.status === 200) {
256 response.text().then(function (json) {
257 jsonData = json;
258 const ocrResult = JSON.parse(jsonData);
259
260 for (const page of ocrResult.Page) {
261 for (const para of page.Para) {
262 for (const line of para.Line) {
263 for (const word of line.Word) {
264 resultText += word.text + ' ';
265 }
266 resultText += '\n';
267 }
268 }
269 }
270 resolve();
271 })
272 } else {
273 const errorText = `āŒ Server responded with status: ${response.status}`;
274 resultText = errorText + resultText;
275 console.error(resultText);
276 reject(new Error(`Server error: ${response.status}`));
277 }
278 }).catch(function (error) {
279 let errorText = 'āŒ Failed to connect to server: ' + error;
280 errorText += '\nšŸ“ Attempted URL: http://localhost:5050/server/handler.js';
281 errorText += '\nšŸ” This likely means the OCR server is not running on port 5050';
282 console.error(errorText);
283 resultText = errorText + resultText;
284 reject(error);
285 });
286 }).catch(function (error) {
287 const errorText = 'āŒ Error in PDF upload promise: ' + error;
288 console.error(errorText);
289 resultText = errorText + resultText;
290 }).finally(function (){
291 ocrText.textContent = resultText;
292 setLoading(instance, false);
293 });
294}
295
296WebViewer({
297 path: '/lib',
298 initialDoc: demoFilesPath + ocrdemofiles['eng'],
299 enableFilePicker: true, // Enable file picker to open files. In WebViewer -> menu icon -> Open File
300 enableMeasurement: false,
301 loadAsPDF: true,
302 licenseKey: licenseKey,
303}, viewerElement).then(instance => {
304
305 // Once the PDF document is loaded, send it to the server.
306 // The sent PDF document will be processed by the server,
307 // by extracting form fields JSON data when the user clicks the "Detect Form Fields" button.
308 instance.Core.documentViewer.addEventListener('documentLoaded', async () => {
309 if(!loadingFromList) {
310 loadedFromPicker = true;
311 loadLabelText = ' Use file picker to load your own file';
312 }
313 loadingFromList = false; // if next time we load from list, this will be set to true again
314 // Customize the main webviewer left panel after loading completes
315 customizeUI(instance);
316 });
317 console.log('āœ… WebViewer loaded successfully.');
318}).catch((error) => {
319 console.error('āŒ Failed to initialize WebViewer:', error);
320});
321

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales