import { PDFPageProxy } from 'pdfjs-dist'; import { BrowserPDF417Reader, BrowserMultiFormatReader } from '@zxing/browser'; import * as pdfJSx from 'pdfjs-dist'; import { BarcodeFormat, DecodeHintType, Result } from '@zxing/library'; export type BillInfo = { header: string, currency: string, amount: number, payerName: string, payerAddress: string, payerTown: string, payeeName: string, payeeAddress: string, payeeTown: string, IBAN: string, model: string, reference: string, code: string, description: string, }; /** * Decodes a PDF417 barcode * @param text * @returns * @description * Example text: "HRVHUB30\nEUR\n000000000012422\nDEREŽIĆ NIKOLA\nULICA DIVKA BUDAKA 17/17\n10000 ZAGREB\nGPZ-Opskrba d.o.o.\nRadniÄ\u008dka cesta 1\n10000 Zagreb\nHR3623400091110343158\nHR05\n02964686-0307\nGASB\nAkontacijska rata za 01.2024.\n" * * Decoded into: * header: HRVHUB30 * currency:EUR * amount:000000000012422 * payerName:DEREŽIĆ NIKOLA * payerAddress:ULICA DIVKA BUDAKA 17/17 * payerTown:10000 ZAGREB * payeeName:GPZ-Opskrba d.o.o. * payeeAddress:RadniÄ\u008dka cesta 1 * payeeTown:10000 Zagreb * IBAN:HR3623400091110343158 * model:HR05 * reference:02964686-0307 * code:GASB * description:Akontacijska rata za 01.2024. * */ const parseHubText = (text: string) => { const [ header, currency, amount, payerName, payerAddress, payerTown, payeeName, payeeAddress, payeeTown, IBAN, model, reference, code, description, ] = text.split('\n'); return { header, currency, amount: parseInt(amount, 10), payerName, payerAddress, payerTown, payeeName, payeeAddress, payeeTown, IBAN, model, reference, code, description, }; } /** * Render an image from the given file onto a canvas. * @param {File} imageFile - a file containing an image * @return {Promise} the canvas with the image rendered onto it */ const image2canvas = async function (imageFile:File): Promise { const reader = new FileReader(); const canvas = await new Promise((resolve, reject) => { reader.onload = (progressEvent:ProgressEvent) => { 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; } ctx.drawImage(img, 0, 0); resolve(canvas); }; const result = (progressEvent.target as FileReader).result; img.src = result as string; }; reader.onerror = (e) => reject(e); reader.readAsDataURL(imageFile); }); return(canvas); } /** * Render the first page of a PDF document onto a new canvas. * @param {File} pdfFile - a file containing a PDF document * @return {Promise} the canvas with the first page of the PDF */ const pdf2canvas = async function (pdfFile:File): Promise { const reader = new FileReader(); const data = await new Promise((resolve, reject) => { reader.onload = (e) => resolve(new Uint8Array((e.target as FileReader).result as ArrayBuffer)); reader.onerror = (e) => reject(e); reader.readAsArrayBuffer(pdfFile); }); const pdfJS = await import('pdfjs-dist'); // worker file was manually copied to the `public` folder pdfJS.GlobalWorkerOptions.workerSrc = window.location.origin + '/pdf.worker.min.mjs'; const pdf = await pdfJS.getDocument(data).promise; const page: PDFPageProxy = await pdf.getPage(1); const scale = 4; const viewport = page.getViewport({ scale }); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: context as CanvasRenderingContext2D, viewport }).promise; return(canvas); } /** * Decodes PDF417 from image contained within the given canvas * */ const decodeFromCanvas = async (canvas:HTMLCanvasElement) => { try { const hints = new Map(); hints.set(DecodeHintType.POSSIBLE_FORMATS, [ BarcodeFormat.PDF_417 ]); hints.set(DecodeHintType.PURE_BARCODE, false); const codeReader = new BrowserPDF417Reader(hints); const result = await codeReader.decodeFromCanvas(canvas); return({ billInfo: parseHubText(result.getText()), barcodeImage: copyBarcodeImage(canvas, result) }) } catch(ex:any) { console.log(ex); return(null); } } /** * Copies bar code from the given canvas * */ const copyBarcodeImage = (canvas:HTMLCanvasElement, decoderResult:Result) => { // get coordinates of bar code const points = decoderResult.getResultPoints(); // get outter coordinates of the bar code const codeLocation = points.reduce((acc, point) => { const x = point.getX(); const y = point.getY(); let result = { top: y < acc.top ? y: acc.top, left: x < acc.left ? x: acc.left, bottom: y > acc.bottom ? y: acc.bottom, right: x > acc.right ? x: acc.right }; return({ ...result, width: result.right - result.left, height: result.bottom - result.top, }); }, { top: Number.MAX_SAFE_INTEGER, left: Number.MAX_SAFE_INTEGER, bottom: 0, right: 0, width: 0, height: 0 }); // copy section of the canvas containing bar code to another canvas const tempCanvas = document.createElement('canvas'); const tempContext = tempCanvas.getContext('2d'); tempCanvas.width = codeLocation.width; tempCanvas.height = codeLocation.height; // Draw the portion of the original canvas onto the temporary canvas // Assuming you want to copy a 100x100 pixels square starting from (50, 50) of the original canvas tempContext?.drawImage(canvas, codeLocation.left, codeLocation.top, codeLocation.width, codeLocation.height, 0, 0, codeLocation.width, codeLocation.height); // Convert the temporary canvas to a data URL const dataURL = tempCanvas.toDataURL(); return(dataURL); } /** Finds PDF417 code within a file and decodes it */ const decodeFromFile = async (file:File) => { switch(file.type) { case 'image/png': case 'image/jpeg': return(await decodeFromCanvas( await image2canvas(file) )); case 'application/pdf': return(await decodeFromCanvas( await pdf2canvas(file) )); default: console.error(file.name, 'is not a .pdf file.'); return null; } } /** * Render the first page of a PDF document onto a new canvas. * @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<{ billInfo: BillInfo, barcodeImage:string } | null> { const file = (event.target as HTMLInputElement).files?.[0]; if(!file) { console.error('No file was selected.'); return null; } return(await decodeFromFile(file)); }