diff --git a/app/lib/pdf/barcodeDecoderWasm.ts b/app/lib/pdf/barcodeDecoderWasm.ts index 2f83849..464879b 100644 --- a/app/lib/pdf/barcodeDecoderWasm.ts +++ b/app/lib/pdf/barcodeDecoderWasm.ts @@ -3,14 +3,14 @@ import { readBarcodes, prepareZXingModule, type ReaderOptions } from 'zxing-wasm // Configure WASM file location (similar to how pdf.worker.min.mjs is configured) prepareZXingModule({ - overrides: { - locateFile: (path, prefix) => { - if (path.endsWith('.wasm')) { - return window.location.origin + '/zxing_reader.wasm'; - } - return prefix + path; + overrides: { + locateFile: (path, prefix) => { + if (path.endsWith('.wasm')) { + return window.location.origin + '/zxing_reader.wasm'; + } + return prefix + path; + } } - } }); export type BillInfo = { @@ -31,7 +31,7 @@ export type BillInfo = { }; /** Breaks current microtask execution and gives the UI thread a chance to do a re-paint */ -const yieldToBrowser = (_label:string) => new Promise((resolve) => { +const yieldToBrowser = (_label: string) => new Promise((resolve) => { setTimeout(() => { resolve(true); }, 0); @@ -61,7 +61,7 @@ const yieldToBrowser = (_label:string) => new Promise((resolve) => { * description:Akontacijska rata za 01.2024. * */ -const parseHubText = (text: string):BillInfo => { +const parseHubText = (text: string): BillInfo => { const [ header, currency, @@ -102,12 +102,12 @@ const parseHubText = (text: string):BillInfo => { * @param {File} imageFile - a file containing an image * @return {Promise} the canvas with the image rendered onto it */ -const file2canvas = async function (imageFile:File): Promise { +const file2canvas = async function (imageFile: File): Promise { const reader = new FileReader(); const canvas = await new Promise((resolve, reject) => { - reader.onload = (progressEvent:ProgressEvent) => { + reader.onload = (progressEvent: ProgressEvent) => { const img = new Image(); img.onload = () => { @@ -115,8 +115,8 @@ const file2canvas = async function (imageFile:File): Promise const ctx = canvas.getContext('2d'); canvas.width = img.width; canvas.height = img.height; - - if(!ctx) { + + if (!ctx) { reject("Context is not set") return; } @@ -127,7 +127,7 @@ const file2canvas = async function (imageFile:File): Promise }; const result = (progressEvent.target as FileReader).result; - + img.src = result as string; }; @@ -135,7 +135,7 @@ const file2canvas = async function (imageFile:File): Promise reader.readAsDataURL(imageFile); }); - return(canvas); + return (canvas); } /** @@ -143,31 +143,31 @@ const file2canvas = async function (imageFile:File): Promise * @param {String} imageBase64 - base64 encoded image string * @return {Promise} the canvas with the image rendered onto it */ -const image2canvas = async function (imageBase64:string): Promise { +const image2canvas = async function (imageBase64: string): Promise { const canvas = await new Promise((resolve, reject) => { - const img = new Image(); + const img = new Image(); - img.onload = () => { - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - canvas.width = img.width; - canvas.height = img.height; - - if(!ctx) { - reject("Context is not set") - return; - } + img.onload = () => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = img.width; + canvas.height = img.height; - ctx.drawImage(img, 0, 0); + if (!ctx) { + reject("Context is not set") + return; + } - resolve(canvas); - }; + ctx.drawImage(img, 0, 0); - img.src = imageBase64; + resolve(canvas); + }; + + img.src = imageBase64; }); - return(canvas); + return (canvas); }; /** @@ -175,7 +175,7 @@ const image2canvas = async function (imageBase64:string): Promise} the canvas with the first page of the PDF */ -const pdf2canvas = async function (pdfFile:File): Promise> { +const pdf2canvas = async function (pdfFile: File): Promise> { const reader = new FileReader(); const data = await new Promise((resolve, reject) => { @@ -193,8 +193,8 @@ const pdf2canvas = async function (pdfFile:File): Promise = []; - for(let i = 0; i< pdf.numPages; i++) { - const page: PDFPageProxy = await pdf.getPage(i+1); + for (let i = 0; i < pdf.numPages; i++) { + const page: PDFPageProxy = await pdf.getPage(i + 1); const scale = 4; const viewport = page.getViewport({ scale }); @@ -209,7 +209,7 @@ const pdf2canvas = async function (pdfFile:File): Promise { * @param {HTMLCanvasElement} canvas - the canvas to search for PDF417 codes * @return {Promise | null>} - an array of decoded results * */ -const decodeFromCanvas = async (canvas:HTMLCanvasElement): Promise | null> => { +const decodeFromCanvas = async (canvas: HTMLCanvasElement): Promise | null> => { try { const readerOptions: ReaderOptions = { tryHarder: true, formats: ['PDF417'], - maxNumberOfSymbols: 255, + maxNumberOfSymbols: 10, }; - const width = canvas.width; - const height = canvas.height; + // give browser a chance to re-paint + // this is needed to avoid UI freezing when decoding large images + await yieldToBrowser('decodeFromCanvas'); - // Canvas can contain multiple PDF417 codes, so we need to try to find them all - // The issue is that zxing-wasm can only decode one code at a time in some cases - // and it will throw an error if it finds more than one code. - // To solve this, we will try splitting the canvas into different number of subsections - // and decode each subsection separately. The best result will be the one with the most codes found. - const splits = [5,4,3,2,1,0]; + const imageData = canvasToImageData(canvas); + const results = await readBarcodes(imageData, readerOptions); - let bestResult: Array|null = null; + const codesFound: Array = results + .filter(result => result.text) + .map((result) => ({ + hub3aText: result.text, + billInfo: parseHubText(result.text), + })); - for(let splitIx = 0; splitIx < splits.length; splitIx++) { - const split = splits[splitIx]; + return (codesFound); - const overlap = split === 0 ? 0 : Math.round(height / 50); // 50% overlap ensuring that we don't miss any codes that might be split between sections - const sectionHeight = split === 0 ? height : (Math.floor( Math.floor(height / split) + overlap)); - - - const canvasSections = Array.from({ length: split+1 }, (_, i) => { - - const sectionCanvas = document.createElement('canvas'); - sectionCanvas.width = width; - sectionCanvas.height = sectionHeight; - const sectionContext = sectionCanvas.getContext('2d'); - - if (!sectionContext) { - throw new Error('Failed to get canvas context'); - } - - // Calculate the starting Y position for each section - const startY = i===0 ? 0 : i * (sectionHeight) - overlap; - - // Draw the section of the original canvas onto the new section canvas - sectionContext.drawImage(canvas, 0, startY, width, sectionHeight, 0, 0, width, sectionHeight); - return sectionCanvas; - }); - - - const codesFoundInSection: Array = []; - - // Try to decode each section - for (const sectionCanvas of canvasSections) { - try { - // give browser a chance to re-paint - // this is needed to avoid UI freezing when decoding large images - await yieldToBrowser('decodeFromCanvas'); - - const imageData = canvasToImageData(sectionCanvas); - const results = await readBarcodes(imageData, readerOptions); - - for (const result of results) { - const hub3aText = result.text; - - if (hub3aText) { - codesFoundInSection.push({ - hub3aText, - billInfo: parseHubText(hub3aText), - }); - } - } - - } catch (error) { - // If no code was found in the current section an error will be thrown - // -> we can ignore it - - } finally { - } - } - - await yieldToBrowser('after decodeFromCanvas'); - - // IF in this iteration we found less codes than in the previous best result, - // we can stop searching for more codes - // This is because the number of codes found in each section will only decrease - // as we increase the number of sections (split) - if(bestResult && codesFoundInSection.length <= bestResult.length) { - return(bestResult); - } - - bestResult = codesFoundInSection; - }; - - return(bestResult); - - } catch(ex:any) { - console.log(ex); - return(null); + } catch (error) { + console.log(error); + return (null); } } /** Finds PDF417 code within a base64 encoded image and decodes it */ -export const decodeFromImage = async (imageBase64:string): Promise => { +export const decodeFromImage = async (imageBase64: string): Promise => { const canvas = await image2canvas(imageBase64); const readerOptions: ReaderOptions = { @@ -346,40 +277,40 @@ export const decodeFromImage = async (imageBase64:string): Promise => { - switch(file.type) { +const decodeFromFile = async (file: File): Promise => { + switch (file.type) { case 'image/png': case 'image/jpeg': - return(await decodeFromCanvas( await file2canvas(file) )); + return (await decodeFromCanvas(await file2canvas(file))); case 'application/pdf': const pageCanvas = await pdf2canvas(file); // go through each page of the PDF and decode the PDF417 codes // if there are multiple pages, we will decode each page separately // and return the results from all pages const results = (await Promise.all(pageCanvas.map(async (canvas) => { - await yieldToBrowser('decodeFromCanvas'); - return await decodeFromCanvas(canvas); - }))) + await yieldToBrowser('decodeFromCanvas'); + return await decodeFromCanvas(canvas); + }))) // remove null results (pages with no PDF417 codes) .filter((result) => result !== null) // flatten the array of arrays into a single array .flat() as DecodeResult[]; - return(results); + return (results); default: console.error(file.name, 'is not a .pdf file.'); return null; @@ -391,13 +322,13 @@ const decodeFromFile = async (file:File): Promise => { * @param {Event} event - The change event from an HTMLInputElement. * @return {Promise} The canvas with the first page of the PDF, or null if the document is not a PDF. */ -export async function findDecodePdf417(event: React.ChangeEvent): Promise|null> { +export async function findDecodePdf417(event: React.ChangeEvent): Promise | null> { const file = (event.target as HTMLInputElement).files?.[0]; - if(!file) { + if (!file) { console.error('No file was selected.'); return null; } - return(await decodeFromFile(file)); + return (await decodeFromFile(file)); }