How to Build a Web App to Scan Barcode, Document, and MRZ with JavaScript APIs
Tags: webdev, javascript, programming, tutorial
Published: 2024-02-21 05:19:13

In the fast-paced realm of web development, seamlessly integrating varied functionalities into web applications has become increasingly essential. Technologies such as barcode detection, Machine Readable Zones (MRZ) recognition, and document rectification are pivotal tools adopted in various domains, including retail, travel, and document management. In this article, we'll explore how to implement these functionalities within your web applications using Dynamsoft JavaScript APIs. 

<video src="https://github.com/yushulx/javascript-barcode-mrz-document-detection/assets/2202306/5c623ebe-89f6-4b5a-a404-116b0713e6c2" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## Online Demo
[https://yushulx.me/javascript-barcode-qr-code-scanner/examples/9.x/barcode_mrz_document/](https://yushulx.me/javascript-barcode-qr-code-scanner/examples/9.x/barcode_mrz_document/)

## Prerequisites
- [Free trial licenses](https://www.dynamsoft.com/customer/license/trialLicense): Get a free trial license for each product
- [Dynamsoft Camera Enhancer](https://www.npmjs.com/package/dynamsoft-camera-enhancer): Helps to access the camera and capture images
- [Dynamsoft JavaScript Barcode](https://www.npmjs.com/package/dynamsoft-javascript-barcode): Reads barcodes from images
- [Dynamsoft Document Normalizer](https://www.npmjs.com/package/dynamsoft-document-normalizer): Detects and rectifies documents
- [Dynamsoft Label Recognizer](https://www.npmjs.com/package/dynamsoft-label-recognizer): Recognizes MRZ from images

## Integrating Dynamsoft JavaScript APIs

In the `index.html` file, include the following JavaScript libraries:

```html
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.3.9/dist/dce.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.32/dist/dbr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@1.0.12/dist/ddn.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-label-recognizer@2.2.31/dist/dlr.js"></script>
```

## Configuring License Key, Input Source and Scanning Options

![image](https://www.dynamsoft.com/codepool/img/2024/02/config-container.png)


The loading of core WebAssembly (WASM) modules will be initiated only upon the user clicking the `Activate SDK` button and entering a valid license key.

```javascript
<div>
    <label>
        Get a License key from <a href="https://www.dynamsoft.com/customer/license/trialLicense"
            target="_blank">here</a>
    </label>
    <input type="text" id="license_key"
        value="LICENSE-KEY"
        placeholder="LICENSE-KEY">
    <button onclick="activate()">Activate SDK</button>
</div>

<script> 
  let normalizer;
  let reader;
  let recognizer;
  let cameraEnhancer;
  let isSDKReady = false;

  async function activate() {
      toggleLoading(true);
      let divElement = document.getElementById("license_key");
      let licenseKey = divElement.value == "" ? divElement.placeholder : divElement.value;

      try {
          Dynamsoft.DBR.BarcodeScanner.license = licenseKey;
          Dynamsoft.DDN.DocumentNormalizer.license = licenseKey;
          Dynamsoft.DLR.LabelRecognizer.license = licenseKey;

          await Dynamsoft.DBR.BarcodeReader.loadWasm();
          await Dynamsoft.DLR.LabelRecognizer.loadWasm();
          await Dynamsoft.DDN.DocumentNormalizer.loadWasm();

          reader = await Dynamsoft.DBR.BarcodeReader.createInstance();

          normalizer = await Dynamsoft.DDN.DocumentNormalizer.createInstance();

          recognizer = await Dynamsoft.DLR.LabelRecognizer.createInstance();
          await recognizer.updateRuntimeSettingsFromString("MRZ");

          cameraEnhancer = await Dynamsoft.DCE.CameraEnhancer.createInstance();

          initCamera();

          isSDKReady = true;
      }
      catch (ex) {
          console.error(ex);
      }

      toggleLoading(false);
  }

  async function initCamera() {
      try {
          cameras = await getCameras(cameraEnhancer);
          if (cameras != null && cameras.length > 0) {
              for (let i = 0; i < cameras.length; i++) {
                  let option = document.createElement("option");
                  option.text = cameras[i].label;
                  cameraSource.add(option);
              }
              await setVideoElement(cameraEnhancer, "camera_view");
              await openCamera(cameraEnhancer, cameras[0]);
          }
          else {
              alert("No camera found.");
          }
      }
      catch (ex) {
          console.error(ex);
      }
  }
</script>
```

Based on the input source selected from a dropdown list, we can dynamically switch between different containers. For instance, selecting an image file as the input will display the file container, whereas choosing a camera input reveals the camera container.


```javascript
<div class="row">
    <div>
        <select onchange="selectChanged()" id="dropdown">
            <option value="file">File</option>
            <option value="camera">Camera</option>
        </select>
        <input type="checkbox" id="barcode_checkbox" checked>Barcode
        <input type="checkbox" id="mrz_checkbox">MRZ
        <input type="checkbox" id="document_checkbox" onchange="checkChanged()">Document
    </div>
</div>

<script>
    function selectChanged() {
        switchProduct(dropdown.value)
    }

    function switchProduct(name) {
        if (name === 'file') {
            let divElement = document.getElementById("file_container");
            divElement.style.display = "block";
    
            divElement = document.getElementById("camera_container");
            divElement.style.display = "none";
        }
        else {
            let divElement = document.getElementById("camera_container");
            divElement.style.display = "block";
    
            divElement = document.getElementById("file_container");
            divElement.style.display = "none";
        }
    }

</script>
```

## Detecting Barcodes, MRZ, and Documents from Images
The following steps outline the process for detecting barcodes, MRZ (Machine Readable Zone), and documents within an image file:
1. Construct the UI (User Interface) for file input and displaying results. This involves creating a section for users to upload an image file and another section to show the detection outcomes.

    ```html
    <div class="container" id="file_container">
        <div>
            <input type="file" id="pick_file" accept="image/*" />
            <button onclick="detect()">Detect</button>
        </div>

        <div class="row">
            <div class="imageview">
                <img id="image_file" src="default.png" />
                <canvas id="overlay_canvas" class="overlay"></canvas>
            </div>
        </div>

        <div class="row">
            <div>
                <textarea id="detection_result"></textarea>
            </div>
        </div>

        <div class="row">
            <div>
                <img id="document-rectified-image" />
            </div>
        </div>

    </div>
    ```

2. Load the uploaded image onto an HTML canvas. The canvas serves not only to display the image but also to overlay the detection results, providing a visual representation of what has been identified.
    
    ```javascript
    document.getElementById("pick_file").addEventListener("change", function () {
        let currentFile = this.files[0];
        if (currentFile == null) {
            return;
        }
        var fr = new FileReader();
        fr.onload = function () {
            loadImage2Canvas(fr.result);
        }
        fr.readAsDataURL(currentFile);
    });

    function loadImage2Canvas(base64Image) {
        imageFile.src = base64Image;
        img.src = base64Image;
        img.onload = function () {
            let width = img.width;
            let height = img.height;
    
            overlayCanvas.width = width;
            overlayCanvas.height = height;
    
            targetCanvas.width = width;
            targetCanvas.height = height;
    
            detect();
        };
    
    }
    ```
    
3. Utilize Dynamsoft JavaScript APIs to detect the barcode, MRZ, and document within the image. Once detected, draw the results directly on the canvas to visually indicate the locations of the detected items.
    
    **Barcode**

    ```javascript
    if (barcodeCheckbox.checked) {
        let barcodeResults = await reader.decode(img);
        if (barcodeResults.length > 0) {
            let txts = [];
            for (var i = 0; i < barcodeResults.length; ++i) {
                txts.push(barcodeResults[i].barcodeText);
                localization = barcodeResults[i].localizationResult;
                text = barcodeResults[i].barcodeText;

                // Draw overlay
                context.beginPath();
                context.strokeStyle = '#ff0000';
                context.lineWidth = 2;
                context.moveTo(localization.x1, localization.y1);
                context.lineTo(localization.x2, localization.y2);
                context.lineTo(localization.x3, localization.y3);
                context.lineTo(localization.x4, localization.y4);
                context.lineTo(localization.x1, localization.y1);
                context.stroke();

                context.font = '18px Verdana';
                context.fillStyle = '#ff0000';
                let x = [localization.x1, localization.x2, localization.x3, localization.x4];
                let y = [localization.y1, localization.y2, localization.y3, localization.y4];
                x.sort(function (a, b) {
                    return a - b;
                });
                y.sort(function (a, b) {
                    return b - a;
                });
                let left = x[0];
                let top = y[0];

                context.fillText(text, left, top + 50);
            }
            detection_result.innerHTML += txts.join(', ') + '\n';
        }
    }

    ```
    
    **MRZ**

    ```javascript
    if (mrzCheckbox.checked) {
        let mrzResults = await recognizer.recognize(img);
        let txts = [];
        for (let result of mrzResults) {
            for (let line of result.lineResults) {
                let text = line.text;
                let points = line.location.points;
                // Draw overlay
                context.beginPath();
                context.strokeStyle = '#0000ff';
                context.lineWidth = 2;
                context.moveTo(points[0].x, points[0].y);
                context.lineTo(points[1].x, points[1].y);
                context.lineTo(points[2].x, points[2].y);
                context.lineTo(points[3].x, points[3].y);
                context.lineTo(points[0].x, points[0].y);
                context.stroke();

                context.font = '18px Verdana';
                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];

                context.fillText(text, left, top);
                txts.push(text);
            }
        }

        if (txts.length == 2) {
            detection_result.innerHTML += JSON.stringify(mrzParseTwoLine(txts[0], txts[1])) + '\n';
        }
        else if (txts.length == 3) {
            detection_result.innerHTML += JSON.stringify(mrzParseThreeLine(txts[0], txts[1], txts[2])) + '\n';
        }
    }

    ```

    **Document**

    ```javascript
    if (documentCheckbox.checked) {
        let documentResults = await normalizer.detectQuad(img);

        if (documentResults.length > 0) {
            let quad = documentResults[0];
            globalPoints = quad.location.points;

            // Start document editor
            openEditor(img.src)

            // Draw overlay
            context.strokeStyle = "#00ff00";
            context.lineWidth = 2;
            for (let i = 0; i < globalPoints.length; i++) {
                context.beginPath();
                context.arc(globalPoints[i].x, globalPoints[i].y, 5, 0, 2 * Math.PI);
                context.stroke();
            }
            context.beginPath();
            context.moveTo(globalPoints[0].x, globalPoints[0].y);
            context.lineTo(globalPoints[1].x, globalPoints[1].y);
            context.lineTo(globalPoints[2].x, globalPoints[2].y);
            context.lineTo(globalPoints[3].x, globalPoints[3].y);
            context.lineTo(globalPoints[0].x, globalPoints[0].y);
            context.stroke();

            let x = [globalPoints[0].x, globalPoints[1].x, globalPoints[0].x, globalPoints[0].x];
            let y = [globalPoints[0].y, globalPoints[1].y, globalPoints[0].y, globalPoints[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];
            context.font = '18px Verdana';
            context.fillStyle = '#00ff00';
            context.fillText('Detected document', left, top);
        }
    }

    ```

### Parsing MRZ
After successfully recognizing the MRZ, it is possible to parse the MRZ string to extract vital information, including the individual's name, passport number, date of birth, and expiration date.

```javascript
function mrzParseLine(line, startIndex, endIndex, isDate = false) {
    let extractedString = line.substring(startIndex, endIndex).replace(/</g, ' ').trim();
    return isDate ? formatDateString(extractedString) : extractedString;
}

function formatDateString(dateString) {
    let currentYear = new Date().getFullYear();
    let century = parseInt(dateString.substr(0, 2)) > currentYear % 100 ? '19' : '20';
    return century + dateString.slice(0, 2) + '-' + dateString.slice(2, 4) + '-' + dateString.slice(4);
}

function extractType(typeChar) {
    if (!/[IPV]/.test(typeChar)) return false;
    switch (typeChar) {
        case 'P': return 'PASSPORT (TD-3)';
        case 'V': return line1.length === 44 ? 'VISA (MRV-A)' : 'VISA (MRV-B)';
        case 'I': return 'ID CARD (TD-2)';
        default: return false;
    }
}

function mrzParseTwoLine(line1, line2) {
    let passportMRZ = {};

    passportMRZ.type = extractType(line1.substring(0, 1));
    if (!passportMRZ.type) return false;

    passportMRZ.nationality = mrzParseLine(line1, 2, 5);
    passportMRZ.surname = mrzParseLine(line1, 5, line1.indexOf("<<"));
    passportMRZ.givenname = mrzParseLine(line1, line1.indexOf("<<") + 2);

    passportMRZ.passportnumber = mrzParseLine(line2, 0, 9);
    passportMRZ.issuecountry = mrzParseLine(line2, 10, 13);
    passportMRZ.birth = mrzParseLine(line2, 13, 19, true);
    passportMRZ.gender = line2[20];
    passportMRZ.expiry = mrzParseLine(line2, 21, 27, true);

    return passportMRZ;
}

function mrzParseThreeLine(line1, line2, line3) {
    let passportMRZ = {};

    passportMRZ.type = extractType(line1.substring(0, 1));
    if (!passportMRZ.type) return false;

    passportMRZ.nationality = mrzParseLine(line2, 15, 18);
    passportMRZ.surname = mrzParseLine(line3, 0, line3.indexOf("<<"));
    passportMRZ.givenname = mrzParseLine(line3, line3.indexOf("<<") + 2);

    passportMRZ.passportnumber = mrzParseLine(line1, 5, 14);
    passportMRZ.issuecountry = mrzParseLine(line1, 2, 5);
    passportMRZ.birth = mrzParseLine(line2, 0, 6, true);
    passportMRZ.gender = line2[7].replace('<', 'X');
    passportMRZ.expiry = mrzParseLine(line2, 8, 14, true);

    return passportMRZ;
}

```

### Creating a Document Edge Editor
A `div` element is utilized to construct the document edge editor. 

```html
<div class="container" id="document_editor">
    <div>
        <button onclick="edit()">Edit</button>
        <button onclick="rectify()">Rectify</button>
        <button onclick="save()">Save</button>
    </div>
    <div class="imageview" id="edit_view">
        <img id="target_file" src="default.png" />
        <canvas id="target_canvas" class="overlay"></canvas>
    </div>
    <div class="imageview" id="rectify_view">
        <img id="rectified_image" src="default.png" />
    </div>
</div>
```


The editor allows users to manually refine the detected document edges if the initial detection is imprecise. The coordinates of the document's four points are stored in the `globalPoints` array. When the user clicks and drags a point, the coordinates are updated and the quad is redrawn.

```javascript
function openEditor(image) {
    let target_context = targetCanvas.getContext('2d');
    targetCanvas.addEventListener("mousedown", (event) => updatePoint(event, target_context, targetCanvas));
    targetCanvas.addEventListener("touchstart", (event) => updatePoint(event, target_context, targetCanvas));
    drawQuad(target_context, targetCanvas);
    targetFile.src = image;
}

function updatePoint(e, context, canvas) {
    if (!globalPoints) {
        return;
    }

    function getCoordinates(e) {
        let rect = canvas.getBoundingClientRect();

        let scaleX = canvas.clientWidth / canvas.width;
        let scaleY = canvas.clientHeight / canvas.height;

        let mouseX = e.clientX || e.touches[0].clientX;
        let mouseY = e.clientX || e.touches[0].clientY;
        if (scaleX < scaleY) {
            mouseX = e.clientX - rect.left;
            mouseY = e.clientY - rect.top - (canvas.clientHeight - canvas.height * scaleX) / 2;

            mouseX = mouseX / scaleX;
            mouseY = mouseY / scaleX;
        }
        else {
            mouseX = e.clientX - rect.left - (canvas.clientWidth - canvas.width * scaleY) / 2;
            mouseY = e.clientY - rect.top;

            mouseX = mouseX / scaleY;
            mouseY = mouseY / scaleY;
        }

        return { x: Math.round(mouseX), y: Math.round(mouseY) };
    }

    let delta = 10;
    let coordinates = getCoordinates(e);

    for (let i = 0; i < globalPoints.length; i++) {
        if (Math.abs(globalPoints[i].x - coordinates.x) < delta && Math.abs(globalPoints[i].y - coordinates.y) < delta) {
            canvas.addEventListener("mousemove", dragPoint);
            canvas.addEventListener("mouseup", releasePoint);
            canvas.addEventListener("touchmove", dragPoint);
            canvas.addEventListener("touchend", releasePoint);
            function dragPoint(e) {
                coordinates = getCoordinates(e);
                globalPoints[i].x = coordinates.x;
                globalPoints[i].y = coordinates.y;
                drawQuad(context, canvas);
            }
            function releasePoint() {
                canvas.removeEventListener("mousemove", dragPoint);
                canvas.removeEventListener("mouseup", releasePoint);
                canvas.removeEventListener("touchmove", dragPoint);
                canvas.removeEventListener("touchend", releasePoint);
            }
            break;
        }
    }
}

function drawQuad(context, canvas) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.strokeStyle = "#00ff00";
    context.lineWidth = 2;
    for (let i = 0; i < globalPoints.length; i++) {
        context.beginPath();
        context.arc(globalPoints[i].x, globalPoints[i].y, 5, 0, 2 * Math.PI);
        context.stroke();
    }
    context.beginPath();
    context.moveTo(globalPoints[0].x, globalPoints[0].y);
    context.lineTo(globalPoints[1].x, globalPoints[1].y);
    context.lineTo(globalPoints[2].x, globalPoints[2].y);
    context.lineTo(globalPoints[3].x, globalPoints[3].y);
    context.lineTo(globalPoints[0].x, globalPoints[0].y);
    context.stroke();
}
```

## Scanning Barcodes, MRZ, and Documents from a Camera
1. Create a `div` element that will serve as the camera view, providing a visual output of the camera feed.
    
    ```html
    <div class="container" id="camera_container">
        <div>
            <select onchange="cameraChanged()" id="camera_source">
            </select>
            <button onclick="scan()" id="scan_button">Start</button>
            <button onclick="capture()">Capture a document</button>
            <div id="videoview">
                <div id="camera_view"></div>
            </div>
    
            <div class="row">
                <div>
                    <textarea id="scan_result"></textarea>
                </div>
            </div>
    
        </div>
    </div>
    ```

2. Choose and activate a camera. Utilize the `isLooping` flag to ensure that the system waits for one camera to close before opening another.

    ```javascript
    async function cameraChanged() {
        isDetecting = false;
        while (isLooping) {
            await new Promise(resolve => setTimeout(resolve, 100));
        }
        if (cameras != null && cameras.length > 0) {
            let index = cameraSource.selectedIndex;
            await openCamera(cameraEnhancer, cameras[index]);
        }
    }    
    ```

3. Start the process of scanning for barcodes, MRZ (Machine Readable Zones), and documents directly from the camera feed.

    ```javascript
    function scan() {
        if (!isSDKReady) {
            alert("Please activate the SDK first.");
            return;
        }
    
        if (!isDetecting) {
            scanButton.innerHTML = "Stop";
            isDetecting = true;
            startDetectionLoop();
        }
        else {
            scanButton.innerHTML = "Scan";
            isDetecting = false;
        }
    }
    
    async function startDetectionLoop() {
        isLooping = true;
        let scan_result = document.getElementById('scan_result');
        scan_result.innerHTML = "";
        while (isDetecting) {
            let frame = acquireCameraFrame(cameraEnhancer);
            let clearCount = 0;

            try {
                if (barcodeCheckbox.checked) {
                    let barcodeResults = await reader.decode(frame);
                    ... // Draw overlay
                }
    
                if (mrzCheckbox.checked) {
                    let mrzResults = await recognizer.recognize(frame);
                    ... // Draw overlay
                }
    
                if (documentCheckbox.checked) {
                    let documentResults = await normalizer.detectQuad(frame);
                    ... // Draw overlay
                }
    
                if (clearCount == 0) {
                    clearOverlay(cameraEnhancer);
                    await new Promise(resolve => setTimeout(resolve, 30));
                }
            }
            catch (ex) {
                console.error(ex);
            }
        }
    
        clearOverlay(cameraEnhancer);
        isLooping = false;
    }
    ```

4. Employ the Dynamsoft Camera Enhancer's built-in rendering capabilities to overlay the scanning results on the camera feed, enhancing user interaction and feedback.
        
    ```javascript
    function drawLine(cameraEnhancer, x1, y1, x2, y2) {
        if (!Dynamsoft) return;
    
        try {
            let drawingLayers = cameraEnhancer.getDrawingLayers();
            let drawingLayer;
            let drawingItems = new Array(
                new Dynamsoft.DCE.DrawingItem.DT_Line({
                    x: x1,
                    y: y1
                }, {
                    x: x2,
                    y: y2
                }, 1)
            )
            if (drawingLayers.length > 0) {
                drawingLayer = drawingLayers[0];
            }
            else {
                drawingLayer = cameraEnhancer.createDrawingLayer();
            }
            drawingLayer.addDrawingItems(drawingItems);
        }
        catch (ex) {
            console.error(ex);
        }
    }
    
    function drawText(cameraEnhancer, text, x, y) {
        if (!Dynamsoft) return;
    
        try {
            let drawingLayers = cameraEnhancer.getDrawingLayers();
            let drawingLayer;
            let drawingItems = new Array(
                new Dynamsoft.DCE.DrawingItem.DT_Text(text, x, y, 1),
            )
            if (drawingLayers.length > 0) {
                drawingLayer = drawingLayers[0];
            }
            else {
                drawingLayer = cameraEnhancer.createDrawingLayer();
            }
            drawingLayer.addDrawingItems(drawingItems);
        }
        catch (ex) {
            console.error(ex);
        }
    }
    ```
    
    **Barcode**
    
    ```javascript
    if (barcodeResults.length > 0) {
        let txts = [];
        for (var i = 0; i < barcodeResults.length; ++i) {
            txts.push(barcodeResults[i].barcodeText);
            localization = barcodeResults[i].localizationResult;
            text = barcodeResults[i].barcodeText;

            // Draw overlay
            drawLine(cameraEnhancer, localization.x1, localization.y1, localization.x2, localization.y2);
            drawLine(cameraEnhancer, localization.x2, localization.y2, localization.x3, localization.y3);
            drawLine(cameraEnhancer, localization.x3, localization.y3, localization.x4, localization.y4);
            drawLine(cameraEnhancer, localization.x4, localization.y4, localization.x1, localization.y1);

            let x = [localization.x1, localization.x2, localization.x3, localization.x4];
            let y = [localization.y1, localization.y2, localization.y3, localization.y4];
            x.sort(function (a, b) {
                return a - b;
            });
            y.sort(function (a, b) {
                return b - a;
            });
            let left = x[0];
            let top = y[0];

            drawText(cameraEnhancer, text, left, top + 50);
        }
        scan_result.innerHTML += txts.join(', ') + '\n';
    }
    ```
    
    **MRZ**
    
    ```javascript
    if (mrzCheckbox.checked) {
        let mrzResults = await recognizer.recognize(frame);
        if (clearCount == 0) {
            clearOverlay(cameraEnhancer);
            clearCount += 1;
        }

        let txts = [];
        for (let result of mrzResults) {
            for (let line of result.lineResults) {
                let text = line.text;
                let points = line.location.points;
                // Draw overlay
                drawLine(cameraEnhancer, points[0].x, points[0].y, points[1].x, points[1].y);
                drawLine(cameraEnhancer, points[1].x, points[1].y, points[2].x, points[2].y);
                drawLine(cameraEnhancer, points[2].x, points[2].y, points[3].x, points[3].y);
                drawLine(cameraEnhancer, points[3].x, points[3].y, points[0].x, points[0].y);

                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];

                drawText(cameraEnhancer, text, left, top);
                txts.push(text);
            }
        }

        if (txts.length == 2) {
            scan_result.innerHTML += JSON.stringify(mrzParseTwoLine(txts[0], txts[1])) + '\n';
        }
        else if (txts.length == 3) {
            scan_result.innerHTML += JSON.stringify(mrzParseThreeLine(txts[0], txts[1], txts[2])) + '\n';
        }
    }
    ```

    **Document**
    
    ```javascript
    if (documentCheckbox.checked) {
        let documentResults = await normalizer.detectQuad(frame);
        if (clearCount == 0) {
            clearOverlay(cameraEnhancer);
            clearCount += 1;
        }

        if (documentResults.length > 0) {
            let quad = documentResults[0];
            let points = quad.location.points;

            if (isCaptured) {
                isCaptured = false;
                globalPoints = points;
                targetCanvas.width = resolution[0];
                targetCanvas.height = resolution[1];
                openEditor(frame.toDataURL());
            }

            // Draw overlay
            drawLine(cameraEnhancer, points[0].x, points[0].y, points[1].x, points[1].y);
            drawLine(cameraEnhancer, points[1].x, points[1].y, points[2].x, points[2].y);
            drawLine(cameraEnhancer, points[2].x, points[2].y, points[3].x, points[3].y);
            drawLine(cameraEnhancer, points[3].x, points[3].y, points[0].x, points[0].y);

            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];
            drawText(cameraEnhancer, 'Detected document', left, top);

        }
    }
    ```

## Testing the Multi-scanning Application in a Web Browser

**Barcode**

![web barcode reader](https://www.dynamsoft.com/codepool/img/2024/02/javascript-barcode-reader.png)

**Machine-readable Zone**

![web mrz reader](https://www.dynamsoft.com/codepool/img/2024/02/javascript-mrz-reader.png)

**Document**

![web document rectifier](https://www.dynamsoft.com/codepool/img/2024/02/javascript-document-detection.png)

## Source Code
[https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/9.x/barcode_mrz_document](https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/9.x/barcode_mrz_document)