diff --git a/README.md b/README.md index 7491c7b..f3c92e4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ git clone https://github.com/Dynamsoft/mrz-scanner-javascript ## Request a Trial License -The key "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9" used in this solution (found in the js/init.js file) is a test license valid for 24 hours for any newly authorized browser. If you wish to test the SDK further, you can request a 30-day free trial license through the Request a Trial License link. +The key "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9" used in this solution (found in the js/init.js file) is a test license valid for 24 hours for any newly authorized browser. If you wish to test the SDK further, you can request a 30-day free trial license through the Request a Trial License link. For more information, see [Specify the License](https://www.dynamsoft.com/capture-vision/docs/web/programming/javascript/user-guide/mrz-scanner.html#specify-the-license) or contact [support@dynamsoft.com](mailto:support@dynamsoft.com). ## Project Structure diff --git a/assets/arrow-down 1.svg b/assets/arrow-down.svg similarity index 100% rename from assets/arrow-down 1.svg rename to assets/arrow-down.svg diff --git a/assets/arrow-up 1.svg b/assets/arrow-up.svg similarity index 100% rename from assets/arrow-up 1.svg rename to assets/arrow-up.svg diff --git a/assets/external-link.svg b/assets/external-link.svg new file mode 100644 index 0000000..55c5f58 --- /dev/null +++ b/assets/external-link.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/assets/Music-selected.svg b/assets/music-selected.svg similarity index 100% rename from assets/Music-selected.svg rename to assets/music-selected.svg diff --git a/assets/qr-code.svg b/assets/qr-code.svg new file mode 100644 index 0000000..4bdc98f --- /dev/null +++ b/assets/qr-code.svg @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/upload-menu.svg b/assets/upload-menu.svg new file mode 100644 index 0000000..fbb3d38 --- /dev/null +++ b/assets/upload-menu.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/video-camera.svg b/assets/video-camera.svg new file mode 100644 index 0000000..d68e94c --- /dev/null +++ b/assets/video-camera.svg @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/css/index.css b/css/index.css index e8860c0..bbce8ba 100644 --- a/css/index.css +++ b/css/index.css @@ -57,21 +57,21 @@ img { } .home-page .logo { - width: 150px; - height: 36px; + width: 112.5px; + height: 27px; } .home-page .description { - width: 50%; + width: 85%; text-align: center; background-color: #323234; margin: 0 auto; - padding: 2rem; + padding: 1rem 2rem 2rem; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .home-page .description .title { - font-size: 30px; + font-size: 26px; font-family: Oswald-Regular; } @@ -79,22 +79,52 @@ img { font-family: OpenSans-Regular; font-size: 16px; line-height: 26px; - margin: 16px 0 25px; + margin: 1rem 0 1rem; +} + +.home-page .description .desktop-qr-container { + font-family: OpenSans-Regular; + background-color: #2b2b2b; + padding: 2rem; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 0.5rem; + justify-content: center; + align-items: center; +} + +.home-page .description .desktop-start-btn { + font-family: OpenSans-Regular; + background-color: #2b2b2b; + margin-top: 1rem; + padding: 2rem; + border-radius: 8px; + display: flex; + flex-direction: column; + align-items: start; + gap: 1rem; + text-align: start; +} + +.home-page .description .desktop-start-btn .start-btn { + display: flex; + width: 100%; } .home-page .description .start-btn { - width: 180px; height: 6vh; min-height: 40px; max-height: 60px; background-color: #fe8e14; font-family: Oswald-Regular; - font-size: 20px; + font-size: 16px; margin: 0 auto; - display: flex; + display: none; justify-content: center; align-items: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + padding: 2rem; } .home-page .description .start-btn:hover { @@ -185,8 +215,8 @@ img { } .scanner-container .header .music-container { - width: 30px; - height: 30px; + width: 24px; + height: 24px; cursor: pointer; display: flex; align-items: center; @@ -208,6 +238,20 @@ img { display: none; } +.scanner-container .header .upload-img-btn { + width: 24px; + height: 24px; + cursor: pointer; + display: flex; + align-items: center; + cursor: pointer; +} + +.scanner-container .header .upload-img-icon { + width: 24px; + height: 24px; +} + .result-container { position: absolute; width: 100%; @@ -239,6 +283,17 @@ img { color: #aaaaaa; } +.result-container .scanned-image { + max-height: 200px; +} + +.result-container .scanned-image img, +.result-container .scanned-image canvas { + object-fit: contain; + width: 100%; + height: 100%; +} + .result-container .parsed-result-area { width: 100%; height: 84%; @@ -254,15 +309,38 @@ img { .result-container .parsed-result-area .parsed-filed .field-name { color: #aaaaaa; + display: flex; + align-items: center; + gap: 0.5rem; } .result-container .parsed-result-area .parsed-filed .field-value { word-wrap: break-word; } + .result-container .parsed-result-area .code .field-value { font-family: monospace; } -.result-container .restart-video { +.result-container .parsed-result-area .parsed-filed .status-icon { + display: inline-flex; + align-items: center; + cursor: help; /* Indicates there's a tooltip */ +} + +.result-container .parsed-result-area .parsed-filed .status-icon svg { + width: 16px; + height: 16px; +} + +.result-container .parsed-result-area .parsed-filed .status-success { + color: #22c55e; /* Green */ +} + +.result-container .parsed-result-area .parsed-filed .status-failed { + color: #ef4444; /* Red */ +} + +.result-container .scan-again { width: 100%; height: 10%; min-height: 60px; @@ -274,7 +352,7 @@ img { box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } -.result-container .restart-video .btn-restart-video { +.result-container .scan-again .btn-scan-again { width: 160px; height: 60%; border: 0; @@ -450,6 +528,20 @@ img { } } +/* LIVE CHAT */ +.scanner-container .header .live-chat { + width: 24px; + height: 24px; + cursor: pointer; + display: none; + align-items: center; + cursor: pointer; +} + +.scanner-container .header .live-chat:hover { + opacity: 0.8; +} + @media screen and (max-width: 800px) { html, body, @@ -462,8 +554,27 @@ img { box-shadow: none; } + .home-page .description .desktop-qr-container { + display: none; + } + + .home-page .description .desktop-start-btn { + display: none; + } + .home-page .description .start-btn { + display: flex; font-size: 20px; + padding: 1.5rem; + } + + /* LIVE CHAT CSS */ + .live-chat { + display: flex !important; + } + + #comm100-float-button-20242b05-3781-4d86-9b7f-fab63ddcdde3-2 { + display: none !important; } } diff --git a/index.html b/index.html index 53cd513..7e85d12 100644 --- a/index.html +++ b/index.html @@ -29,7 +29,7 @@ /> - +
@@ -40,21 +40,63 @@ Dynamsoft MRZ Scanner recognizes the Machine-Readable Zone (MRZ) on a passport or ID card and converts the encoded strings into human-readable fields
+
+ https://demo.dynamsoft.com/solutions/mrz-scanner/ +

Scan to Open on Mobile

+

+ For optimal performance, scan this QR code to open the scanner on your mobile device +

+
+
+

Quick Start Options

+ +

+ Note: Desktop cameras may have limited performance. Mobile scanning is recommended for best results. +

+
Powered by Dynamsoft
-
- - - - +
+ camera + down + up +
+
+ +
+ upload-menu +
+
+ music + no-music +
+
+ + + + - down - up -
-
-
- music - no-music
-
+ @@ -195,8 +181,8 @@ right: 0; bottom: 0; margin: auto; - width: 40%; - height: 40%; + width: 20%; + height: 20%; fill: #aaa; animation: 1s linear infinite dce-rotate; " @@ -389,9 +375,10 @@
+
-
- +
+
@@ -414,5 +401,31 @@ > +
+ +
+ + +
diff --git a/js/const.js b/js/const.js index 721cdea..3a12a6e 100644 --- a/js/const.js +++ b/js/const.js @@ -31,6 +31,8 @@ const cameraViewContainer = document.querySelector(".camera-view-container"); const cameraListContainer = document.querySelector(".camera-list"); const cameraSelector = document.querySelector(".camera-selector"); +const uploadImageInput = document.querySelector(".upload-image-input"); + const informationBtn = document.querySelectorAll(".information-btn"); const informationListContainer = document.querySelector(".information-list"); @@ -39,14 +41,15 @@ const scannerContainer = document.querySelector(".scanner-container"); const mrzGuideFrame = document.querySelector(".mrz-frame"); const resultContainer = document.querySelector(".result-container"); +const scannedImage = document.querySelector(".scanned-image"); const parsedResultArea = document.querySelector(".parsed-result-area"); -const startScaningBtn = document.querySelector(".start-btn"); +const startScaningBtn = document.querySelectorAll(".start-btn"); const scanModeContainer = document.querySelector(".scan-mode-container"); const scanBothBtn = document.querySelector("#scan-both-btn"); -const restartVideoBtn = document.querySelector(".btn-restart-video"); +const scanAgainBtn = document.querySelector(".btn-scan-again"); const playSoundBtn = document.querySelector(".music"); const closeSoundBtn = document.querySelector(".no-music"); diff --git a/js/index.js b/js/index.js index ba61235..580d0b2 100644 --- a/js/index.js +++ b/js/index.js @@ -1,4 +1,4 @@ -import { init, pDataLoad } from "./init.js"; +import { handleCapturedResult, init, pDataLoad } from "./init.js"; import { judgeCurResolution, shouldShowScanModeContainer, showNotification } from "./util.js"; import { checkOrientation, getVisibleRegionOfVideo } from "./util.js"; @@ -115,10 +115,10 @@ const region = () => { // -----------Logic for calculating scan region ↑------------ window.addEventListener("click", () => { - cameraListContainer.style.display = "none"; + cameraListContainer.style.display = "none"; // hide camera list and reset arrow indicator up.style.display = "none"; down.style.display = "inline-block"; - informationListContainer.style.display = "none"; + informationListContainer.style.display = "none"; // hide information menu }); // Recalculate the scan region when the window size changes @@ -134,14 +134,40 @@ window.addEventListener("resize", () => { }); // Add click events to buttons -startScaningBtn.addEventListener("click", () => scanBothBtn.click()); +startScaningBtn.forEach((btn) => btn.addEventListener("click", () => scanBothBtn.click())); const restartVideo = async () => { resultContainer.style.display = "none"; document.querySelector(`#scan-${currentMode}-btn`).click(); }; -restartVideoBtn.addEventListener("click", restartVideo); +scanAgainBtn.addEventListener("click", restartVideo); + +uploadImageInput.addEventListener("change", async (event) => { + try { + const file = event.target.files[0]; + + console.log(event); + + if (file) { + // Open the camera after the model and .wasm files have loaded + pInit = pInit || (await init); + await pDataLoad.promise; + + event.target.value = ""; + + // Decode selected image with 'both' template. + const result = await cvRouter.capture(file, SCAN_TEMPLATES.both); + handleCapturedResult(result, file); + } + } catch (ex) { + let errMsg = ex.message || ex; + alert(errMsg); + console.error(errMsg); + } +}); cameraSelector.addEventListener("click", (e) => { + informationListContainer.style.display = "none"; // hide information menu + e.stopPropagation(); const isShow = cameraListContainer.style.display === "block"; cameraListContainer.style.display = isShow ? "none" : "block"; @@ -165,6 +191,8 @@ closeSoundBtn.addEventListener("click", () => { informationBtn.forEach((infoBtn) => infoBtn.addEventListener("click", (e) => { + cameraListContainer.style.display = "none"; // hide camera list + e.stopPropagation(); const isShow = informationListContainer.style.display === "block"; informationListContainer.style.display = isShow ? "none" : "block"; diff --git a/js/init.js b/js/init.js index bbddd60..8230c00 100644 --- a/js/init.js +++ b/js/init.js @@ -1,5 +1,5 @@ import { judgeCurResolution, showNotification } from "./util.js"; -import { createPendingPromise, extractDocumentFields, resultToHTMLElement, formatMRZ } from "./util.js"; +import { createPendingPromise, extractDocumentFields, resultToHTMLElement } from "./util.js"; // Promise variable used to control model loading state const pDataLoad = createPendingPromise(); @@ -7,13 +7,10 @@ const pDataLoad = createPendingPromise(); /** LICENSE ALERT - README * To use the library, you need to first specify a license key using the API "initLicense" as shown below. */ -Dynamsoft.License.LicenseManager.initLicense( - "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAwLTEwMzAwNjk2NyIsIm1haW5TZXJ2ZXJVUkwiOiJodHRwczovL21sdHMuZHluYW1zb2Z0LmNvbS8iLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMCIsInNlc3Npb25QYXNzd29yZCI6IkVUSHZVNlNPV3F3ZiIsInN0YW5kYnlTZXJ2ZXJVUkwiOiJodHRwczovL3NsdHMuZHluYW1zb2Z0LmNvbS8iLCJjaGVja0NvZGUiOjM5OTMzODU2Nn0=" -); +Dynamsoft.License.LicenseManager.initLicense(""); /** - * You can visit https://www.dynamsoft.com/customer/license/trialLicense/?product=mrz&utm_source=docs&package=js to get your own trial license good for 30 days. - * Note that if you downloaded this sample from Dynamsoft while logged in, the above license key may already be your own 30-day trial license. - * For more information, see https://www.dynamsoft.com/label-recognition/programming/javascript/user-guide.html?ver=latest#specify-the-license or contact support@dynamsoft.com. + * You can visit https://www.dynamsoft.com/customer/license/trialLicense/?product=mrz&utm_source=samples&package=js to get your own trial license good for 30 days. + * For more information, see https://www.dynamsoft.com/capture-vision/docs/web/programming/javascript/user-guide/mrz-scanner.html#specify-the-license or contact support@dynamsoft.com. * LICENSE ALERT - THE END */ @@ -100,42 +97,93 @@ let init = (async () => { /* Defines the result receiver for the solution.*/ const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver(); - resultReceiver.onCapturedResultReceived = (result) => { - const recognizedResults = result.textLineResultItems; - const parsedResults = result.parsedResultItems; - - if (recognizedResults?.length) { - // Play sound feedback if enabled - isSoundOn ? Dynamsoft.DCE.Feedback.beep() : null; - - parsedResultArea.innerText = ""; - - // Add MRZ Text to Result - const mrzElement = resultToHTMLElement("MRZ String", formatMRZ(recognizedResults[0]?.text)); - mrzElement.classList.add("code"); - parsedResultArea.appendChild(mrzElement); - - // If a parsed result is obtained, use it to render the result page - if (parsedResults) { - const parseResultInfo = extractDocumentFields(parsedResults[0]); - Object.entries(parseResultInfo).map(([field, value]) => { - const resultElement = resultToHTMLElement(field, value); - parsedResultArea.appendChild(resultElement); - }); - } else { - alert(`Failed to parse the content.`); - parsedResultArea.style.justifyContent = "flex-start"; - } - resultContainer.style.display = "flex"; - cameraListContainer.style.display = "none"; - informationListContainer.style.display = "none"; - scanModeContainer.style.display = "none"; - - cvRouter.stopCapturing(); - cameraView.clearAllInnerDrawingItems(); - } - }; + resultReceiver.onCapturedResultReceived = handleCapturedResult; + await cvRouter.addResultReceiver(resultReceiver); })(); +export const handleCapturedResult = (result, uploadedImage = null) => { + console.log(result); + const recognizedResults = result.textLineResultItems; + const parsedResults = result.parsedResultItems; + const originalImage = result.items?.[0]?.imageData; + + if (recognizedResults?.length) { + // Play sound feedback if enabled + isSoundOn ? Dynamsoft.DCE.Feedback.beep() : null; + + parsedResultArea.innerText = ""; + + // Add MRZ Text to Result + const mrzElement = resultToHTMLElement("MRZ String", recognizedResults[0]?.text); + mrzElement.classList.add("code"); + parsedResultArea.appendChild(mrzElement); + + const parseSuccess = displayResults(recognizedResults[0]?.text, parsedResults?.[0]); + + if (!parseSuccess) { + alert(`Failed to parse the content.`); + parsedResultArea.style.justifyContent = "flex-start"; + } + displayImage(uploadedImage || originalImage); + + dispose(); + } else if (uploadedImage) { + parsedResultArea.innerText = "No results found"; + displayImage(uploadedImage); + dispose(); + } +}; + +const displayResults = (recognizedText, parsedResult) => { + parsedResultArea.innerText = ""; + + // Display MRZ text + const mrzElement = resultToHTMLElement("MRZ String", recognizedText); + mrzElement.classList.add("code"); + parsedResultArea.appendChild(mrzElement); + + // Display parsed fields + if (parsedResult) { + const fields = extractDocumentFields(parsedResult); + Object.entries(fields).forEach(([field, value]) => { + parsedResultArea.appendChild(resultToHTMLElement(field, value)); + }); + return true; + } + + return false; +}; + +function displayImage(image) { + scannedImage.textContent = ""; + + if (image.type?.startsWith("image/")) { + const img = document.createElement("img"); + const imageUrl = URL.createObjectURL(image); + + img.src = imageUrl; + img.className = "uploaded-image"; + + img.onload = () => { + URL.revokeObjectURL(imageUrl); + + scannedImage.append(img); + }; + } else if (image.toCanvas) { + scannedImage.append(image.toCanvas()); + } +} + +function dispose() { + resultContainer.style.display = "flex"; // Show result container + cameraListContainer.style.display = "none"; // hide header menu windows + informationListContainer.style.display = "none"; + scanModeContainer.style.display = "none"; // hide scan mode buttons + + cameraEnhancer.close(); + cvRouter.stopCapturing(); + cameraView.clearAllInnerDrawingItems(); +} + export { pDataLoad, init }; diff --git a/js/util.js b/js/util.js index 1dc0a1b..d2d8da2 100644 --- a/js/util.js +++ b/js/util.js @@ -20,32 +20,101 @@ export function createPendingPromise() { * @returns {Object} An object with key-value pairs of the extracted fields. */ export function extractDocumentFields(result) { - const parseResultInfo = {}; - if (!result.exception) { - const type = result.getFieldValue("documentCode"); - const documentType = JSON.parse(result.jsonString).CodeType; - const birthYear = result.getFieldValue("birthYear"); - const birthYearBase = parseInt(birthYear) > new Date().getFullYear() % 100 ? "19" : "20"; - const fullBirthYear = `${birthYearBase}${birthYear}`; - - const expiryYear = result.getFieldValue("expiryYear"); - const expiryYearBase = parseInt(expiryYear) >= 60 ? "19" : "20"; - const fullExpiryYear = `${expiryYearBase}${expiryYear}`; - - parseResultInfo["Document Type"] = documentType; - parseResultInfo["Issuing State"] = result.getFieldValue("issuingState"); - parseResultInfo["Surname"] = result.getFieldValue("primaryIdentifier"); - parseResultInfo["Given Name"] = result.getFieldValue("secondaryIdentifier"); - parseResultInfo["Document Number"] = - type === "P" ? result.getFieldValue("passportNumber") : result.getFieldValue("documentNumber"); - parseResultInfo["Nationality"] = result.getFieldValue("nationality"); - parseResultInfo["Sex"] = result.getFieldValue("sex"); - parseResultInfo["Date of Birth (YYYY-MM-DD)"] = - fullBirthYear + "-" + result.getFieldValue("birthMonth") + "-" + result.getFieldValue("birthDay"); - parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = - fullExpiryYear + "-" + result.getFieldValue("expiryMonth") + "-" + result.getFieldValue("expiryDay"); + if (!result || result.exception) { + return {}; } - return parseResultInfo; + + const fieldWithStatus = (fieldName) => ({ + text: result.getFieldValue(fieldName), + status: result.getFieldValidationStatus(fieldName), + }); + + const parseDate = (yearField, monthField, dayField) => { + const year = result.getFieldValue(yearField); + const currentYear = new Date().getFullYear() % 100; + const baseYear = + yearField === "expiryYear" ? (parseInt(year) >= 60 ? "19" : "20") : parseInt(year) > currentYear ? "19" : "20"; + + return { + text: `${baseYear}${year}-${result.getFieldValue(monthField)}-${result.getFieldValue(dayField)}`, + status: [yearField, monthField, dayField].every( + (field) => result.getFieldValidationStatus(field) === Dynamsoft.DCP.EnumValidationStatus.VS_SUCCEEDED + ) + ? Dynamsoft.DCP.EnumValidationStatus.VS_SUCCEEDED + : Dynamsoft.DCP.EnumValidationStatus.VS_FAILED, + }; + }; + + const documentType = result.getFieldValue("documentCode"); + const documentNumberField = documentType === "P" ? "passportNumber" : "documentNumber"; + + return { + Surname: fieldWithStatus("primaryIdentifier"), + "Given Name": fieldWithStatus("secondaryIdentifier"), + Nationality: fieldWithStatus("nationality"), + "Document Number": fieldWithStatus(documentNumberField), + "Issuing State": fieldWithStatus("issuingState"), + Sex: fieldWithStatus("sex"), + "Date of Birth (YYYY-MM-DD)": parseDate("birthYear", "birthMonth", "birthDay"), + "Date of Expiry (YYYY-MM-DD)": parseDate("expiryYear", "expiryMonth", "expiryDay"), + "Document Type": JSON.parse(result.jsonString).CodeType, + }; +} + +/** + * Create an HTML paragraph element containing the document field name and value. + * + * @param {string} field - The document field name. + * @param {string} value - The document field value. + * @returns {HTMLElement} The paragraph element containing the formatted document field name and value. + */ +export function resultToHTMLElement(field, value) { + const p = document.createElement("p"); + p.className = "parsed-filed"; + const spanFieldName = document.createElement("span"); + spanFieldName.className = "field-name"; + const spanValue = document.createElement("span"); + spanValue.className = "field-value"; + const statusIcon = document.createElement("span"); + statusIcon.className = "status-icon"; + + // Define success and failed icons + const icons = { + success: ` + + `, + failed: ` + + `, + }; + + // Handle validation status based on EnumValidationStatus + switch (value?.status) { + case Dynamsoft.DCP.EnumValidationStatus.VS_SUCCEEDED: + statusIcon.innerHTML = icons.success; + statusIcon.className += " status-success"; + statusIcon.title = "Validation passed"; + break; + case Dynamsoft.DCP.EnumValidationStatus.VS_FAILED: + statusIcon.innerHTML = icons.failed; + statusIcon.className += " status-failed"; + statusIcon.title = "Validation failed"; + break; + case Dynamsoft.DCP.EnumValidationStatus.VS_NONE: + default: + // Don't add any icon for VS_NONE + statusIcon.style.display = "none"; + break; + } + + spanFieldName.innerText = `${field}`; + spanFieldName.append(statusIcon); + spanValue.innerText = `${value?.text || value || "Not detected"}`; + + p.appendChild(spanFieldName); + p.appendChild(spanValue); + + return p; } /** @@ -111,54 +180,6 @@ export function getVisibleRegionOfVideo() { return regionInPixels; } -/** - * Create an HTML paragraph element containing the document field name and value. - * - * @param {string} field - The document field name. - * @param {string} value - The document field value. - * @returns {HTMLElement} The paragraph element containing the formatted document field name and value. - */ -export function resultToHTMLElement(field, value) { - const p = document.createElement("p"); - p.className = "parsed-filed"; - const spanFieldName = document.createElement("span"); - spanFieldName.className = "field-name"; - const spanValue = document.createElement("span"); - spanValue.className = "field-value"; - - spanFieldName.innerText = `${field} : `; - spanValue.innerText = `${value || "Not detected"}`; - - p.appendChild(spanFieldName); - p.appendChild(spanValue); - - return p; -} - -/** - * Formats a Machine Readable Zone (MRZ) string by adding line breaks based on its length. - * - * @param {string} [mrzString=""] - The MRZ string to format. - * @returns {string} The formatted MRZ string with appropriate line breaks or the original string - */ -export function formatMRZ(mrzString = "") { - let formattedMRZ = mrzString; - - // Check if the length matches any known MRZ format - if (mrzString.length === 88) { - // Passport (TD3 format) - formattedMRZ = mrzString.slice(0, 44) + "\n" + mrzString.slice(44); - } else if (mrzString.length === 90) { - // ID card (TD1 format) - formattedMRZ = mrzString.slice(0, 30) + "\n" + mrzString.slice(30, 60) + "\n" + mrzString.slice(60); - } else if (mrzString.length === 72) { - // Visa (TD2 format) - formattedMRZ = mrzString.slice(0, 36) + "\n" + mrzString.slice(36); - } - - return formattedMRZ; -} - /** Check if current resolution is Full HD or HD * @params {Object} currentResolution - an object with `width` and `height` to represent the current resolution of the camera * @returns {string} Either "HD" or "Full HD" depending of the resolution of the screen diff --git a/template.json b/template.json index 2933d2e..b2ac8fb 100644 --- a/template.json +++ b/template.json @@ -2,21 +2,21 @@ "CaptureVisionTemplates": [ { "Name": "ReadPassportAndId", - "OutputOriginalImage": 0, + "OutputOriginalImage": 1, "ImageROIProcessingNameArray": ["roi-passport-and-id"], "SemanticProcessingNameArray": ["sp-passport-and-id"], "Timeout": 2000 }, { "Name": "ReadPassport", - "OutputOriginalImage": 0, + "OutputOriginalImage": 1, "ImageROIProcessingNameArray": ["roi-passport"], "SemanticProcessingNameArray": ["sp-passport"], "Timeout": 2000 }, { "Name": "ReadId", - "OutputOriginalImage": 0, + "OutputOriginalImage": 1, "ImageROIProcessingNameArray": ["roi-id"], "SemanticProcessingNameArray": ["sp-id"], "Timeout": 2000 @@ -38,75 +38,75 @@ ], "TextLineSpecificationOptions": [ { - "Name": "tls-mrz-passport", - "BaseTextLineSpecificationName": "tls-base", + "Name": "tls_mrz_passport", + "BaseTextLineSpecificationName": "tls_base", "StringLengthRange": [44, 44], "OutputResults": 1, "ExpectedGroupsCount": 1, "ConcatResults": 1, - "ConcatSeparator": "", + "ConcatSeparator": "\n", "SubGroups": [ { "StringRegExPattern": "(P[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}", "StringLengthRange": [44, 44], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" }, { - "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}", + "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[0-9<]{4}[0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}", "StringLengthRange": [44, 44], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" } ] }, { - "Name": "tls-mrz-id-td2", - "BaseTextLineSpecificationName": "tls-base", + "Name": "tls_mrz_id_td2", + "BaseTextLineSpecificationName": "tls_base", "StringLengthRange": [36, 36], "OutputResults": 1, "ExpectedGroupsCount": 1, "ConcatResults": 1, - "ConcatSeparator": "", + "ConcatSeparator": "\n", "SubGroups": [ { "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z<]{31}){(36)}", "StringLengthRange": [36, 36], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" }, { - "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{8}){(36)}", + "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[0-9<]{4}[0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{8}){(36)}", "StringLengthRange": [36, 36], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" } ] }, { - "Name": "tls-mrz-id-td1", - "BaseTextLineSpecificationName": "tls-base", + "Name": "tls_mrz_id_td1", + "BaseTextLineSpecificationName": "tls_base", "StringLengthRange": [30, 30], "OutputResults": 1, "ExpectedGroupsCount": 1, "ConcatResults": 1, - "ConcatSeparator": "", + "ConcatSeparator": "\n", "SubGroups": [ { "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z0-9<]{9}[0-9<][A-Z0-9<]{15}){(30)}", "StringLengthRange": [30, 30], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" }, { - "StringRegExPattern": "([0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z<]{3}[A-Z0-9<]{11}[0-9]){(30)}", + "StringRegExPattern": "([0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[0-9<]{4}[0-9][A-Z<]{3}[A-Z0-9<]{11}[0-9]){(30)}", "StringLengthRange": [30, 30], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" }, { "StringRegExPattern": "([A-Z<]{30}){(30)}", "StringLengthRange": [30, 30], - "BaseTextLineSpecificationName": "tls-base" + "BaseTextLineSpecificationName": "tls_base" } ] }, { - "Name": "tls-base", + "Name": "tls_base", "CharacterModelName": "MRZ", "CharHeightRange": [5, 1000, 1], "BinarizationModes": [ @@ -132,7 +132,7 @@ { "Name": "task-passport", "ConfusableCharactersPath": "ConfusableChars.data", - "TextLineSpecificationNameArray": ["tls-mrz-passport"], + "TextLineSpecificationNameArray": ["tls_mrz_passport"], "SectionImageParameterArray": [ { "Section": "ST_REGION_PREDETECTION", @@ -151,7 +151,7 @@ { "Name": "task-id", "ConfusableCharactersPath": "ConfusableChars.data", - "TextLineSpecificationNameArray": ["tls-mrz-id-td1", "tls-mrz-id-td2"], + "TextLineSpecificationNameArray": ["tls_mrz_id_td1", "tls_mrz_id_td2"], "SectionImageParameterArray": [ { "Section": "ST_REGION_PREDETECTION", @@ -170,7 +170,7 @@ { "Name": "task-passport-and-id", "ConfusableCharactersPath": "ConfusableChars.data", - "TextLineSpecificationNameArray": ["tls-mrz-passport", "tls-mrz-id-td1", "tls-mrz-id-td2"], + "TextLineSpecificationNameArray": ["tls_mrz_passport", "tls_mrz_id_td1", "tls_mrz_id_td2"], "SectionImageParameterArray": [ { "Section": "ST_REGION_PREDETECTION",