implemented barcode decoder
This commit is contained in:
271
app/lib/pdf/barcodeDecoder.ts
Normal file
271
app/lib/pdf/barcodeDecoder.ts
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { PDFPageProxy } from 'pdfjs-dist';
|
|
||||||
import { BrowserPDF417Reader, BrowserMultiFormatReader } from '@zxing/browser';
|
|
||||||
|
|
||||||
import * as pdfJSx from 'pdfjs-dist';
|
|
||||||
import { BarcodeFormat, DecodeHintType } from '@zxing/library';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 pdf2canvas(event: React.ChangeEvent<HTMLInputElement>): Promise<HTMLCanvasElement | null> {
|
|
||||||
const file = (event.target as HTMLInputElement).files?.[0];
|
|
||||||
|
|
||||||
if(!file) {
|
|
||||||
console.error('No file was selected.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.type !== 'application/pdf') {
|
|
||||||
console.error(file.name, 'is not a .pdf file.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(file);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 = 1.5;
|
|
||||||
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;
|
|
||||||
|
|
||||||
const hints = new Map();
|
|
||||||
hints.set(DecodeHintType.POSSIBLE_FORMATS, [ BarcodeFormat.PDF_417 ]);
|
|
||||||
// hints.set(DecodeHintType.TRY_HARDER, true);
|
|
||||||
hints.set(DecodeHintType.PURE_BARCODE, false);
|
|
||||||
|
|
||||||
const codeReader = new BrowserPDF417Reader(hints);
|
|
||||||
// const codeReader = new BrowserMultiFormatReader(hints);
|
|
||||||
const result = await codeReader.decodeFromCanvas(canvas);
|
|
||||||
|
|
||||||
// TODO: Try Next: Quagga
|
|
||||||
|
|
||||||
console.log(result);
|
|
||||||
console.log(result.getResultPoints());
|
|
||||||
|
|
||||||
// codeReader.decode(imageData);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import * as pdfjsModule from 'pdfjs-dist';
|
|
||||||
|
|
||||||
const pdfjs = (
|
|
||||||
'default' in pdfjsModule ? pdfjsModule['default'] : pdfjsModule
|
|
||||||
) as typeof pdfjsModule;
|
|
||||||
|
|
||||||
export default pdfjs;
|
|
||||||
@@ -7,7 +7,7 @@ import { useFormState } from "react-dom";
|
|||||||
import { updateOrAddBill } from "../lib/actions/billActions";
|
import { updateOrAddBill } from "../lib/actions/billActions";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { formatYearMonth } from "../lib/format";
|
import { formatYearMonth } from "../lib/format";
|
||||||
import { pdf2canvas } from "../lib/pdf/pdf2png";
|
import { findDecodePdf417 } from "../lib/pdf/barcodeDecoder";
|
||||||
|
|
||||||
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
|
// Next.js does not encode an utf-8 file name correctly when sending a form with a file attachment
|
||||||
// This is a workaround for that
|
// This is a workaround for that
|
||||||
@@ -40,7 +40,7 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const billAttachment_handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const billAttachment_handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
pdf2canvas(event);
|
findDecodePdf417(event).then(result => console.log(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
return(
|
return(
|
||||||
|
|||||||
Reference in New Issue
Block a user