How to Build an Ionic Angular MRZ Passport 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

  1. Generate a page named scanner.

    ionic generate page scanner
    
  2. 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.

  3. In home.page.ts, add a navigate function which navigates to the scanner page.

    navigate(){
      this.router.navigate(['/scanner'],{});
    }
    
  4. In home.page.ts, add an ionViewWillEnter lifecycle hook to check whether there is MRZ text found and update the mrzRawText property. The HTML will be updated if the property is updated.

    ionViewWillEnter(){
      if (history.state.mrzRawText) {
        this.mrzRawText = history.state.mrzRawText;
      }
    }
    
  5. In scanner.page.ts, add the following to ngOnInit to check the navigation with a not found value

    ngOnInit() {
      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.

  1. Run the following command to create the component:

    ionic generate component MRZScanner
    
  2. 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>
    
  3. 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;}
    
  4. In mrzscanner.component.ts, ask for camera permissoin for Android if the component is mounted using the cordova-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;
    }
    
  5. 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;
    }
    
  6. 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.

  7. 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

  1. Add the component in scanner.page.html and scanner.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]
     })
    
  2. In scanner.page.ts, add an onMRZRead function which navigates to the home page with the mrzRawText 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:

https://github.com/tony-xlh/Ionic-Angular-MRZ-Scanner