Dynamic Web TWAIN provides JavaScript API for camera and document scanner access, which allows developers to embed document scanning functionality into web applications. This article aims to build reusable Angular components based on Dynamic Web TWAIN SDK for expediting the development of web document scanner application in Angular.
Pre-requisites
- Node.js
-
Angular CLI v13.3.7
npm install -g @angular/cli ng --version Angular CLI: 13.3.7 Node: 16.13.1 Package Manager: npm 8.1.2 OS: win32 x64 Angular: 13.3.10 ... animations, common, compiler, compiler-cli, core, forms ... platform-browser, platform-browser-dynamic, router Package Version --------------------------------------------------------- @angular-devkit/architect 0.1303.7 @angular-devkit/build-angular 13.3.7 @angular-devkit/core 13.3.7 @angular-devkit/schematics 13.3.7 @angular/cli 13.3.7 @schematics/angular 13.3.7 ng-packagr 13.3.1 rxjs 7.5.5 typescript 4.6.4
-
npm i dwt
Wrapping Dynamic Web TWAIN SDK with Angular Components
We scaffold a new Angular library project with the Angular CLI:
ng new my-workspace --no-create-application
cd my-workspace
ng generate library ngx-web-document-scanner
The project depends on Dynamic Web TWAIN SDK. Add the following line to projects/ngx-web-document-scanner/package.json
:
"peerDependencies": {
"dwt": "^17.3.1"
},
After that, install Dynamic Web TWAIN SDK as a peer dependency:
npm install
Create three Angular components
Next, generate three Angular components for the library. Every component consists of a TypeScript file, CSS file, and HTML file.
cd src/lib
ng generate component ngx-scanner-capture --skip-import
ng generate component ngx-camera-capture --skip-import
ng generate component ngx-document-scanner --skip-import
ngx-scanner-capture
This component is responsible for acquiring images from a TWAIN-compatible, SANE-compatible or ICA-compatible scanners. To communicate with the scanner, you need to install a local service provided by Dynamsoft on the client machine. Dynamic Web TWAIN JavaScript API will automatically detect the service and pop up the download link if the service is not installed.
The projects/ngx-web-document-scanner/src/lib/ngx-scanner-capture/ngx-scanner-capture.component.html
file is the template of the component. We create a select element for listing all the available scanners and three buttons for acquiring documents, loading images from local files, and saving images to local disk.
<div id="scanner-capture" hidden>
<select *ngIf="useLocalService" id="sources"></select><br />
<button *ngIf="useLocalService" (click)="acquireImage()">Scan Documents</button>
<button (click)="openImage()">Load Documents</button>
<button (click)="downloadDocument()">Download Documents</button>
</div>
In the projects/ngx-web-document-scanner/src/lib/ngx-scanner-capture/ngx-scanner-capture.component.ts
file, we firstly import the Dynamic Web TWAIN SDK and initialize the scanner. The useLocalService
property is used to determine whether to use the local service. If the value is true
, the API requests data from the local service. Otherwise, it invokes camera APIs implemented in the wasm module.
import { Component, EventEmitter, OnInit, Output, Input } from '@angular/core';
import { WebTwain } from 'dwt/dist/types/WebTwain';
import Dynamsoft from 'dwt';
@Component({
selector: 'ngx-scanner-capture',
templateUrl: './ngx-scanner-capture.component.html',
styleUrls: ['./ngx-scanner-capture.component.css'],
})
export class NgxScannerCaptureComponent implements OnInit {
dwtObject: WebTwain | undefined;
selectSources: HTMLSelectElement | undefined;
@Input() containerId = '';
@Input() useLocalService = false;
@Input() width = '600px';
@Input() height = '600px';
constructor() {
}
ngOnDestroy() {
Dynamsoft.DWT.Unload();
}
ngOnInit(): void {
Dynamsoft.DWT.Containers = [{ ContainerId: this.containerId, Width: this.width, Height: this.height }];
Dynamsoft.DWT.UseLocalService = this.useLocalService;
Dynamsoft.DWT.Load();
Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => { this.onReady(); });
}
onReady(): void {
this.dwtObject = Dynamsoft.DWT.GetWebTwain(this.containerId);
if (!this.useLocalService) {
this.dwtObject.Viewer.cursor = "pointer";
} else {
let sources = this.dwtObject.GetSourceNames();
this.selectSources = <HTMLSelectElement>document.getElementById("sources");
this.selectSources.options.length = 0;
for (let i = 0; i < sources.length; i++) {
this.selectSources.options.add(new Option(<string>sources[i], i.toString()));
}
}
let elem = document.getElementById('scanner-capture');
if (elem) elem.hidden = false;
}
}
Then implement the three button click events:
acquireImage(): void {
if (!this.dwtObject) return;
if (!this.useLocalService) {
alert("Scanning is not supported under the WASM mode!");
}
else if (this.dwtObject.SourceCount > 0 && this.selectSources && this.dwtObject.SelectSourceByIndex(this.selectSources.selectedIndex)) {
const onAcquireImageSuccess = () => { if (this.dwtObject) this.dwtObject.CloseSource(); };
const onAcquireImageFailure = onAcquireImageSuccess;
this.dwtObject.OpenSource();
this.dwtObject.AcquireImage({}, onAcquireImageSuccess, onAcquireImageFailure);
} else {
alert("No Source Available!");
}
}
openImage(): void {
if (!this.dwtObject) return;
this.dwtObject.IfShowFileDialog = true;
this.dwtObject.Addon.PDF.SetConvertMode(Dynamsoft.DWT.EnumDWT_ConvertMode.CM_RENDERALL);
this.dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL,
() => {
}, () => {
});
}
downloadDocument() {
if (this.dwtObject) {
this.dwtObject.SaveAsJPEG("document.jpg", this.dwtObject.CurrentImageIndexInBuffer);
}
}
ngx-camera-capture
This component is responsible for acquiring images from a camera. All camera-relevant APIs are implemented in the wasm module. So there is no need to install a local service.
Similar to the ngx-scanner-capture
component, we create a source list and three buttons in the projects/ngx-web-document-scanner/src/lib/ngx-camera-capture/ngx-camera-capture.component.html
file.
<div id="camera-capture" hidden>
<div>
<label for="videoSource"></label>
<select id="videoSource"></select><br />
<button (click)="openCamera()">Open a Camera</button>
<button (click)="captureDocument()">Capture Documents</button>
<button (click)="downloadDocument()">Download Documents</button>
</div>
</div>
The difference is that camera requires an HTML element to display the video stream. Therefore, we add a @Input() previewId
property to the component. The previewId
property is used to specify the ID of the HTML element that binds to the video stream.
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { WebTwain } from 'dwt/dist/types/WebTwain';
import Dynamsoft from 'dwt';
@Component({
selector: 'ngx-camera-capture',
templateUrl: './ngx-camera-capture.component.html',
styleUrls: ['./ngx-camera-capture.component.css']
})
export class NgxCameraCaptureComponent implements OnInit {
dwtObject: WebTwain | undefined;
videoSelect: HTMLSelectElement | undefined;
sourceDict: any = {};
@Input() containerId = '';
@Input() useLocalService = true;
@Input() width = '600px';
@Input() height = '600px';
@Input() previewId = '';
constructor() {
}
ngOnDestroy() {
Dynamsoft.DWT.Unload();
}
ngOnInit(): void {
this.videoSelect = document.querySelector('select#videoSource') as HTMLSelectElement;
Dynamsoft.DWT.Containers = [{ ContainerId: this.containerId, Width: this.width, Height: this.height }];
Dynamsoft.DWT.UseLocalService = this.useLocalService;
Dynamsoft.DWT.Load();
Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', () => { this.onReady(); });
}
}
Here are the button click events:
-
openCamera
: Open a camera and display the video stream in the HTML element specified by thepreviewId
property.
openCamera() { if (this.videoSelect) { let index = this.videoSelect.selectedIndex; if (index < 0) return; var option = this.videoSelect.options[index]; if (this.dwtObject) { this.dwtObject.Addon.Camera.selectSource(this.sourceDict[option.text]).then(camera => { if (this.videoSelect) this.createCameraScanner(this.sourceDict[option.text]); }); } } } async createCameraScanner(deviceId: string): Promise<void> { if (this.dwtObject) { await this.dwtObject.Addon.Camera.closeVideo(); await this.dwtObject.Addon.Camera.play(document.getElementById(this.previewId) as HTMLDivElement); } }
-
captureDocument
: Capture images from the camera.
async captureDocument() { if (this.dwtObject) { await this.dwtObject.Addon.Camera.capture(); } }
-
downloadDocument
: Download the captured images.
async downloadDocument() { if (this.dwtObject) { this.dwtObject.SaveAsJPEG("document.jpg", this.dwtObject.CurrentImageIndexInBuffer); } }
ngx-document-scanner
This component contains camera capture and some advanced image processing features. In contrast to the ngx-camera-capture
component, it calls a more advanced method scanDocument()
instead of play()
when initializing the camera preview.
async createCameraScanner(deviceId: string): Promise<void> {
if (this.dwtObject) {
await this.dwtObject.Addon.Camera.closeVideo();
this.dwtObject.Addon.Camera.scanDocument({
scannerViewer: {
deviceId: deviceId,
fullScreen: true,
autoDetect: {
enableAutoDetect: true
},
continuousScan: true
}
}).then(
function () { console.log("OK"); },
function (error: any) { console.log(error.message); });
}
}
The scanDocument()
method launches a document scanner viewer which features document capture, document edge detection, image cropping, perspective correction and image enhancement. You can refer to the API documentation for more details.
Export the components
As the three components are done, we need to declare them in the projects/ngx-web-document-scanner/src/lib/ngx-web-document-scanner.module.ts
file.
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { NgxScannerCaptureComponent } from './ngx-scanner-capture/ngx-scanner-capture.component';
import { NgxCameraCaptureComponent } from './ngx-camera-capture/ngx-camera-capture.component';
import { NgxDocumentScannerComponent } from './ngx-document-scanner/ngx-document-scanner.component';
import { DocumentScannerServiceConfig } from './ngx-web-document-scanner.service';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [
NgxScannerCaptureComponent,
NgxCameraCaptureComponent,
NgxDocumentScannerComponent
],
imports: [
CommonModule
],
exports: [
NgxScannerCaptureComponent,
NgxCameraCaptureComponent,
NgxDocumentScannerComponent
]
})
In addition, set the license key and resource path globally in the projects/ngx-web-document-scanner/src/lib/ngx-web-document-scanner.service.ts
file.
import { Injectable, Optional } from '@angular/core';
import Dynamsoft from 'dwt';
export class DocumentScannerServiceConfig {
licenseKey = '';
resourcePath = '';
}
@Injectable({
providedIn: 'root'
})
export class NgxDocumentScannerService {
constructor(@Optional() config?: DocumentScannerServiceConfig) {
if (config) {
Dynamsoft.DWT.ProductKey = config.licenseKey;
Dynamsoft.DWT.ResourcesPath = config.resourcePath;
}
}
}
The license key of Dynamic Web TWAIN can be obtained from here. The resource path is the path to the folder containing the Dynamic Web TWAIN resources. You also need to configure the resource path in angular.json
file.
Publish the package
We can now build and publish the Angular library project to npm:
ng build ngx-web-document-scanner
cd dist/ngx-web-document-scanner
npm publish
NPM Package
https://www.npmjs.com/package/ngx-web-document-scanner
npm i ngx-web-document-scanner
Creating Web Document Scanner Application in Angular
Let's implement a web document scanner application in Angular within 5 minutes.
-
Create a new Angular project:
ng create web-document-scanner
-
Install the
ngx-web-document-scanner
package:
npm i ngx-web-document-scanner
-
Create a new component:
ng generate component document-scanner
-
Inject the
NgxDocumentScannerService
service in thedocument-scanner.component.ts
file:
import { Component, OnInit } from '@angular/core'; import { NgxDocumentScannerService } from 'ngx-web-document-scanner'; @Component({ selector: 'app-document-scanner', templateUrl: './document-scanner.component.html', styleUrls: ['./document-scanner.component.css'] }) export class DocumentScannerComponent implements OnInit { constructor(private documentScannerService: NgxDocumentScannerService) { } ngOnInit(): void { } }
-
Add an HTML Div element as the image container and include the
ngx-document-capture
component in thedocument-scanner.component.html
file:
<div id="container"> <div id="dwtcontrolContainer"></div> </div> <ngx-scanner-capture [useLocalService]="true" [containerId]="'dwtcontrolContainer'" [width]="'600px'" [height]="'600px'"></ngx-scanner-capture>
-
Configure the resource path of Dynamic Web TWAIN in the
angular.json
file:
"build": { "builder": "@angular-devkit/build-angular:browser", ... "assets": [ "src/favicon.ico", "src/assets", { "glob": "**/*", "input": "./node_modules/dwt/dist", "output": "assets/dynamic-web-twain" } ], ... }
-
Specify the license key and resource path in the
app.module.ts
file:
import { NgxDocumentScannerModule } from 'ngx-web-document-scanner'; @NgModule({ ... imports: [ BrowserModule, AppRoutingModule, NgxDocumentScannerModule.forRoot({ licenseKey: "LICENSE-KEY", resourcePath: "assets/dynamic-web-twain"}), ], ... })
-
Launch the web document scanner application:
ng serve
Try the Online Demo
https://yushulx.me/angular-scanner-camera-capture/