Cross-platform Document Scan Application with Electron

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

Installation

Building Desktop App with Electron

Docs

Electron Documentation

Electron app structure

An Electron app is structured as follows:

app/

├── package.json

├── main.js

└── index.html

Do you know how to write the package.json file for Node.js module? It is totally same. 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": "yushulx",
  "homepage": "http://www.dynamsoft.com/Products/WebTWAIN_Overview.aspx",
  "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 Electron API reference, you may see main process and renderer process. What are they and how to use relevant APIs?

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 renderer process

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

Document Scanning App

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 common 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:

electron document scan

Application Distribution

To distribute the application, there are two steps:

  1. pack the app with asar.
    npm install -g asar
    asar pack your-app app.asar
  2. download Electron prebuilt package and put app.asar into recourses folder.
    electron distribution
    By default there are two asar files, it is fine to leave them there. You can double-click electron.exe to run the app on Windows.

Source Code

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