chore: remove legacy @zxing packages and old decoder implementation
Remove @zxing/browser and @zxing/library dependencies as they have been fully replaced by zxing-wasm. Delete old barcodeDecoder.ts file which is no longer used after migration to barcodeDecoderWasm.ts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,370 +0,0 @@
|
|||||||
import { PDFPageProxy } from 'pdfjs-dist';
|
|
||||||
import { BrowserPDF417Reader } from '@zxing/browser';
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Breaks current microtask execution and gives the UI thread a chance to do a re-paint */
|
|
||||||
const yieldToBrowser = (label:string) => new Promise<boolean>((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(true);
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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):BillInfo => {
|
|
||||||
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<HTMLCanvasElement>} the canvas with the image rendered onto it
|
|
||||||
*/
|
|
||||||
const file2canvas = 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 an image from onto a canvas.
|
|
||||||
* @param {String} image - base64 encoded image string
|
|
||||||
* @return {Promise<HTMLCanvasElement>} the canvas with the image rendered onto it
|
|
||||||
*/
|
|
||||||
const image2canvas = async function (imageBase64:string): Promise<HTMLCanvasElement> {
|
|
||||||
|
|
||||||
const canvas = await new Promise<HTMLCanvasElement>((resolve, reject) => {
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
img.src = imageBase64;
|
|
||||||
});
|
|
||||||
|
|
||||||
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<Array<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 canvases: Array<HTMLCanvasElement> = [];
|
|
||||||
|
|
||||||
for(let i = 0; i< pdf.numPages; i++) {
|
|
||||||
const page: PDFPageProxy = await pdf.getPage(i+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;
|
|
||||||
|
|
||||||
canvases.push(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
return(canvases);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DecodeResult = {
|
|
||||||
hub3aText: string,
|
|
||||||
billInfo: BillInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches the given canvas for all PDF417 codes and decodes them.
|
|
||||||
* @param {HTMLCanvasElement} canvas - the canvas to search for PDF417 codes
|
|
||||||
* @return {Promise<Array<DecodeResult> | null>} - an array of decoded results
|
|
||||||
* */
|
|
||||||
const decodeFromCanvas = async (canvas:HTMLCanvasElement): Promise<Array<DecodeResult> | null> => {
|
|
||||||
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 width = canvas.width;
|
|
||||||
const height = canvas.height;
|
|
||||||
|
|
||||||
// Canvas can contain multiple PDF417 codes, so we need to try to find them all
|
|
||||||
// The issue is that `BrowserPDF417Reader` can only decode one code at a time
|
|
||||||
// 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];
|
|
||||||
|
|
||||||
let bestResult: Array<DecodeResult>|null = null;
|
|
||||||
|
|
||||||
for(let splitIx = 0; splitIx < splits.length; splitIx++) {
|
|
||||||
const split = splits[splitIx];
|
|
||||||
|
|
||||||
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<DecodeResult> = [];
|
|
||||||
|
|
||||||
// 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 result = await codeReader.decodeFromCanvas(sectionCanvas);
|
|
||||||
const hub3aText = result.getText()
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Finds PDF417 code within a base64 encoded image and decodes it */
|
|
||||||
export const decodeFromImage = async (imageBase64:string): Promise<DecodeResult|null> => {
|
|
||||||
const canvas = await image2canvas(imageBase64);
|
|
||||||
|
|
||||||
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);
|
|
||||||
const hub3aText = result.getText();
|
|
||||||
|
|
||||||
return({
|
|
||||||
hub3aText,
|
|
||||||
billInfo: parseHubText(hub3aText)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Finds PDF417 code within a file and decodes it */
|
|
||||||
const decodeFromFile = async (file:File): Promise<DecodeResult[]|null> => {
|
|
||||||
switch(file.type) {
|
|
||||||
case 'image/png':
|
|
||||||
case 'image/jpeg':
|
|
||||||
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);
|
|
||||||
})))
|
|
||||||
// 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);
|
|
||||||
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<Array<DecodeResult>|null> {
|
|
||||||
const file = (event.target as HTMLInputElement).files?.[0];
|
|
||||||
|
|
||||||
if(!file) {
|
|
||||||
console.error('No file was selected.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(await decodeFromFile(file));
|
|
||||||
}
|
|
||||||
42
package-lock.json
generated
42
package-lock.json
generated
@@ -15,8 +15,6 @@
|
|||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@types/iban": "^0.0.35",
|
"@types/iban": "^0.0.35",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
"@zxing/browser": "^0.1.5",
|
|
||||||
"@zxing/library": "^0.21.3",
|
|
||||||
"autoprefixer": "10.4.15",
|
"autoprefixer": "10.4.15",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
@@ -2219,38 +2217,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@zxing/browser": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.1.5.tgz",
|
|
||||||
"integrity": "sha512-4Lmrn/il4+UNb87Gk8h1iWnhj39TASEHpd91CwwSJtY5u+wa0iH9qS0wNLAWbNVYXR66WmT5uiMhZ7oVTrKfxw==",
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@zxing/text-encoding": "^0.9.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@zxing/library": "^0.21.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@zxing/library": {
|
|
||||||
"version": "0.21.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz",
|
|
||||||
"integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"ts-custom-error": "^3.2.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.4.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@zxing/text-encoding": "~0.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@zxing/text-encoding": {
|
|
||||||
"version": "0.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
|
|
||||||
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/abbrev": {
|
"node_modules/abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
@@ -8191,14 +8157,6 @@
|
|||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ts-custom-error": {
|
|
||||||
"version": "3.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
|
|
||||||
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ts-interface-checker": {
|
"node_modules/ts-interface-checker": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@types/iban": "^0.0.35",
|
"@types/iban": "^0.0.35",
|
||||||
"@types/node": "20.5.7",
|
"@types/node": "20.5.7",
|
||||||
"@zxing/browser": "^0.1.5",
|
|
||||||
"@zxing/library": "^0.21.3",
|
|
||||||
"autoprefixer": "10.4.15",
|
"autoprefixer": "10.4.15",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user