How to Build a Web App to Scan Barcode, Document, and MRZ with JavaScript APIs

Xiao Ling - Feb 21 - - Dev Community

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.

Online Demo

https://yushulx.me/javascript-barcode-qr-code-scanner/examples/9.x/barcode_mrz_document/

Prerequisites

Integrating Dynamsoft JavaScript APIs

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

<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>
Enter fullscreen mode Exit fullscreen mode

Configuring License Key, Input Source and Scanning Options

image

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

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

    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

    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

    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

    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.

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

Enter fullscreen mode Exit fullscreen mode

Creating a Document Edge Editor

A div element is utilized to construct the document edge editor.

<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>
Enter fullscreen mode Exit fullscreen mode

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.

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();
}
Enter fullscreen mode Exit fullscreen mode

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.

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

    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.

    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.

    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

    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

    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

    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

Machine-readable Zone

web mrz reader

Document

web document rectifier

Source Code

https://github.com/yushulx/javascript-barcode-qr-code-scanner/tree/main/examples/9.x/barcode_mrz_document

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player