How to Build an Online MRZ Generator with Python, Pyodide and HTML5
Tags: python, webdev, javascript, webassembly
Published: 2023-10-16 07:55:46

When developing or selecting an MRZ (**Machine Readable Zone**) recognition SDK, the primary challenge lies in finding an appropriate dataset for testing. Acquiring genuine MRZ images is challenging, and due to privacy concerns, they aren't publicly accessible. Therefore, crafting MRZ images becomes a practical solution. Fortunately, there's an open-source [Python MRZ generator project](https://pypi.org/project/mrz/), available for download from pypi, eliminating the need to start from scratch. This article aims to illustrate how to integrate and run Python scripts within web applications. First, We will showcase how to employ the Python MRZ SDK and [Flet](https://flet.dev/) to construct a cross-platform MRZ generator. Subsequently, we will reuse the Python script with [Pyodide](https://pyodide.org/en/stable/index.html), HTML5, and the [Dynamsoft JavaScript MRZ SDK](https://www.npmjs.com/package/dynamsoft-label-recognizer), creating an advanced online MRZ tool that can handle both MRZ creation and MRZ detection.

## Building a Cross-Platform MRZ Generator with Python and Flet
Flet UI is powered by Flutter. It allows developers to build desktop, mobile, and web applications using Python.

### New Flet Project

1. Install `flet` and `mrz` packages using `pip`:

    ```bash
    pip install flet
    pip install mrz
    ```

2. Create a Flet project:
    
    ```bash
    flet create mrz-generator
    ```
3. Run the project for desktop:

    ```bash
    cd mrz-generator
    flet run
    ```

### MRZ Generator UI Design
The UI of the MRZ generator is designed as follows:

![Flet Python MRZ generator](https://www.dynamsoft.com/codepool/img/2023/10/flet-python-mrz-generator.png)


- The dropdown list is used for selecting MRZ type:
    
    ```python
    dropdown = ft.Dropdown(on_change=dropdown_changed, width=200, options=[
        ft.dropdown.Option('Passport(TD3)'),
        ft.dropdown.Option('ID Card(TD1)'),
        ft.dropdown.Option('ID Card(TD2)'),
        ft.dropdown.Option('Visa(A)'),
        ft.dropdown.Option('Visa(B)'),
    ],)
    dropdown.value = 'Passport(TD3)'
    ```

- The input fields are used for entering passport, ID card, and visa information:
    
    ```python
    document_type = ft.Text('Document type')
    country_code = ft.Text('Country')
    document_number = ft.Text('Document number')
    birth_date = ft.Text('Birth date')
    sex = ft.Text('Sex')
    expiry_date = ft.Text('Expiry date')
    nationality = ft.Text('Nationality')
    surname = ft.Text('Surname')
    given_names = ft.Text('Given names')
    optional_data1 = ft.Text('Optional data 1')
    optional_data2 = ft.Text('Optional data 2')

    document_type_txt = ft.TextField(
        value='P', text_align=ft.TextAlign.LEFT, width=200, height=50)

    country_code_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    document_number_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    birth_date_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    sex_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    expiry_date_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    nationality_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    surname_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    given_names_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    optional_data1_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)

    optional_data2_txt = ft.TextField(
        value='', text_align=ft.TextAlign.LEFT, width=200, height=50)
    container_loaded = ft.ResponsiveRow([
        ft.Column(col=2, controls=[document_type, document_type_txt,
                                   country_code, country_code_txt,
                                   document_number, document_number_txt,]),
        ft.Column(col=2, controls=[birth_date, birth_date_txt,
                                   sex, sex_txt,
                                   expiry_date, expiry_date_txt,]),
        ft.Column(col=2, controls=[nationality, nationality_txt,
                                   surname, surname_txt,
                                   given_names, given_names_txt,]),
        ft.Column(col=2, controls=[optional_data1, optional_data1_txt,
                                   optional_data2, optional_data2_txt])
    ])
    ```

- The random button is used for generating information automatically:
    
    ```python
    def generate_random_data():
        data = utils.random_mrz_data()
        surname_txt.value = data['Surname']
        given_names_txt.value = data['Given Name']
        nationality_txt.value = data['Nationality']
        country_code_txt.value = nationality_txt.value
        sex_txt.value = data['Sex']
        document_number_txt.value = data['Document Number']
        birth_date_txt.value = data['Birth Date']
        expiry_date_txt.value = data['Expiry Date']

    def generate_random(e):
        generate_random_data()
        page.update()

    button_random = ft.ElevatedButton(
        text='Random', on_click=generate_random)
    ```

    The `random_mrz_data()` function randomly generates surname, given names, nationality, country code, sex, document number, birth date and expiry date.
    
    ```python
    import random
    import datetime

    VALID_COUNTRY_CODES = ['USA', 'CAN', 'GBR', 'AUS', 'FRA', 'CHN', 'IND',
                        'BRA', 'JPN', 'ZAF', 'RUS', 'MEX', 'ITA', 'ESP', 'NLD', 'SWE', 'ARG', 'BEL', 'CHE']


    def random_date(start_year=1900, end_year=datetime.datetime.now().year):
        year = random.randint(start_year, end_year)
        month = random.randint(1, 12)

        if month in [1, 3, 5, 7, 8, 10, 12]:
            day = random.randint(1, 31)
        elif month in [4, 6, 9, 11]:
            day = random.randint(1, 30)
        else:  # February
            if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):  # leap year
                day = random.randint(1, 29)
            else:
                day = random.randint(1, 28)

        return datetime.date(year, month, day)


    def random_string(length=10, allowed_chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
        return ''.join(random.choice(allowed_chars) for i in range(length))


    def random_mrz_data():
        surname = random_string(random.randint(3, 7))
        given_name = random_string(random.randint(3, 7))
        nationality = random.choice(VALID_COUNTRY_CODES)
        sex = random.choice(['M', 'F'])
        document_number = random_string(9, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
        birth_date = random_date()
        expiry_date = random_date(start_year=datetime.datetime.now(
        ).year, end_year=datetime.datetime.now().year + 10)

        return {
            'Surname': surname,
            'Given Name': given_name,
            'Nationality': nationality,
            'Sex': sex,
            'Document Number': document_number,
            'Birth Date': birth_date.strftime('%y%m%d'),
            'Expiry Date': expiry_date.strftime('%y%m%d')
        }

    ```

- The generate button is used for generating MRZ text:
    
    ```python
    def generate_mrz(e):
        if dropdown.value == 'ID Card(TD1)':
            try:
                mrz_field.value = TD1CodeGenerator(
                    document_type_txt.value, country_code_txt.value, document_number_txt.value, birth_date_txt.value, sex_txt.value, expiry_date_txt.value, nationality_txt.value, surname_txt.value, given_names_txt.value, optional_data1_txt.value, optional_data2_txt.value)
            except Exception as e:
                page.snack_bar = ft.SnackBar(
                    content=ft.Text(str(e)),
                    action='OK',
                )
                page.snack_bar.open = True

        elif dropdown.value == 'ID Card(TD2)':

            try:
                mrz_field.value = TD2CodeGenerator(
                    document_type_txt.value, country_code_txt.value, surname_txt.value, given_names_txt.value, document_number_txt.value, nationality_txt.value, birth_date_txt.value, sex_txt.value, expiry_date_txt.value, optional_data1_txt.value)
            except Exception as e:
                page.snack_bar = ft.SnackBar(
                    content=ft.Text(str(e)),
                    action='OK',
                )
                page.snack_bar.open = True

        elif dropdown.value == 'Passport(TD3)':

            try:
                mrz_field.value = TD3CodeGenerator(
                    document_type_txt.value, country_code_txt.value, surname_txt.value, given_names_txt.value, document_number_txt.value, nationality_txt.value, birth_date_txt.value, sex_txt.value, expiry_date_txt.value, optional_data1_txt.value)
            except Exception as e:
                page.snack_bar = ft.SnackBar(
                    content=ft.Text(str(e)),
                    action='OK',
                )
                page.snack_bar.open = True
        elif dropdown.value == 'Visa(A)':

            try:
                mrz_field.value = MRVACodeGenerator(
                    document_type_txt.value, country_code_txt.value, surname_txt.value, given_names_txt.value, document_number_txt.value, nationality_txt.value, birth_date_txt.value, sex_txt.value, expiry_date_txt.value, optional_data1_txt.value)
            except Exception as e:
                page.snack_bar = ft.SnackBar(
                    content=ft.Text(str(e)),
                    action='OK',
                )
                page.snack_bar.open = True

        elif dropdown.value == 'Visa(B)':
            try:
                mrz_field.value = MRVBCodeGenerator(
                    document_type_txt.value, country_code_txt.value, surname_txt.value, given_names_txt.value, document_number_txt.value, nationality_txt.value, birth_date_txt.value, sex_txt.value, expiry_date_txt.value, optional_data1_txt.value)
            except Exception as e:
                page.snack_bar = ft.SnackBar(
                    content=ft.Text(str(e)),
                    action='OK',
                )
                page.snack_bar.open = True

        page.update()

    button_generate = ft.ElevatedButton(
        text='Generate', on_click=generate_mrz)
    ```

### Deploying the MRZ Generator to GitHub Pages
After completing the MRZ generator, we can build it into static web pages by running:
```bash
flet publish main.py
```

The standalone web app is located in the `dist` folder. We can deploy it to GitHub Pages.

Here is the YAML file for GitHub Actions:

```yml
name: deploy mrz generator

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v3
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flet mrz
    - name: Build package
      run: flet publish main.py
    - name: Change base-tag in index.html from / to mrz-generator
      run: sed -i 's/<base href="\/">/<base href="\/mrz-generator\/">/g' dist/index.html
    - name: Commit dist to GitHub Pages
      uses: JamesIves/github-pages-deploy-action@3.7.1
      with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BRANCH: gh-pages
          FOLDER: dist

```

The `mrz-generator` is the name of the repository. The `sed` command is used to change the base tag in `index.html` from `/` to `/mrz-generator/`.

Flet is a convenient choice for building cross-platform applications. However, it is still in the development phase and lacks several features. For instance, it does not support image drawing on a canvas for crafting MRZ images. Given that our objective is to create an online MRZ generator, we can integrate the Pyodide engine utilized by Flet into an HTML5 project to execute Python scripts. This integration of Python and JavaScript code can significantly enhance the capabilities of our MRZ generator.

## Building an Online MRZ Generator with Python, Pyodide, and HTML5
Include the `pyodide.js` file in the HTML page and initialize the Pyodide engine as follows:

```html
<script src="https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js"></script>

<script>
async function main() {
    pyodide = await loadPyodide();
    await pyodide.loadPackage("micropip");
    const micropip = pyodide.pyimport("micropip");
    await micropip.install('mrz');
}
main();
</script>
```

The `mrz` package is installed via `micropip`.

Construct the UI in HTML5. In addition to generating MRZ text, the web project can also create MRZ images by drawing lines, text, and background images on the canvas.
    
```html
<div class="container">
    <div class="row">
        <div>
            <select onchange="selectChanged()" id="dropdown">
                <option value="Passport(TD3)">Passport(TD3)</option>
                <option value="ID Card(TD1)">ID Card(TD1)</option>
                <option value="ID Card(TD2)">ID Card(TD2)</option>
                <option value="Visa(A)">Visa(A)</option>
                <option value="Visa(B)">Visa(B)</option>
            </select>
        </div>
    </div>
    <div class="row">
        <div>
            <label for="docType">Document type</label>
            <input type="text" id="document_type_txt" placeholder="P">
        </div>
        <div>
            <label for="birthDate">Birth date</label>
            <input type="text" id="birth_date_txt" placeholder="210118">
        </div>
        <div>
            <label for="nationality">Nationality</label>
            <input type="text" id="nationality_txt" placeholder="GBR">
        </div>
    </div>

    <div class="row">
        <div>
            <label for="country">Country</label>
            <input type="text" id="country_code_txt" placeholder="GBR">
        </div>
        <div>
            <label for="sex">Sex</label>
            <input type="text" id="sex_txt" placeholder="F">
        </div>
        <div>
            <label for="surname">Surname</label>
            <input type="text" id="surname_txt" placeholder="SXNGND">
        </div>
    </div>

    <div class="row">
        <div>
            <label for="docNumber">Document number</label>
            <input type="text" id="document_number_txt" placeholder="K1RELFC7">
        </div>
        <div>
            <label for="expiryDate">Expiry date</label>
            <input type="text" id="expiry_date_txt" placeholder="240710">
        </div>
        <div>
            <label for="givenNames">Given names</label>
            <input type="text" id="given_names_txt" placeholder="MGGPJ">
        </div>
    </div>

    <div class="row">
        <div>
            <label for="optionalData1">Optional data 1</label>
            <input type="text" id="optional_data1_txt" placeholder="ZE184226B">
        </div>
        <div>
            <label for="optionalData2">Optional data 2</label>
            <input type="text" id="optional_data2_txt">
        </div>
    </div>

    <div class="row">
        <div>
            <button id="randomBtn" onclick="randomize()">Random</button>
        </div>
        <div>
            <button id="generateBtn" onclick="generate()">Generate</button>
        </div>
        <div>
            <button onclick="recognize()">Recognize MRZ</button>
        </div>

    </div>

    <div class="row">
        <div>
            <textarea rows="3" cols="50" readonly id="outputMRZ"></textarea>
        </div>
    </div>

    <div class="row">
        <div class="image-container">
            <canvas id="overlay"></canvas>
        </div>
    </div>


    <div id="mrz-result" class="right-sticky-content"></div>

</div>
```

To expedite development, we can utilize ChatGPT to efficiently port the Python code logic to JavaScript.

- Randomize information for passport, ID card, and visa:
    
    ```javascript
    const VALID_COUNTRY_CODES = ['USA', 'CAN', 'GBR', 'AUS', 'FRA', 'CHN', 'IND', 'BRA', 'JPN', 'ZAF', 'RUS', 'MEX', 'ITA', 'ESP', 'NLD', 'SWE', 'ARG', 'BEL', 'CHE'];

    function randomIntFromInterval(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    function randomDate(startYear = 1900, endYear = new Date().getFullYear()) {
        let year = randomIntFromInterval(startYear, endYear);
        let month = randomIntFromInterval(1, 12);
        let day;

        if ([1, 3, 5, 7, 8, 10, 12].includes(month)) {
            day = randomIntFromInterval(1, 31);
        } else if ([4, 6, 9, 11].includes(month)) {
            day = randomIntFromInterval(1, 30);
        } else { // February
            if ((year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0)) { // leap year
                day = randomIntFromInterval(1, 29);
            } else {
                day = randomIntFromInterval(1, 28);
            }
        }

        let date = new Date(year, month - 1, day);
        return date;
    }

    function formatDate(date) {
        return date.toISOString().slice(2, 10).replace(/-/g, "");
    }

    function randomString(length = 10, allowedChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') {
        let result = '';
        for (let i = 0; i < length; i++) {
            result += allowedChars.charAt(Math.floor(Math.random() * allowedChars.length));
        }
        return result;
    }

    function randomMRZData() {
        let surname = randomString(randomIntFromInterval(3, 7));
        let givenName = randomString(randomIntFromInterval(3, 7));
        let nationality = VALID_COUNTRY_CODES[Math.floor(Math.random() * VALID_COUNTRY_CODES.length)];
        let sex = Math.random() < 0.5 ? 'M' : 'F';
        let documentNumber = randomString(9, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
        let birthDate = randomDate();
        let expiryDate = randomDate(new Date().getFullYear(), new Date().getFullYear() + 10);

        return {
            'Surname': surname,
            'Given Name': givenName,
            'Nationality': nationality,
            'Sex': sex,
            'Document Number': documentNumber,
            'Birth Date': formatDate(birthDate),
            'Expiry Date': formatDate(expiryDate)
        };
    }
    ```

- Generate the MRZ text:
    
    ```javascript
    function generate() {
        detectedLines = [];
        document.getElementById('mrz-result').innerText = '';
        if (!pyodide) return;

        pyodide.globals.set('dropdown', dropdown.value);
        pyodide.globals.set('document_type_txt', document_type_txt.value);
        pyodide.globals.set('country_code_txt', country_code_txt.value);
        pyodide.globals.set('birth_date_txt', birth_date_txt.value);
        pyodide.globals.set('document_number_txt', document_number_txt.value);
        pyodide.globals.set('sex_txt', sex_txt.value);
        pyodide.globals.set('expiry_date_txt', expiry_date_txt.value);
        pyodide.globals.set('nationality_txt', nationality_txt.value);
        pyodide.globals.set('surname_txt', surname_txt.value);
        pyodide.globals.set('given_names_txt', given_names_txt.value);
        pyodide.globals.set('optional_data1_txt', optional_data1_txt.value);
        pyodide.globals.set('optional_data2_txt', optional_data2_txt.value);

        pyodide.runPython(`
        from mrz.generator.td1 import TD1CodeGenerator
        from mrz.generator.td2 import TD2CodeGenerator
        from mrz.generator.td3 import TD3CodeGenerator
        from mrz.generator.mrva import MRVACodeGenerator
        from mrz.generator.mrvb import MRVBCodeGenerator
        
        if dropdown == 'ID Card(TD1)':

            try:
                txt = str(TD1CodeGenerator(
                    document_type_txt, country_code_txt, document_number_txt, birth_date_txt, sex_txt, expiry_date_txt, nationality_txt, surname_txt, given_names_txt, optional_data1_txt, optional_data2_txt))
            except Exception as e:
                txt = e

        elif dropdown == 'ID Card(TD2)':

            try:
                txt = str(TD2CodeGenerator(
                    document_type_txt, country_code_txt, surname_txt, given_names_txt, document_number_txt, nationality_txt, birth_date_txt, sex_txt, expiry_date_txt, optional_data1_txt))
            except Exception as e:
                txt = e

        elif dropdown == 'Passport(TD3)':

            try:
                txt = str(TD3CodeGenerator(
                    document_type_txt, country_code_txt, surname_txt, given_names_txt, document_number_txt, nationality_txt, birth_date_txt, sex_txt, expiry_date_txt, optional_data1_txt))
            except Exception as e:
                txt = e

        elif dropdown == 'Visa(A)':

            try:
                txt = str(MRVACodeGenerator(
                    document_type_txt, country_code_txt, surname_txt, given_names_txt, document_number_txt, nationality_txt, birth_date_txt, sex_txt, expiry_date_txt, optional_data1_txt))
            except Exception as e:
                txt = e

        elif dropdown == 'Visa(B)':
            try:
                txt = str(MRVBCodeGenerator(
                    document_type_txt, country_code_txt, surname_txt, given_names_txt, document_number_txt, nationality_txt, birth_date_txt, sex_txt, expiry_date_txt, optional_data1_txt))
            except Exception as e:
                txt = e
        `);
        dataFromPython = pyodide.globals.get('txt');
        document.getElementById("outputMRZ").value = dataFromPython;
    }
    ```

Lastly, we can render document information and MRZ text onto the canvas:

```javascript
function drawImage() {
    let canvas = document.getElementById("overlay");
    let ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    var img = new Image();
    img.src = 'images/bg.jpg';

    img.onload = function () {
        canvas.width = img.width;
        canvas.height = img.height;
        ctx.fillStyle = '#FFFFFF';  // e.g., a shade of orange
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.drawImage(img, 0, 0, img.width, img.height);

        lines = dataFromPython.split('\n');
        ctx.fillStyle = "black";

        // Title
        let x = 60;
        let y = 80;
        ctx.font = '40px "Arial", monospace';
        if (dropdown.value === 'ID Card(TD1)' || dropdown.value === 'ID Card(TD2)') {
            ctx.fillText('ID Card', x, y);
        }
        else if (dropdown.value === 'Passport(TD3)') {
            ctx.fillText('Passport', x, y);
        }
        else {
            ctx.fillText('Visa', x, y);
        }

        // Info area
        let delta = 21;
        let space = 10;
        x = 400;
        y = 140;

        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Type', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(document_type_txt.value, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Surname', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(surname_txt.value, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Given names', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(given_names_txt.value, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Date of birth', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(`${birth_date_txt.value.slice(0, 2)}/${birth_date_txt.value.slice(2, 4)}/${birth_date_txt.value.slice(4, 6)}`, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Sex', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(sex_txt.value, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Date of expiry', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(`${expiry_date_txt.value.slice(0, 2)}/${expiry_date_txt.value.slice(2, 4)}/${expiry_date_txt.value.slice(4, 6)}`, x, y);

        y += delta + space;
        ctx.font = '16px "Arial", monospace';
        ctx.fillText('Issuing country', x, y);

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(country_code_txt.value, x, y);

        x = 500
        y = 140
        ctx.font = '16px "Arial", monospace';
        if (dropdown.value === 'ID Card(TD1)' || dropdown.value === 'ID Card(TD2)') {
            ctx.fillText('Document number', x, y);
        }
        else if (dropdown.value === 'Passport(TD3)') {
            ctx.fillText('Passport number', x, y);
        }
        else {
            ctx.fillText('Visa number', x, y);
        }

        y += delta;
        ctx.font = 'bold 18px "Arial", monospace';
        ctx.fillText(document_number_txt.value, x, y);

        // MRZ area
        ctx.font = '16px "OCR-B", monospace';
        x = 60;
        y = canvas.height - 80;
        let letterSpacing = 3;
        let index = 0;
        for (text of lines) {

            let currentX = x;
            let checkLine = '';

            if (detectedLines.length > 0) {
                checkLine = detectedLines[index];
            }

            for (let i = 0; i < text.length; i++) {
                ctx.fillText(text[i], currentX, y);

                if (checkLine !== '' && checkLine[i] !== text[i]) {
                    ctx.fillRect(currentX, y + 5, ctx.measureText(text[i]).width, 2);
                }

                currentX += ctx.measureText(text[i]).width + letterSpacing;
            }
            y += 30;
            index += 1;
        }

    }
}
``` 

## Evaluating JavaScript MRZ SDK with MRZ Generator 
After completing the MRZ generator, we can evaluate any JavaScript MRZ SDK with it. For example, we can use the [Dynamsoft JavaScript MRZ SDK](https://www.dynamsoft.com/mrz-sdk/) to recognize MRZ code from the generated MRZ images. A [valid license key](https://www.dynamsoft.com/customer/license/trialLicense?product=dlr&source=codepool) is required for the JavaScript MRZ detection SDK.

```javascript
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-label-recognizer@2.2.31/dist/dlr.js"></script>

<script>
async function main() {
    ...

    Dynamsoft.DLR.LabelRecognizer.initLicense("LICENSE-KEY");
    recognizer = await Dynamsoft.DLR.LabelRecognizer.createInstance({
        runtimeSettings: "MRZ"
    });
}
main();

function recognize() {

    if (recognizer) {
        let div = document.getElementById('mrz-result');
        div.textContent = 'Recognizing...';

        recognizer.recognize(document.getElementById("overlay")).then(function (results) {
            let hasResult = false;
            for (let result of results) {
                if (result.lineResults.length !== 2 && result.lineResults.length !== 3) {
                    continue;
                }
                let output = '';
                for (let line of result.lineResults) {
                    detectedLines.push(line.text);
                    output += line.text + '\n';
                }
                div.innerText = output;
                hasResult = true;
            }
            if (!hasResult) {
                div.innerText = 'Not found';
            }
            else {
                drawImage();
            }
        });
    }
}
</script>
```

![Online MRZ Generator](https://www.dynamsoft.com/codepool/img/2023/10/free-online-mrz-generator.png)

## Try Online MRZ Generator
- MRZ generator only: [https://yushulx.me/mrz-generator/](https://yushulx.me/mrz-generator/)
- MRZ generator and MRZ detector: [https://yushulx.me/mrz-generator-pyodide/](https://yushulx.me/mrz-generator-pyodide/)

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