Build a Cross-Platform Document Scanning App with Electron

Mar 20, 2019

Last updated Mar 20, 2017, for an updated version, please visit Github

Electron is a framework for building cross-platform desktop Apps with HTML, JavaScript, and CSS. Dynamic Web TWAIN is also a cross-platform JavaScript library for document scanning, it’s pretty easy to implement a document scan app with Electron and Dynamic Web TWAIN that runs on Windows, Linux and macOS.

Installation

Building a Desktop App with Electron

  • Reference: Electron documentation
  • Electron app structureAn Electron app is structured as follows:app/
    — package.json (metadata)
    — main.js (code)
    —index.html (UI)

package.json is to specify the metadata. It is the same as for package.json for Node.js module. Here is mine:

{
  "name": "docscanner",
  "version": "1.0.0",
  "description": "Cross-platform document scanning application for Windows, Linux and macOS",
  "main": "main.js",
  "scripts": {
  "start": "electron main.js"
  },
  "repository": {
  "type": "git",
  "url": "git+https://github.com/yushulx/electron-document-scan.git"
  },
  "keywords": [
  "Dynamsoft",
  "document scan",
  "web twain",
  "SDK"
  ],
  "author": "Dynamsoft",
  "homepage": "https://www.dynamsoft.com/web-twain/overview/",
  "devDependencies": {
  "electron-prebuilt": "^1.6.1"
  }
}

Specify the main field with the JavaScript file that will be loaded by Electron. In addition, you need to create index.html for rendering UI with HTML and CSS. When reading the Eelctron API reference, you may see main process and renderer process.

Electron main process The main process is the entry point that runs main.js. It creates renderer process and manages native elements. The full Node API is built in.

Electron render process The render process is a browser window that runs index.html. Electron enables developers to use Node.js APIs in Web pages.

Create a Document Scan App using Dynamic Web TWAIN

According to Electron documentation, we just need to put all works in index.html and use main.js to load it.

index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>Document Scanner</title>
  <script type="text/javascript" src="http://www.dynamsoft.com/library/dwt/dynamsoft.webtwain.min.js"></script>
  <style>
  h1 {
  font-size: 2em;
  font-weight: bold;
  color: #777777;
  text-align: center
  }
   
  table {
  margin: auto;
  }
  </style>
</head>
 
<body>
  <h1>
  Document Scanner
  </h1>
  We are using node
  <script>
  document.write(process.versions.node)
  </script>, Chrome
  <script>
  document.write(process.versions.chrome)
  </script>, and Electron
  <script>
  document.write(process.versions.electron)
  </script>.
  <table>
  <tr>
  <td>
  <!-- dwtcontrolContainer is the default div id for Dynamic Web TWAIN control.
  If you need to rename the id, you should also change the id in dynamsoft.webtwain.config.js accordingly. -->
  <div id="dwtcontrolContainer"></div>
  </td>
  </tr>
 
  <tr>
  <td>
  <input type="button" value="Scan" onclick="scanImage();" />
  <input type="button" value="Load" onclick="loadImage();" />
  <input type="button" value="Save" onclick="saveImage();" />
  </td>
  </tr>
  </table>
 
  <script type="text/javascript">
  var console = window['console'] ? window['console'] : {
  'log': function() {}
  };
  Dynamsoft.WebTwainEnv.RegisterEvent('OnWebTwainReady', Dynamsoft_OnReady); // Register OnWebTwainReady event. This event fires as soon as Dynamic Web TWAIN is initialized and ready to be used
 
  var DWObject;
 
  function Dynamsoft_OnReady() {
  DWObject = Dynamsoft.WebTwainEnv.GetWebTwain('dwtcontrolContainer'); // Get the Dynamic Web TWAIN object that is embeded in the div with id 'dwtcontrolContainer'
  if (DWObject) {
  DWObject.RegisterEvent('OnPostAllTransfers', SaveWithFileDialog);
  }
  }
 
  function scanImage() {
  if (DWObject) {
  var bSelected = DWObject.SelectSource();
 
  if (bSelected) {
  var OnAcquireImageSuccess, OnAcquireImageFailure;
  OnAcquireImageSuccess = OnAcquireImageFailure = function() {
  DWObject.CloseSource();
  };
 
  DWObject.OpenSource();
  DWObject.IfDisableSourceAfterAcquire = true; // Scanner source will be disabled/closed automatically after the scan. 
  DWObject.AcquireImage(OnAcquireImageSuccess, OnAcquireImageFailure);
  }
  }
  }
 
  //Callback functions for async APIs
  function OnSuccess() {
  console.log('successful');
  }
 
  function OnFailure(errorCode, errorString) {
  alert(errorString);
  }
 
  function loadImage() {
  if (DWObject) {
  DWObject.IfShowFileDialog = true; // Open the system's file dialog to load image
  DWObject.LoadImageEx("", EnumDWT_ImageType.IT_ALL, OnSuccess, OnFailure); // Load images in all supported formats (.bmp, .jpg, .tif, .png, .pdf). OnSuccess or OnFailure will be called after the operation
  }
  }
 
  function saveImage() {
  if (DWObject) {
  if (DWObject.HowManyImagesInBuffer > 0) {
  DWObject.IfShowFileDialog = true;
  if (DWObject.GetImageBitDepth(DWObject.CurrentImageIndexInBuffer) == 1) {
  DWObject.ConvertToGrayScale(DWObject.CurrentImageIndexInBuffer);
  }
  DWObject.SaveAsJPEG("DynamicWebTWAIN.jpg", DWObject.CurrentImageIndexInBuffer);
  }
  }
  }
  </script>
</body>
 
</html>

main.js

'use strict';
 
const { app, BrowserWindow } = require('electron');
 
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
 
function createWindow() {
  // Create the browser window.
  mainWindow = new BrowserWindow({ width: 480, height: 640, resizable: false });
 
  // and load the index.html of the app.
  mainWindow.loadURL('file://' + __dirname + '/index.htm');
 
  // Open the DevTools.
  // mainWindow.webContents.openDevTools();
 
  // Emitted when the window is closed.
  mainWindow.on('closed', function() {
  // Dereference the window object, usually you would store windows
  // in an array if your app supports multi windows, this is the time
  // when you should delete the corresponding element.
  mainWindow = null;
  });
}
 
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
app.on('ready', createWindow);
 
// Quit when all windows are closed.
app.on('window-all-closed', function() {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
  app.quit();
  }
});
 
app.on('activate', function() {
  // On OS X it's coimon to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) {
  createWindow();
  }
});

Run the app

Online document scanning

Distribute the app

You can distribute the application in two steps:

  1. Pack the app with asar npm install -g asar asar pack your-app app.asar

  2. Download Electron pre-built package and put app.asar into the resources folder. By default there are two asar files. It’s fine to leave them there. You can double-click electron.exe to run the app on Windows.

Electron Distribution

Electron Distribution

Source Code

https://github.com/yushulx/electron-document-scan