How to Implement an Angular MRZ Recognition Module from Scratch
Tags: angular, webdev, mrz, typescript
Published: 2022-08-30 01:31:23

[Dynamsoft Label Recognizer](https://www.npmjs.com/package/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](https://www.npmjs.com/package/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](https://www.npmjs.com/package/ngx-mrz-sdk)

## Pre-requisites
- [Node.js](https://nodejs.org/en/download/)
- Angular CLI

    ```bash
    npm install -g @angular/cli
    ```

- Apply for a [30-day FREE License key of Dynamsoft Label Recognizer](https://www.dynamsoft.com/customer/license/trialLicense?product=dlr)

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

```bash
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:

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

```bash
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:

```json
"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.

```typescript
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](https://github.com/Dynamsoft/label-recognizer-javascript-demo/blob/master/src/mrz-parser.ts), we don't need to re-invent the wheel. Create a [parser.ts](https://github.com/yushulx/ngx-mrz-sdk/blob/main/projects/ngx-mrz-sdk/src/lib/parser.ts) file containing the following code:

```typescript
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:

```typescript
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`:

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

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

```typescript
@Input() showOverlay: boolean;
@Output() result = new EventEmitter<any>();

constructor() {
  this.showOverlay = true;
}
```

Initialize Dynamsoft Label Recognizer in `ngOnInit` method:

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

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

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

```typescript
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
1. Install `ngx-mrz-sdk` package.

    ```bash
    npm install ngx-mrz-sdk
    ```
2. Create a new Angular component in the Angular project:

    ```bash
    ng generate component mrz-scanner
    ```

3. Include `<ngx-mrz-scanner>` in the `mrz-scanner.component.html` file.

    ```html
    <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`:

    ```typescript
    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];
      }
    }
    ```
4. Set the license key and the resource path in `app.module.ts`:
    ```typescript
    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 { }

    ```

5. Configure the asset path in `angular.json`:

    ```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"
        }
      ],
    ```
6. Run the Angular MRZ scanner application:
  
    ```bash
    ng serve
    ```

    ![Angular MRZ scanner](https://www.dynamsoft.com/codepool/img/2022/08/angular-mrz-scanner.png) 



## Source Code
[https://github.com/yushulx/ngx-mrz-sdk](https://github.com/yushulx/ngx-mrz-sdk)