diff --git a/example/file_input.html b/example/file_input.html index 93f3083..d45abd5 100644 --- a/example/file_input.html +++ b/example/file_input.html @@ -53,6 +53,7 @@ Codabar Interleaved 2 of 5 Standard 2 of 5 + Code 93 diff --git a/example/live_w_locator.html b/example/live_w_locator.html index 289f268..491af8e 100644 --- a/example/live_w_locator.html +++ b/example/live_w_locator.html @@ -45,6 +45,7 @@ Codabar Interleaved 2 of 5 Standard 2 of 5 + Code 93 diff --git a/example/live_w_locator.js b/example/live_w_locator.js index cd49f37..f08b0d1 100644 --- a/example/live_w_locator.js +++ b/example/live_w_locator.js @@ -3,17 +3,17 @@ $(function() { capture: true, capacity: 20, blacklist: [{ - code: "9577149002", format: "2of5" + code: "WIWV8ETQZ1", format: "code_93" }, { - code: "5776158811", format: "2of5" + code: "EH3C-%GU23RK3", format: "code_93" }, { - code: "0463381455", format: "2of5" + code: "O308SIHQOXN5SA/PJ", format: "code_93" }, { - code: "3261594101", format: "2of5" + code: "DG7Q$TV8JQ/EN", format: "code_93" }, { - code: "6730705801", format: "2of5" + code: "VOFD1DB5A.1F6QU", format: "code_93" }, { - code: "8568166929", format: "2of5" + code: "4SO64P4X8 U4YUU1T-", format: "code_93" }], filter: function(codeResult) { // only store results which match this constraint diff --git a/example/static_images.html b/example/static_images.html index 23fd49f..dfb4c31 100644 --- a/example/static_images.html +++ b/example/static_images.html @@ -47,6 +47,7 @@ I2of5 Interleaved 2 of 5 Standard 2 of 5 + Code 93 diff --git a/src/decoder/barcode_decoder.js b/src/decoder/barcode_decoder.js index 8bbcc02..e5dbecf 100644 --- a/src/decoder/barcode_decoder.js +++ b/src/decoder/barcode_decoder.js @@ -12,6 +12,7 @@ import EAN5Reader from '../reader/ean_5_reader'; import UPCEReader from '../reader/upc_e_reader'; import I2of5Reader from '../reader/i2of5_reader'; import TwoOfFiveReader from '../reader/2of5_reader'; +import Code93Reader from '../reader/code_93_reader'; const READERS = { code_128_reader: Code128Reader, @@ -26,6 +27,7 @@ const READERS = { upc_e_reader: UPCEReader, i2of5_reader: I2of5Reader, '2of5_reader': TwoOfFiveReader, + code_93_reader: Code93Reader }; export default { create: function(config, inputImageWrapper) { diff --git a/src/reader/code_93_reader.js b/src/reader/code_93_reader.js new file mode 100644 index 0000000..42f8646 --- /dev/null +++ b/src/reader/code_93_reader.js @@ -0,0 +1,278 @@ +import BarcodeReader from './barcode_reader'; +import ArrayHelper from '../common/array_helper'; + +function Code93Reader() { + BarcodeReader.call(this); +} + +const ALPHABETH_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*"; + +var properties = { + ALPHABETH_STRING: {value: ALPHABETH_STRING}, + ALPHABET: {value: ALPHABETH_STRING.split('').map(char => char.charCodeAt(0))}, + CHARACTER_ENCODINGS: {value: [ + 0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A, + 0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134, + 0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6, + 0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, 0x12E, 0x1D4, 0x1D2, 0x1CA, + 0x16E, 0x176, 0x1AE, 0x126, 0x1DA, 0x1D6, 0x132, 0x15E + ]}, + ASTERISK: {value: 0x15E}, + FORMAT: {value: "code_93", writeable: false} +}; + +Code93Reader.prototype = Object.create(BarcodeReader.prototype, properties); +Code93Reader.prototype.constructor = Code93Reader; + +Code93Reader.prototype._toCounters = function(start, counter) { + var self = this, + numCounters = counter.length, + end = self._row.length, + isWhite = !self._row[start], + i, + counterPos = 0; + + ArrayHelper.init(counter, 0); + + for ( i = start; i < end; i++) { + if (self._row[i] ^ isWhite) { + counter[counterPos]++; + } else { + counterPos++; + if (counterPos === numCounters) { + break; + } else { + counter[counterPos] = 1; + isWhite = !isWhite; + } + } + } + + return counter; +}; + +Code93Reader.prototype._decode = function() { + var self = this, + counters = [0, 0, 0, 0, 0, 0], + result = [], + start = self._findStart(), + decodedChar, + lastStart, + pattern, + nextStart; + + if (!start) { + return null; + } + nextStart = self._nextSet(self._row, start.end); + + do { + counters = self._toCounters(nextStart, counters); + pattern = self._toPattern(counters); + if (pattern < 0) { + return null; + } + decodedChar = self._patternToChar(pattern); + if (decodedChar < 0){ + return null; + } + result.push(decodedChar); + lastStart = nextStart; + nextStart += ArrayHelper.sum(counters); + nextStart = self._nextSet(self._row, nextStart); + } while (decodedChar !== '*'); + result.pop(); + + if (!result.length) { + return null; + } + + if (!self._verifyEnd(lastStart, nextStart, counters)) { + return null; + } + + if (!self._verifyChecksums(result)) { + return null; + } + + result = result.slice(0, result.length - 2); + if ((result = self._decodeExtended(result)) === null) { + return null; + }; + + return { + code: result.join(""), + start: start.start, + end: nextStart, + startInfo: start, + decodedCodes: result + }; +}; + +Code93Reader.prototype._verifyEnd = function(lastStart, nextStart) { + if (lastStart === nextStart || !this._row[nextStart]) { + return false; + } + return true; +}; + +Code93Reader.prototype._patternToChar = function(pattern) { + var i, + self = this; + + for (i = 0; i < self.CHARACTER_ENCODINGS.length; i++) { + if (self.CHARACTER_ENCODINGS[i] === pattern) { + return String.fromCharCode(self.ALPHABET[i]); + } + } + return -1; +}; + +Code93Reader.prototype._toPattern = function(counters) { + const numCounters = counters.length; + let pattern = 0; + let sum = 0; + for (let i = 0; i < numCounters; i++) { + sum += counters[i]; + } + + for (let i = 0; i < numCounters; i++) { + let normalized = Math.round(counters[i] * 9 / sum); + if (normalized < 1 || normalized > 4) { + return -1; + } + if ((i & 1) === 0) { + for (let j = 0; j < normalized; j++) { + pattern = (pattern << 1) | 1; + } + } else { + pattern <<= normalized; + } + } + + return pattern; +}; + +Code93Reader.prototype._findStart = function() { + var self = this, + offset = self._nextSet(self._row), + patternStart = offset, + counter = [0, 0, 0, 0, 0, 0], + counterPos = 0, + isWhite = false, + i, + j, + whiteSpaceMustStart; + + for ( i = offset; i < self._row.length; i++) { + if (self._row[i] ^ isWhite) { + counter[counterPos]++; + } else { + if (counterPos === counter.length - 1) { + // find start pattern + if (self._toPattern(counter) === self.ASTERISK) { + whiteSpaceMustStart = Math.floor(Math.max(0, patternStart - ((i - patternStart) / 4))); + if (self._matchRange(whiteSpaceMustStart, patternStart, 0)) { + return { + start: patternStart, + end: i + }; + } + } + + patternStart += counter[0] + counter[1]; + for ( j = 0; j < 4; j++) { + counter[j] = counter[j + 2]; + } + counter[4] = 0; + counter[5] = 0; + counterPos--; + } else { + counterPos++; + } + counter[counterPos] = 1; + isWhite = !isWhite; + } + } + return null; +}; + +Code93Reader.prototype._decodeExtended = function(charArray) { + const length = charArray.length; + const result = []; + for (let i = 0; i < length; i++) { + const char = charArray[i]; + if (char >= 'a' && char <= 'd') { + if (i > (length - 2)) { + return null; + } + const nextChar = charArray[++i]; + const nextCharCode = nextChar.charCodeAt(0); + let decodedChar; + switch (char) { + case 'a': + if (nextChar >= 'A' && nextChar <= 'Z') { + decodedChar = String.fromCharCode(nextCharCode - 64); + } else { + return null; + } + break; + case 'b': + if (nextChar >= 'A' && nextChar <= 'E') { + decodedChar = String.fromCharCode(nextCharCode - 38); + } else if (nextChar >= 'F' && nextChar <= 'J') { + decodedChar = String.fromCharCode(nextCharCode - 11); + } else if (nextChar >= 'K' && nextChar <= 'O') { + decodedChar = String.fromCharCode(nextCharCode + 16); + } else if (nextChar >= 'P' && nextChar <= 'S') { + decodedChar = String.fromCharCode(nextCharCode + 43); + } else if (nextChar >= 'T' && nextChar <= 'Z') { + decodedChar = String.fromCharCode(127); + } else { + return null; + } + break; + case 'c': + if (nextChar >= 'A' && nextChar <= 'O') { + decodedChar = String.fromCharCode(nextCharCode - 32); + } else if (nextChar == 'Z') { + decodedChar = ':'; + } else { + return null; + } + break; + case 'd': + if (nextChar >= 'A' && nextChar <= 'Z') { + decodedChar = String.fromCharCode(nextCharCode + 32); + } else { + return null; + } + break; + } + result.push(decodedChar); + } else { + result.push(char); + } + } + return result; +}; + +Code93Reader.prototype._verifyChecksums = function(charArray) { + return this._matchCheckChar(charArray, charArray.length - 2, 20) + && this._matchCheckChar(charArray, charArray.length - 1, 15); +}; + +Code93Reader.prototype._matchCheckChar = function(charArray, index, maxWeight) { + const arrayToCheck = charArray.slice(0, index); + const length = arrayToCheck.length; + const weightedSums = arrayToCheck.reduce((sum, char, i) => { + const weight = (((i * -1) + (length - 1)) % maxWeight) + 1; + const value = this.ALPHABET.indexOf(char.charCodeAt(0)); + return sum + (weight * value); + }, 0); + + const checkChar = this.ALPHABET[(weightedSums % 47)]; + return checkChar === charArray[index].charCodeAt(0); +}; + +export default Code93Reader; diff --git a/test/fixtures/code_93/image-001.jpg b/test/fixtures/code_93/image-001.jpg new file mode 100644 index 0000000..795196b Binary files /dev/null and b/test/fixtures/code_93/image-001.jpg differ diff --git a/test/fixtures/code_93/image-002.jpg b/test/fixtures/code_93/image-002.jpg new file mode 100644 index 0000000..5bad400 Binary files /dev/null and b/test/fixtures/code_93/image-002.jpg differ diff --git a/test/fixtures/code_93/image-003.jpg b/test/fixtures/code_93/image-003.jpg new file mode 100644 index 0000000..2a523e6 Binary files /dev/null and b/test/fixtures/code_93/image-003.jpg differ diff --git a/test/fixtures/code_93/image-004.jpg b/test/fixtures/code_93/image-004.jpg new file mode 100644 index 0000000..39758ec Binary files /dev/null and b/test/fixtures/code_93/image-004.jpg differ diff --git a/test/fixtures/code_93/image-005.jpg b/test/fixtures/code_93/image-005.jpg new file mode 100644 index 0000000..227f6fe Binary files /dev/null and b/test/fixtures/code_93/image-005.jpg differ diff --git a/test/fixtures/code_93/image-006.jpg b/test/fixtures/code_93/image-006.jpg new file mode 100644 index 0000000..f4622bc Binary files /dev/null and b/test/fixtures/code_93/image-006.jpg differ diff --git a/test/fixtures/code_93/image-007.jpg b/test/fixtures/code_93/image-007.jpg new file mode 100644 index 0000000..6bf7d02 Binary files /dev/null and b/test/fixtures/code_93/image-007.jpg differ diff --git a/test/fixtures/code_93/image-008.jpg b/test/fixtures/code_93/image-008.jpg new file mode 100644 index 0000000..21021b6 Binary files /dev/null and b/test/fixtures/code_93/image-008.jpg differ diff --git a/test/fixtures/code_93/image-009.jpg b/test/fixtures/code_93/image-009.jpg new file mode 100644 index 0000000..1250685 Binary files /dev/null and b/test/fixtures/code_93/image-009.jpg differ diff --git a/test/fixtures/code_93/image-010.jpg b/test/fixtures/code_93/image-010.jpg new file mode 100644 index 0000000..8f6a20e Binary files /dev/null and b/test/fixtures/code_93/image-010.jpg differ diff --git a/test/integration/integration.spec.js b/test/integration/integration.spec.js index 9153e8a..8e0cf0b 100644 --- a/test/integration/integration.spec.js +++ b/test/integration/integration.spec.js @@ -341,4 +341,41 @@ describe('decodeSingle', function () { _runTestSet(testSet, config); }); + + describe("code_93", function() { + var config = config = { + inputStream: { + size: 800, + singleChannel: false + }, + locator: { + patchSize: "large", + halfSample: true + }, + numOfWorkers: 0, + decoder: { + readers: ["code_93_reader"] + }, + locate: true, + src: null + }, + testSet = [ + {"name": "image-001.jpg", "result": "WIWV8ETQZ1"}, + {"name": "image-002.jpg", "result": "EH3C-%GU23RK3"}, + {"name": "image-003.jpg", "result": "O308SIHQOXN5SA/PJ"}, + {"name": "image-004.jpg", "result": "DG7Q$TV8JQ/EN"}, + {"name": "image-005.jpg", "result": "DG7Q$TV8JQ/EN"}, + {"name": "image-006.jpg", "result": "O308SIHQOXN5SA/PJ"}, + {"name": "image-007.jpg", "result": "VOFD1DB5A.1F6QU"}, + {"name": "image-008.jpg", "result": "WIWV8ETQZ1"}, + {"name": "image-009.jpg", "result": "4SO64P4X8 U4YUU1T-"}, + {"name": "image-010.jpg", "result": "4SO64P4X8 U4YUU1T-"} + ]; + + testSet.forEach(function(sample) { + sample.format = "code_93"; + }); + + _runTestSet(testSet, config); + }); });