How to Build an Ionic Angular Passport MRZ Scanner with Cordova
Ionic is a popular framework to build cross-platform mobile web apps. In this article, we are going to build an Ionic Angular app based on Cordova to scan the MRZ codes which are often found on passports.
The JavaScript version of Dynamsoft Label Recognizer is used to provide the ability to read MRZ text. Dynamsoft Camera Enhancer is used to open the camera.
Preview of the final result:
Build an Ionic Angular MRZ Passport Scanner
Let’s do this in steps.
New Project
Use the Ionic cli tool to create a new project.
ionic start mrz-scanner --type=angular --cordova
You can test it in a browser:
ionic serve
Add Camera Permissions
Open config.xml
:
For Android, add the following in the Android platform:
<platform name="android">
<config-file parent="/*" target="AndroidManifest.xml">
<uses-permission android:name="android.permission.CAMERA" />
</config-file>
</platform>
In addition, add xmlns:android="http://schemas.android.com/apk/res/android"
to the root widget
.
For iOS, add the following in the iOS platform:
<platform name="ios">
<config-file parent="NSCameraUsageDescription" target="*-Info.plist">
<string>For barcode scanning</string>
</config-file>
</platform>
Add Native Platforms
Add iOS and Android platforms:
ionic cordova platforms add ios
ionic cordova platforms add android
Run the app:
ionic cordova run android
ionic cordova run ios
Install Dependencies
npm install mrz dynamsoft-camera-enhancer dynamsoft-label-recognizer @awesome-cordova-plugins/android-permissions
ionic cordova plugin add cordova-plugin-android-permissions
Open angular.json
, add the following to projects->app->architect->build->assets
, so that it will copy the resources of Dynamsoft Camera Enhancer and Dynamsoft Label Recognizer to the output folder.
{
"glob": "**/*",
"input": "node_modules/dynamsoft-label-recognizer/dist",
"output": "/assets/dlr/"
},
{
"glob": "**/*",
"input": "node_modules/dynamsoft-camera-enhancer/dist",
"output": "/assets/dce/"
},
Create a Scanner Page and Manage Navigation
-
Generate a page named scanner.
ionic generate page scanner
-
In
home.page.html
, add the following template:<ion-header [translucent]="true"> <ion-toolbar> <ion-title> Home </ion-title> </ion-toolbar> </ion-header> <ion-content [fullscreen]="true"> <ion-button expand="full" (click)="navigate()">Scan MRZ code</ion-button> <ion-label class="ion-text-wrap monospace"> </ion-label> </ion-content>
mrzRawText
is a property to store the recognized MRZ result. -
In
home.page.ts
, add anavigate
function which navigates to the scanner page.navigate(){ this.router.navigate(['/scanner'],{}); }
-
In
home.page.ts
, add anionViewWillEnter
lifecycle hook to check whether there is MRZ text found and update themrzRawText
property. The HTML will be updated if the property is updated.ionViewWillEnter(){ if (history.state.mrzRawText) { this.mrzRawText = history.state.mrzRawText; } }
-
In
scanner.page.ts
, add the following tongOnInit
to check the navigation with anot found
valuengOnInit() { this.router.navigate(['/home'],{ state: { mrzRawText:"Not found" } }); }
Create an MRZ Scanner Component
We need to create an MRZ scanner component to use in the scanner page.
-
Run the following command to create the component:
ionic generate component MRZScanner
-
Add the following elements to
mrzscanner.component.html
:<div class="container" #container> <svg class="dce-bg-loading" viewBox="0 0 1792 1792"><path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z"/></svg> <svg class="dce-bg-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224zm-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5z"/></svg> <div class="dce-video-container"></div> <div class="dce-scanarea"> <div class="dce-scanlight"></div> </div> <div class="sel-container"> <select class="dce-sel-camera"></select> <select class="dce-sel-resolution"></select> <select class="dlr-sel-minletter"></select> </div> </div>
-
Add the following styles to
mrzscanner.component.scss
:@keyframes dce-rotate{from{transform:rotate(0turn);}to{transform:rotate(1turn);}} @keyframes dce-scanlight{from{top:0;}to{top:97%;}} .container{width:100%;height:100%;min-width:100px;min-height:100px;background:#eee;position:absolute;left:0;top:0;} .dce-bg-loading{animation:1s linear infinite dce-rotate;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;} .dce-bg-camera{display:none;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;} .dce-video-container{position:absolute;left:0;top:0;width:100%;height:100%;} .dce-scanarea{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;} .dce-scanarea .dce-scanlight{display:none;position:absolute;width:100%;height:3%;border-radius:50%;box-shadow:0px 0px 2vw 1px #00e5ff;background:#fff;animation:3s infinite dce-scanlight;user-select:none;} .sel-container{position: absolute;left: 0;top: 0;} .sel-container .dce-sel-camera{display:block;} .sel-container .dce-sel-resolution{display:block;margin-top:5px;} .sel-container .dlr-sel-minletter{display:block;margin-top:5px;}
-
In
mrzscanner.component.ts
, ask for camera permissoin for Android if the component is mounted using thecordova-plugin-android-permissions
. If the platform is not Android or the permission has been granted, start scanning.import { AndroidPermissions } from '@awesome-cordova-plugins/android-permissions/'; ngOnInit() { if (this.platform.is("android")) { this.checkPermission(); }else{ this.startScanning(); } } async checkPermission(){ const cameraPermissionResult:boolean = await this.hasCameraPermission(); if (cameraPermissionResult === false) { const response = await this.requestCameraPermission(); console.log(response); if (response === true) { this.startScanning(); } }else{ this.startScanning(); } } async hasCameraPermission():Promise<boolean> { const response = await AndroidPermissions.checkPermission(AndroidPermissions.PERMISSION.CAMERA); return response.hasPermission; } async requestCameraPermission():Promise<boolean> { const response = await AndroidPermissions.requestPermission(AndroidPermissions.PERMISSION.CAMERA); return response.hasPermission; }
-
Create a
startScanning
function which opens the camera using Dynamsoft Camera Enhancer and recognizes MRZ code from the camera frames using Dynamsoft Label Recognizer.async startScanning(){ try { this.configure(); let cameraEnhancer = await CameraEnhancer.createInstance(); await cameraEnhancer.setUIElement((this as any).container.nativeElement); LabelRecognizer.onResourcesLoadStarted = () => { console.log('load started...'); } LabelRecognizer.onResourcesLoadProgress = (resourcesPath, progress)=>{ console.log("Loading resources progress: " + progress.loaded + "/" + progress.total); }; LabelRecognizer.onResourcesLoaded = () => { console.log('load ended...'); } let recognizer = await LabelRecognizer.createInstance(); await recognizer.setImageSource(cameraEnhancer, {resultsHighlightBaseShapes: DrawingItem}); await recognizer.updateRuntimeSettingsFromString("video-mrz"); await recognizer.startScanning(true); // Callback to MRZ recognizing result recognizer.onMRZRead = (txt: string, results: any) => { if (this.onMRZRead) { const valid = this.validateMRZ(txt); if (valid === true) { this.onMRZRead.emit(txt); }else { console.log("Invalid mrz code."); } } } this.cameraEnhancer = cameraEnhancer; this.recognizer = recognizer; } catch (ex) { let errMsg: string; if (ex.message.includes("network connection error")) { errMsg = "Failed to connect to Dynamsoft License Server: network connection error. Check your Internet connection or contact Dynamsoft Support (support@dynamsoft.com) to acquire an offline license."; } else { errMsg = ex.message||ex; } console.error(errMsg); alert(errMsg); } }
The MRZ text is validated using the
mrz
library. If the result is valid, it will navigate to the home page.validateMRZ(mrzText:string) { const parse = require('mrz').parse; let mrz = mrzText.split("\n"); const result = parse(mrz); return result.valid; }
-
Create a
configure
function to configure the resource path and license for Dynamsoft Camera Enhancer and Dynamsoft Label Recognizer.configure(){ let pDLR: any = LabelRecognizer; if (pDLR._pLoad.isFulfilled === false) { LabelRecognizer.engineResourcePath = "/assets/dlr/"; // use the resources copied from node_modules CameraEnhancer.engineResourcePath = "/assets/dce/"; LabelRecognizer.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; } }
You can apply for your own license here.
-
Destroy the instances of Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer in the
ngOnDestroy
lifecycle event.async ngOnDestroy() { if (this.recognizer) { await this.recognizer.destroyContext(); this.cameraEnhancer.dispose(false); this.recognizer = null; this.cameraEnhancer = null; console.log('VideoRecognizer Component Unmount'); } }
Modify the Scanner Page to Use the MRZ Scanner Component
-
Add the component in
scanner.page.html
andscanner.module.ts
HTML:
<div> <app-mrzscanner license="DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==" (onMRZRead)="onMRZRead($event)" ></app-mrzscanner> </div>
module.ts:
+ import { MRZScannerComponent } from '../mrzscanner/mrzscanner.component'; @NgModule({ imports: [ CommonModule, FormsModule, IonicModule, ScannerPageRoutingModule ], schemas: [ CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA ], - declarations: [ScannerPage] + declarations: [ScannerPage,MRZScannerComponent] })
-
In
scanner.page.ts
, add anonMRZRead
function which navigates to the home page with themrzRawText
state:onMRZRead(txt:string) { this.router.navigate(['/home'],{ state: { mrzRawText:txt } }); }
All right, we’ve now finished the MRZ scanner.
Source Code
Check out the source code to have a try: