Dynamsoft Label Recognizer is a JavaScript MRZ SDK, which allows developers to build web applications to recognize passport, Visa, ID card and travel documents. By combining Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer, it is easy to build real-time MRZ recognition applications with a camera. This article aims to wrap Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer into an Angular module, which saves time for developers to build MRZ recognition applications using Angular.
NPM Package
https://www.npmjs.com/package/ngx-mrz-sdk
Pre-requisites
- Node.js
-
Angular CLI
npm install -g @angular/cli
Apply for a 30-day FREE License key of Dynamsoft Label Recognizer
Steps to Build an Angular MRZ Recognition Module
Scaffold an Angular Library Project
We use Angular CLI to create a new Angular project and add a library project to it:
ng new angular-mrz-scanner
cd angular-mrz-scanner
ng generate library ngx-mrz-sdk
The Angular app project can be used to test and debug the library project. Run the following commands respectively in two terminals to make them work together:
ng build ngx-mrz-sdk --watch
ng serve --ssl
If both of them are compiled successfully, we can change directory to the library project and start to implement the MRZ recognition module.
cd projects/ngx-mrz-sdk/src/lib
Install Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer
Add dynamsoft-label-recognizer
and dynamsoft-camera-enhancer
to peerDependencies
in the package.json
file:
"peerDependencies": {
"dynamsoft-camera-enhancer": "^3.0.1",
"dynamsoft-label-recognizer": "^2.2.11"
},
Then run npm install
to download the two packages.
Configure License Key and Resource Path
The ngx-mrz-sdk.service.ts
file exports a global accessible service, in which we can set the license key of Dynamsoft Label Recognizer and resource paths of Dynamsoft Camera Enhancer and Dynamsoft Label Recognizer.
import { Injectable, Optional } from '@angular/core';
import { LabelRecognizer } from 'dynamsoft-label-recognizer';
import { CameraEnhancer } from 'dynamsoft-camera-enhancer';
export class MrzSdkServiceConfig {
licenseKey = '';
dceResourcePath = '';
dlrResourcePath = '';
}
@Injectable({
providedIn: 'root'
})
export class NgxMrzSdkService {
constructor(@Optional() config?: MrzSdkServiceConfig) {
if (config) {
LabelRecognizer.license = config.licenseKey;
LabelRecognizer.engineResourcePath = config.dlrResourcePath;
CameraEnhancer.engineResourcePath = config.dceResourcePath;
}
}
}
Parse MRZ Data
As MRZ strings are recognized, we need to parse them and extract detailed information. Since Dynamsoft online demo has contained the parsing logic, we don't need to re-invent the wheel. Create a parser.ts file containing the following code:
export class MrzParser {
static parseTwoLines(line1: string, line2: string): any {
let mrzInfo: any = {};
let type = line1.substring(0, 1);
if (!(/[I|P|V]/.test(type))) return false;
if (type === 'P') {
mrzInfo.type = 'PASSPORT (TD-3)';
}
...
return mrzInfo;
};
static parseThreeLines(line1: string, line2: string, line3: string): any {
let mrzInfo: any = {};
let type = line1.substring(0, 1);
if (!(/[I|P|V]/.test(type))) return false;
mrzInfo.type = 'ID CARD (TD-1)';
...
return mrzInfo;
}
}
Show Results on Overlay
To provider a better user experience, we can show the results on an overlay. Create an overlay.ts
file to draw MRZ recognition results:
export class OverlayManager {
overlay: HTMLCanvasElement | undefined;
context: CanvasRenderingContext2D | undefined;
initOverlay(overlay: HTMLCanvasElement): void {
this.overlay = overlay;
this.context = this.overlay.getContext('2d') as CanvasRenderingContext2D;
}
updateOverlay(width: number, height: number): void {
if (this.overlay) {
this.overlay.width = width;
this.overlay.height = height;
this.clearOverlay();
}
}
clearOverlay(): void {
if (this.context && this.overlay) {
this.context.clearRect(0, 0, this.overlay.width, this.overlay.height);
this.context.strokeStyle = '#ff0000';
this.context.lineWidth = 5;
}
}
drawOverlay(points: any, text: any): void {
if (this.context) {
this.context.beginPath();
this.context.moveTo(points[0].x, points[0].y);
this.context.lineTo(points[1].x, points[1].y);
this.context.lineTo(points[2].x, points[2].y);
this.context.lineTo(points[3].x, points[3].y);
this.context.lineTo(points[0].x, points[0].y);
this.context.stroke();
this.context.font = '18px Verdana';
this.context.fillStyle = '#ff0000';
let x = [points[0].x, points[1].x, points[0].x, points[0].x];
let y = [points[0].y, points[1].y, points[0].y, points[0].y];
x.sort(function (a, b) {
return a - b;
});
y.sort(function (a, b) {
return b - a;
});
let left = x[0];
let top = y[0];
this.context.fillText(text, left, top);
}
}
}
Generate MRZ Reader and MRZ Scanner Components
We run the following commands to generate two components: ngx-mrz-reader
and ngx-mrz-scanner
:
ng generate component ngx-mrz-reader --skip-import
ng generate component ngx-mrz-scanner --skip-import
-
ngx-mrz-reader
is used to scan MRZ from image files.
<span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span> <br /> <input type="file" title="file" id="file" accept="image/*" (change)="onChange($event)" /> <div id="imageview"> <img id="image" alt=""/> <canvas id="overlay"></canvas> </div>
-
ngx-mrz-scanner
is used to scan MRZ from camera.
<div id="mrz-scanner"> <span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span> <br /> <div> <label for="videoSource">Video Source</label> <select id="videoSource" (change)="openCamera()"></select> </div> <div id="videoview"> <div class="dce-video-container" id="videoContainer"></div> <canvas id="overlay"></canvas> </div> </div>
Both of them contain @Input
and @Output
properties. The @Input
properties are used to pass configuration to the components. The @Output
properties are used to emit events to the parent component.
@Input() showOverlay: boolean;
@Output() result = new EventEmitter<any>();
constructor() {
this.showOverlay = true;
}
Initialize Dynamsoft Label Recognizer in ngOnInit
method:
ngOnInit(): void {
this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
(async () => {
LabelRecognizer.onResourcesLoaded = (resourcePath) => {
this.isLoaded = true;
};
this.reader = await LabelRecognizer.createInstance();
await this.reader.updateRuntimeSettingsFromString("MRZ");
})();
}
To recognize MRZ from image files, we invoke recognize()
method.
this.reader.recognize(file).then((results: any) => {
let txts: any = [];
try {
if (results.length > 0) {
for (let result of results) {
for (let line of result.lineResults) {
txts.push(line.text);
if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
}
}
let parsedResults = "";
if (txts.length == 2) {
parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
}
else if (txts.length == 3) {
parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
}
this.result.emit([txts.join('\n'), parsedResults]);
} else {
this.result.emit(txts.join(''));
}
} catch (e) {
alert(e);
}
});
Scanning MRZ from camera is also a piece of cake. What we need to do is to bind Dynamsoft Label Recognizer to Dynamsoft Camera Enhancer and thereafter receive the MRZ recognition results from a registered callback.
this.cameraEnhancer = await CameraEnhancer.createInstance();
let uiElement = document.getElementById('videoContainer');
if (uiElement) {
await this.cameraEnhancer.setUIElement(uiElement);
}
if (this.showOverlay) {
await this.scanner.setImageSource(this.cameraEnhancer, { resultsHighlightBaseShapes: DrawingItem });
}
else {
await this.scanner.setImageSource(this.cameraEnhancer, {});
}
this.scanner.onImageRead = (results: any) => {
this.overlayManager.clearOverlay();
let txts: any = [];
try {
if (results.length > 0) {
for (let result of results) {
for (let line of result.lineResults) {
txts.push(line.text);
if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
}
}
let parsedResults = "";
if (txts.length == 2) {
parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
}
else if (txts.length == 3) {
parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
}
this.result.emit([txts.join('\n'), parsedResults]);
} else {
this.result.emit(txts.join(''));
}
} catch (e) {
alert(e);
}
};
this.cameraEnhancer.on("played", (playCallBackInfo: any) => {
this.updateResolution();
});
await this.scanner.startScanning(true);
Configure *.module.ts
The ngx-mrz-sdk.module.ts
file is used to export the Angular components and initialize the service.
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { NgxMrzReaderComponent } from './ngx-mrz-reader/ngx-mrz-reader.component';
import { NgxMrzScannerComponent } from './ngx-mrz-scanner/ngx-mrz-scanner.component';
import { MrzSdkServiceConfig } from './ngx-mrz-sdk.service';
@NgModule({
declarations: [
NgxMrzReaderComponent,
NgxMrzScannerComponent
],
imports: [
],
exports: [
NgxMrzReaderComponent,
NgxMrzScannerComponent
]
})
export class NgxMrzSdkModule {
constructor(@Optional() @SkipSelf() parentModule?: NgxMrzSdkModule) {
if (parentModule) {
throw new Error(
'GreetingModule is already loaded. Import it in the AppModule only');
}
}
static forRoot(config: MrzSdkServiceConfig): ModuleWithProviders<NgxMrzSdkModule> {
return {
ngModule: NgxMrzSdkModule,
providers: [
{ provide: MrzSdkServiceConfig, useValue: config }
]
};
}
}
Test the Angular MRZ Recognition Module
-
Install
ngx-mrz-sdk
package.
npm install ngx-mrz-sdk
-
Create a new Angular component in the Angular project:
ng generate component mrz-scanner
-
Include
<ngx-mrz-scanner>
in themrz-scanner.component.html
file.
<div id="mrzResult"> {{mrzResult}} </div> <ngx-mrz-scanner (result)="onResultReady($event)" [showOverlay]="true" ></ngx-mrz-scanner>
Inject the service and implement the MRZ callback function in
mrz-scanner.component.ts
:
import { Component, OnInit } from '@angular/core'; import { NgxMrzSdkService } from 'ngx-mrz-sdk'; @Component({ selector: 'app-mrz-scanner', templateUrl: './mrz-scanner.component.html', styleUrls: ['./mrz-scanner.component.css'] }) export class MrzScannerComponent implements OnInit { mrzResult: string = ''; constructor(private mrzSdkService: NgxMrzSdkService) { } ngOnInit(): void { } // result = [originalValue, parsedValue] onResultReady(result: any): void { this.mrzResult = ""; for (let i in result[1]) { this.mrzResult += i + ": " + result[1][i] + '\n'; } // this.mrzResult = result[0]; } }
-
Set the license key and the resource path in
app.module.ts
:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { MrzReaderComponent } from './mrz-reader/mrz-reader.component'; import { MrzScannerComponent } from './mrz-scanner/mrz-scanner.component'; import { ProductListComponent } from './product-list/product-list.component'; import { TopBarComponent } from './top-bar/top-bar.component'; import { NgxMrzSdkModule } from 'ngx-mrz-sdk'; @NgModule({ declarations: [ AppComponent, MrzReaderComponent, MrzScannerComponent, ProductListComponent, TopBarComponent, ], imports: [ BrowserModule, AppRoutingModule, NgxMrzSdkModule.forRoot({ licenseKey: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", dceResourcePath: "assets/dynamsoft-camera-enhancer", dlrResourcePath: "assets/dynamsoft-label-recognizer"}), ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
-
Configure the asset path in
angular.json
:
"assets": [ "src/favicon.ico", "src/assets", { "glob": "**/*", "input": "./node_modules/dynamsoft-label-recognizer/dist", "output": "assets/dynamsoft-label-recognizer" }, { "glob": "**/*", "input": "./node_modules/dynamsoft-camera-enhancer/dist", "output": "assets/dynamsoft-camera-enhancer" } ],
-
Run the Angular MRZ scanner application:
ng serve