Files
evidencija-rezija/app/lib/pdf/barcodeDecoder.ts

271 lines
8.0 KiB
TypeScript

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,
ammount: 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
* ammount: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,
ammount,
payerName,
payerAddress,
payerTown,
payeeName,
payeeAddress,
payeeTown,
IBAN,
model,
reference,
code,
description,
] = text.split('\n');
return {
header,
currency,
ammount: parseInt(ammount, 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<HTMLCanvasElement>} the canvas with the image rendered onto it
*/
const image2canvas = async function (imageFile:File): Promise<HTMLCanvasElement> {
const reader = new FileReader();
const canvas = await new Promise<HTMLCanvasElement>((resolve, reject) => {
reader.onload = (progressEvent:ProgressEvent<FileReader>) => {
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<HTMLCanvasElement>} the canvas with the first page of the PDF
*/
const pdf2canvas = async function (pdfFile:File): Promise<HTMLCanvasElement> {
const reader = new FileReader();
const data = await new Promise<Uint8Array>((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();
// Create a new Image object
const barcodeImage = new Image();
// Set the src of the image object to the data URL
barcodeImage.src = dataURL;
return(barcodeImage);
}
/** 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<HTMLCanvasElement | null>} 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<HTMLInputElement>): Promise<{ billInfo: BillInfo, barcodeImage:HTMLImageElement } | null> {
const file = (event.target as HTMLInputElement).files?.[0];
if(!file) {
console.error('No file was selected.');
return null;
}
return(await decodeFromFile(file));
}