diff --git a/src/barcode.js b/src/barcode.js index da76898..c6342ff 100644 --- a/src/barcode.js +++ b/src/barcode.js @@ -25,8 +25,14 @@ async function ensureReady() { await readyPromise; } +// Bisheriges Verhalten (Code-128 und DataMatrix), falls der Aufrufer keine +// eigene Liste von Codearten angibt - main.js reicht seit den Scan-Funktionen +// (siehe scan-modes.js) stets eine eigene Liste durch, aber ohne Angabe +// bleibt der Adapter unveraendert nutzbar (z. B. fuer kuenftige Aufrufer). +const DEFAULT_FORMATS = ['Code128', 'DataMatrix']; + /** - * Dekodiert Code-128 und DataMatrix aus einem Einzelbild. + * Dekodiert Barcodes aus einem Einzelbild. * Barcode-Dekodierung ist exakt - deshalb ist sie die bevorzugte Quelle * gegenueber der fehleranfaelligen Texterkennung. * @@ -35,12 +41,14 @@ async function ensureReady() { * Ausgang und kein Fehler. * * @param {ImageData} imageData Einzelbild aus der Kamera. + * @param {string[]} [formats] Zu suchende Codearten (zxing-wasm-Formatnamen, + * siehe scan-modes.js). Ohne Angabe wie bisher: Code128 und DataMatrix. * @returns {Promise} */ -export async function decodeBarcodes(imageData) { +export async function decodeBarcodes(imageData, formats = DEFAULT_FORMATS) { await ensureReady(); const results = await readBarcodes(imageData, { - formats: ['Code128', 'DataMatrix'], + formats, tryHarder: true, maxNumberOfSymbols: 4, }); diff --git a/src/scan-recognition.js b/src/scan-recognition.js new file mode 100644 index 0000000..c2f03d1 --- /dev/null +++ b/src/scan-recognition.js @@ -0,0 +1,34 @@ +// Baut die Adapter fuer recognize() (pipeline.js) anhand der gewaehlten +// Scan-Funktion (siehe scan-modes.js). Reines Modul ohne Browser-Zugriff - +// direkt unter Node pruefbar. pipeline.js selbst bleibt unveraendert: die +// Funktionen unterscheiden sich ausschliesslich darin, welche Adapter +// hineingereicht werden. +// +// Funktionen 1/2 (laufende Codesuche): echte Barcode-Dekodierung mit den zur +// Funktion gehoerenden Codearten; die Texterkennung liefert sofort leeren +// Text, ohne dass runOcr ueberhaupt aufgerufen wird. +// Funktion 3 (Text erkennen): echte Texterkennung; die Barcode-Dekodierung +// liefert sofort eine leere Liste, ohne dass decodeBarcodes ueberhaupt +// aufgerufen wird. + +import { getScanMode } from './scan-modes.js'; + +/** + * @param {string} modeId Kennung aus scan-modes.js. + * @param {{decodeBarcodes: Function, runOcr: Function}} adapters echte, + * browser-gebundene Adapter (aus barcode.js / ocr.js). + * @returns {{decodeBarcodes: Function, runOcr: Function}} an recognize() + * durchzureichende Adapter, passend zur gewaehlten Funktion. + */ +export function buildRecognitionAdapters(modeId, adapters) { + const mode = getScanMode(modeId); + + return { + decodeBarcodes: mode.useOcr + ? async () => [] + : (imageData) => adapters.decodeBarcodes(imageData, mode.barcodeFormats), + runOcr: mode.useOcr + ? adapters.runOcr + : async () => '', + }; +} diff --git a/test/scan-recognition.test.js b/test/scan-recognition.test.js new file mode 100644 index 0000000..98f779b --- /dev/null +++ b/test/scan-recognition.test.js @@ -0,0 +1,79 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { buildRecognitionAdapters } from '../src/scan-recognition.js'; +import { recognize } from '../src/pipeline.js'; + +test('Funktion 1: Texterkennung wird nicht aufgerufen, auch wenn kein Code gefunden wird', async () => { + let ocrCalled = false; + const adapters = buildRecognitionAdapters('barcode', { + decodeBarcodes: async () => [], + runOcr: async () => { ocrCalled = true; return 'irgendein Text'; }, + }); + const result = await recognize({}, adapters); + assert.equal(ocrCalled, false, 'runOcr darf in Funktion 1 nie aufgerufen werden'); + assert.equal(result.confidence, 'red'); +}); + +test('Funktion 2: Texterkennung wird nicht aufgerufen, auch wenn kein Code gefunden wird', async () => { + let ocrCalled = false; + const adapters = buildRecognitionAdapters('qrcode', { + decodeBarcodes: async () => [], + runOcr: async () => { ocrCalled = true; return 'irgendein Text'; }, + }); + const result = await recognize({}, adapters); + assert.equal(ocrCalled, false, 'runOcr darf in Funktion 2 nie aufgerufen werden'); + assert.equal(result.confidence, 'red'); +}); + +test('Funktion 3: Barcode-Dekodierung wird nicht aufgerufen', async () => { + let barcodeCalled = false; + const adapters = buildRecognitionAdapters('ocr', { + decodeBarcodes: async () => { barcodeCalled = true; return []; }, + runOcr: async () => '64GB 4DRx4 PC4-2400T-LD1-11-MC0 M386A8K40BM1-CRC4Y 1908', + }); + const result = await recognize({}, adapters); + assert.equal(barcodeCalled, false, 'decodeBarcodes darf in Funktion 3 nie aufgerufen werden'); + assert.equal(result.source, 'ocr'); + assert.equal(result.confidence, 'yellow'); +}); + +test('Funktion 1: ein gelesener Strichcode fuehrt zu Gruen', async () => { + const adapters = buildRecognitionAdapters('barcode', { + decodeBarcodes: async () => ['M386A8K40BM1-CRC4Y'], + runOcr: async () => '', + }); + const result = await recognize({}, adapters); + assert.equal(result.source, 'barcode'); + assert.equal(result.confidence, 'green'); +}); + +test('Funktion 3: kein brauchbarer Text fuehrt zu Rot, nicht zu Gruen', async () => { + const adapters = buildRecognitionAdapters('ocr', { + decodeBarcodes: async () => { throw new Error('darf nicht aufgerufen werden'); }, + runOcr: async () => 'Made in Philippines', + }); + const result = await recognize({}, adapters); + assert.equal(result.source, 'none'); + assert.equal(result.confidence, 'red'); +}); + +test('decodeBarcodes bekommt die Codearten der jeweiligen Funktion uebergeben', async () => { + let receivedFormats = null; + const adapters = buildRecognitionAdapters('qrcode', { + decodeBarcodes: async (_imageData, formats) => { receivedFormats = formats; return []; }, + runOcr: async () => '', + }); + await adapters.decodeBarcodes({}); + assert.deepEqual(receivedFormats, ['QRCode', 'MicroQRCode', 'RMQRCode']); +}); + +test('Funktion 3 uebergibt keine Codearten, weil decodeBarcodes gar nicht aufgerufen wird', async () => { + let called = false; + const adapters = buildRecognitionAdapters('ocr', { + decodeBarcodes: async () => { called = true; return []; }, + runOcr: async () => '', + }); + const codes = await adapters.decodeBarcodes({}); + assert.deepEqual(codes, []); + assert.equal(called, false); +});