diff --git a/dist/decoder/decodeData/index.d.ts b/dist/decoder/decodeData/index.d.ts index 732b0f9b..2cb2879f 100644 --- a/dist/decoder/decodeData/index.d.ts +++ b/dist/decoder/decodeData/index.d.ts @@ -10,7 +10,13 @@ export interface ECIChunk { type: Mode.ECI; assignmentNumber: number; } -export declare type Chunks = Array; +export interface StructuredAppend { + type: Mode.StructuredAppend; + currentSequence: number; + totalSequence: number; + parity: number; +} +export declare type Chunks = Array; export interface DecodedQR { text: string; bytes: number[]; @@ -22,5 +28,6 @@ export declare enum Mode { Byte = "byte", Kanji = "kanji", ECI = "eci", + StructuredAppend = "structuredappend" } export declare function decode(data: Uint8ClampedArray, version: number): DecodedQR; diff --git a/dist/jsQR.js b/dist/jsQR.js index 429afeac..869b9f8b 100644 --- a/dist/jsQR.js +++ b/dist/jsQR.js @@ -228,6 +228,7 @@ var GenericGFPoly = /** @class */ (function () { return this.coefficients[this.coefficients.length - 1 - degree]; }; GenericGFPoly.prototype.addOrSubtract = function (other) { + var _a; if (this.isZero()) { return other; } @@ -248,7 +249,6 @@ var GenericGFPoly = /** @class */ (function () { sumDiff[i] = GenericGF_1.addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new GenericGFPoly(this.field, sumDiff); - var _a; }; GenericGFPoly.prototype.multiply = function (scalar) { if (scalar === 0) { @@ -580,7 +580,7 @@ function readCodewords(matrix, version, formatInfo) { // Read columns in pairs, from right to left var readingUp = true; for (var columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) { - if (columnIndex === 6) { + if (columnIndex === 6) { // Skip whole column with vertical alignment pattern; columnIndex--; } for (var i = 0; i < dimension; i++) { @@ -594,7 +594,7 @@ function readCodewords(matrix, version, formatInfo) { bit = !bit; } currentByte = pushBit(bit, currentByte); - if (bitsRead === 8) { + if (bitsRead === 8) { // Whole bytes codewords.push(currentByte); bitsRead = 0; currentByte = 0; @@ -609,7 +609,7 @@ function readCodewords(matrix, version, formatInfo) { function readVersion(matrix) { var dimension = matrix.height; var provisionalVersion = Math.floor((dimension - 17) / 4); - if (provisionalVersion <= 6) { + if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code return version_1.VERSIONS[provisionalVersion - 1]; } var topRightVersionBits = 0; @@ -651,21 +651,21 @@ function readVersion(matrix) { function readFormatInformation(matrix) { var topLeftFormatInfoBits = 0; for (var x = 0; x <= 8; x++) { - if (x !== 6) { + if (x !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits); } } for (var y = 7; y >= 0; y--) { - if (y !== 6) { + if (y !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits); } } var dimension = matrix.height; var topRightBottomRightFormatInfoBits = 0; - for (var y = dimension - 1; y >= dimension - 7; y--) { + for (var y = dimension - 1; y >= dimension - 7; y--) { // bottom left topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits); } - for (var x = dimension - 8; x < dimension; x++) { + for (var x = dimension - 8; x < dimension; x++) { // top right topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits); } var bestDifference = Infinity; @@ -680,7 +680,7 @@ function readFormatInformation(matrix) { bestFormatInfo = formatInfo; bestDifference = difference; } - if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { + if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits); if (difference < bestDifference) { bestFormatInfo = formatInfo; @@ -810,6 +810,7 @@ var Mode; Mode["Byte"] = "byte"; Mode["Kanji"] = "kanji"; Mode["ECI"] = "eci"; + Mode["StructuredAppend"] = "structuredappend"; })(Mode = exports.Mode || (exports.Mode = {})); var ModeByte; (function (ModeByte) { @@ -819,7 +820,7 @@ var ModeByte; ModeByte[ModeByte["Byte"] = 4] = "Byte"; ModeByte[ModeByte["Kanji"] = 8] = "Kanji"; ModeByte[ModeByte["ECI"] = 7] = "ECI"; - // StructuredAppend = 0x3, + ModeByte[ModeByte["StructuredAppend"] = 3] = "StructuredAppend"; // FNC1FirstPosition = 0x5, // FNC1SecondPosition = 0x9, })(ModeByte || (ModeByte = {})); @@ -926,6 +927,7 @@ function decodeKanji(stream, size) { return { bytes: bytes, text: text }; } function decode(data, version) { + var _a, _b, _c, _d; var stream = new BitStream_1.BitStream(data); // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2). var size = version <= 9 ? 0 : version <= 26 ? 1 : 2; @@ -1004,8 +1006,15 @@ function decode(data, version) { text: kanjiResult.text, }); } + else if (mode === ModeByte.StructuredAppend) { + result.chunks.push({ + type: Mode.StructuredAppend, + currentSequence: stream.readBits(4), + totalSequence: stream.readBits(4), + parity: stream.readBits(8), + }); + } } - var _a, _b, _c, _d; } exports.decode = decode; @@ -8126,6 +8135,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); var GenericGF_1 = __webpack_require__(1); var GenericGFPoly_1 = __webpack_require__(2); function runEuclideanAlgorithm(field, a, b, R) { + var _a; // Assume a's degree is >= b's if (a.degree() < b.degree()) { _a = [b, a], a = _a[0], b = _a[1]; @@ -8166,7 +8176,6 @@ function runEuclideanAlgorithm(field, a, b, R) { } var inverse = field.inverse(sigmaTildeAtZero); return [t.multiply(inverse), r.multiply(inverse)]; - var _a; } function findErrorLocations(field, errorLocator) { // This is a direct application of Chien's search @@ -9571,7 +9580,7 @@ var BitMatrix_1 = __webpack_require__(0); function squareToQuadrilateral(p1, p2, p3, p4) { var dx3 = p1.x - p2.x + p3.x - p4.x; var dy3 = p1.y - p2.y + p3.y - p4.y; - if (dx3 === 0 && dy3 === 0) { + if (dx3 === 0 && dy3 === 0) { // Affine return { a11: p2.x - p1.x, a12: p2.y - p1.y, @@ -9677,6 +9686,7 @@ function sum(values) { } // Takes three finder patterns and organizes them into topLeft, topRight, etc function reorderFinderPatterns(pattern1, pattern2, pattern3) { + var _a, _b, _c, _d; // Find distances between pattern centers var oneTwoDistance = distance(pattern1, pattern2); var twoThreeDistance = distance(pattern2, pattern3); @@ -9701,7 +9711,6 @@ function reorderFinderPatterns(pattern1, pattern2, pattern3) { _d = [topRight, bottomLeft], bottomLeft = _d[0], topRight = _d[1]; } return { bottomLeft: bottomLeft, topLeft: topLeft, topRight: topRight }; - var _a, _b, _c, _d; } // Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns function computeDimension(topLeft, topRight, bottomLeft, matrix) { @@ -9791,13 +9800,13 @@ function countBlackWhiteRunTowardsPoint(origin, end, matrix, length) { // along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes // of the black white run. Takes a length which represents the number of switches from black to white to look for. function countBlackWhiteRun(origin, end, matrix, length) { + var _a; var rise = end.y - origin.y; var run = end.x - origin.x; var towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2)); var awayFromEnd = countBlackWhiteRunTowardsPoint(origin, { x: origin.x - run, y: origin.y - rise }, matrix, Math.ceil(length / 2)); var middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel return (_a = awayFromEnd.concat(middleValue)).concat.apply(_a, towardsEnd); - var _a; } // Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the "error" - // that is the amount the run diverges from the expected ratio @@ -9846,6 +9855,7 @@ function scorePattern(point, ratios, matrix) { } } function locate(matrix) { + var _a; var finderPatternQuads = []; var activeFinderPatternQuads = []; var alignmentPatternQuads = []; @@ -9947,6 +9957,7 @@ function locate(matrix) { }) .filter(function (q) { return !!q; }) // Filter out any rejected quads from above .sort(function (a, b) { return a.score - b.score; }) + // Now take the top finder pattern options and try to find 2 other options with a similar size. .map(function (point, i, finderPatterns) { if (i > MAX_FINDERPATTERNS_TO_SEARCH) { return null; @@ -9966,13 +9977,13 @@ function locate(matrix) { if (finderPatternGroups.length === 0) { return null; } - var _a = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _a.topRight, topLeft = _a.topLeft, bottomLeft = _a.bottomLeft; + var _b = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _b.topRight, topLeft = _b.topLeft, bottomLeft = _b.bottomLeft; // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code. // We'll use these to help find the alignment pattern but also later when we do the extraction. var dimension; var moduleSize; try { - (_b = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _b.dimension, moduleSize = _b.moduleSize); + (_a = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _a.dimension, moduleSize = _a.moduleSize); } catch (e) { return null; @@ -10013,7 +10024,6 @@ function locate(matrix) { topLeft: { x: topLeft.x, y: topLeft.y }, topRight: { x: topRight.x, y: topRight.y }, }; - var _b; } exports.locate = locate; diff --git a/docs/jsQR.js b/docs/jsQR.js index 429afeac..869b9f8b 100644 --- a/docs/jsQR.js +++ b/docs/jsQR.js @@ -228,6 +228,7 @@ var GenericGFPoly = /** @class */ (function () { return this.coefficients[this.coefficients.length - 1 - degree]; }; GenericGFPoly.prototype.addOrSubtract = function (other) { + var _a; if (this.isZero()) { return other; } @@ -248,7 +249,6 @@ var GenericGFPoly = /** @class */ (function () { sumDiff[i] = GenericGF_1.addOrSubtractGF(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); } return new GenericGFPoly(this.field, sumDiff); - var _a; }; GenericGFPoly.prototype.multiply = function (scalar) { if (scalar === 0) { @@ -580,7 +580,7 @@ function readCodewords(matrix, version, formatInfo) { // Read columns in pairs, from right to left var readingUp = true; for (var columnIndex = dimension - 1; columnIndex > 0; columnIndex -= 2) { - if (columnIndex === 6) { + if (columnIndex === 6) { // Skip whole column with vertical alignment pattern; columnIndex--; } for (var i = 0; i < dimension; i++) { @@ -594,7 +594,7 @@ function readCodewords(matrix, version, formatInfo) { bit = !bit; } currentByte = pushBit(bit, currentByte); - if (bitsRead === 8) { + if (bitsRead === 8) { // Whole bytes codewords.push(currentByte); bitsRead = 0; currentByte = 0; @@ -609,7 +609,7 @@ function readCodewords(matrix, version, formatInfo) { function readVersion(matrix) { var dimension = matrix.height; var provisionalVersion = Math.floor((dimension - 17) / 4); - if (provisionalVersion <= 6) { + if (provisionalVersion <= 6) { // 6 and under dont have version info in the QR code return version_1.VERSIONS[provisionalVersion - 1]; } var topRightVersionBits = 0; @@ -651,21 +651,21 @@ function readVersion(matrix) { function readFormatInformation(matrix) { var topLeftFormatInfoBits = 0; for (var x = 0; x <= 8; x++) { - if (x !== 6) { + if (x !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(x, 8), topLeftFormatInfoBits); } } for (var y = 7; y >= 0; y--) { - if (y !== 6) { + if (y !== 6) { // Skip timing pattern bit topLeftFormatInfoBits = pushBit(matrix.get(8, y), topLeftFormatInfoBits); } } var dimension = matrix.height; var topRightBottomRightFormatInfoBits = 0; - for (var y = dimension - 1; y >= dimension - 7; y--) { + for (var y = dimension - 1; y >= dimension - 7; y--) { // bottom left topRightBottomRightFormatInfoBits = pushBit(matrix.get(8, y), topRightBottomRightFormatInfoBits); } - for (var x = dimension - 8; x < dimension; x++) { + for (var x = dimension - 8; x < dimension; x++) { // top right topRightBottomRightFormatInfoBits = pushBit(matrix.get(x, 8), topRightBottomRightFormatInfoBits); } var bestDifference = Infinity; @@ -680,7 +680,7 @@ function readFormatInformation(matrix) { bestFormatInfo = formatInfo; bestDifference = difference; } - if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { + if (topLeftFormatInfoBits !== topRightBottomRightFormatInfoBits) { // also try the other option difference = numBitsDiffering(topRightBottomRightFormatInfoBits, bits); if (difference < bestDifference) { bestFormatInfo = formatInfo; @@ -810,6 +810,7 @@ var Mode; Mode["Byte"] = "byte"; Mode["Kanji"] = "kanji"; Mode["ECI"] = "eci"; + Mode["StructuredAppend"] = "structuredappend"; })(Mode = exports.Mode || (exports.Mode = {})); var ModeByte; (function (ModeByte) { @@ -819,7 +820,7 @@ var ModeByte; ModeByte[ModeByte["Byte"] = 4] = "Byte"; ModeByte[ModeByte["Kanji"] = 8] = "Kanji"; ModeByte[ModeByte["ECI"] = 7] = "ECI"; - // StructuredAppend = 0x3, + ModeByte[ModeByte["StructuredAppend"] = 3] = "StructuredAppend"; // FNC1FirstPosition = 0x5, // FNC1SecondPosition = 0x9, })(ModeByte || (ModeByte = {})); @@ -926,6 +927,7 @@ function decodeKanji(stream, size) { return { bytes: bytes, text: text }; } function decode(data, version) { + var _a, _b, _c, _d; var stream = new BitStream_1.BitStream(data); // There are 3 'sizes' based on the version. 1-9 is small (0), 10-26 is medium (1) and 27-40 is large (2). var size = version <= 9 ? 0 : version <= 26 ? 1 : 2; @@ -1004,8 +1006,15 @@ function decode(data, version) { text: kanjiResult.text, }); } + else if (mode === ModeByte.StructuredAppend) { + result.chunks.push({ + type: Mode.StructuredAppend, + currentSequence: stream.readBits(4), + totalSequence: stream.readBits(4), + parity: stream.readBits(8), + }); + } } - var _a, _b, _c, _d; } exports.decode = decode; @@ -8126,6 +8135,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); var GenericGF_1 = __webpack_require__(1); var GenericGFPoly_1 = __webpack_require__(2); function runEuclideanAlgorithm(field, a, b, R) { + var _a; // Assume a's degree is >= b's if (a.degree() < b.degree()) { _a = [b, a], a = _a[0], b = _a[1]; @@ -8166,7 +8176,6 @@ function runEuclideanAlgorithm(field, a, b, R) { } var inverse = field.inverse(sigmaTildeAtZero); return [t.multiply(inverse), r.multiply(inverse)]; - var _a; } function findErrorLocations(field, errorLocator) { // This is a direct application of Chien's search @@ -9571,7 +9580,7 @@ var BitMatrix_1 = __webpack_require__(0); function squareToQuadrilateral(p1, p2, p3, p4) { var dx3 = p1.x - p2.x + p3.x - p4.x; var dy3 = p1.y - p2.y + p3.y - p4.y; - if (dx3 === 0 && dy3 === 0) { + if (dx3 === 0 && dy3 === 0) { // Affine return { a11: p2.x - p1.x, a12: p2.y - p1.y, @@ -9677,6 +9686,7 @@ function sum(values) { } // Takes three finder patterns and organizes them into topLeft, topRight, etc function reorderFinderPatterns(pattern1, pattern2, pattern3) { + var _a, _b, _c, _d; // Find distances between pattern centers var oneTwoDistance = distance(pattern1, pattern2); var twoThreeDistance = distance(pattern2, pattern3); @@ -9701,7 +9711,6 @@ function reorderFinderPatterns(pattern1, pattern2, pattern3) { _d = [topRight, bottomLeft], bottomLeft = _d[0], topRight = _d[1]; } return { bottomLeft: bottomLeft, topLeft: topLeft, topRight: topRight }; - var _a, _b, _c, _d; } // Computes the dimension (number of modules on a side) of the QR Code based on the position of the finder patterns function computeDimension(topLeft, topRight, bottomLeft, matrix) { @@ -9791,13 +9800,13 @@ function countBlackWhiteRunTowardsPoint(origin, end, matrix, length) { // along the line that intersects with the end point. Returns an array of elements, representing the pixel sizes // of the black white run. Takes a length which represents the number of switches from black to white to look for. function countBlackWhiteRun(origin, end, matrix, length) { + var _a; var rise = end.y - origin.y; var run = end.x - origin.x; var towardsEnd = countBlackWhiteRunTowardsPoint(origin, end, matrix, Math.ceil(length / 2)); var awayFromEnd = countBlackWhiteRunTowardsPoint(origin, { x: origin.x - run, y: origin.y - rise }, matrix, Math.ceil(length / 2)); var middleValue = towardsEnd.shift() + awayFromEnd.shift() - 1; // Substract one so we don't double count a pixel return (_a = awayFromEnd.concat(middleValue)).concat.apply(_a, towardsEnd); - var _a; } // Takes in a black white run and an array of expected ratios. Returns the average size of the run as well as the "error" - // that is the amount the run diverges from the expected ratio @@ -9846,6 +9855,7 @@ function scorePattern(point, ratios, matrix) { } } function locate(matrix) { + var _a; var finderPatternQuads = []; var activeFinderPatternQuads = []; var alignmentPatternQuads = []; @@ -9947,6 +9957,7 @@ function locate(matrix) { }) .filter(function (q) { return !!q; }) // Filter out any rejected quads from above .sort(function (a, b) { return a.score - b.score; }) + // Now take the top finder pattern options and try to find 2 other options with a similar size. .map(function (point, i, finderPatterns) { if (i > MAX_FINDERPATTERNS_TO_SEARCH) { return null; @@ -9966,13 +9977,13 @@ function locate(matrix) { if (finderPatternGroups.length === 0) { return null; } - var _a = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _a.topRight, topLeft = _a.topLeft, bottomLeft = _a.bottomLeft; + var _b = reorderFinderPatterns(finderPatternGroups[0].points[0], finderPatternGroups[0].points[1], finderPatternGroups[0].points[2]), topRight = _b.topRight, topLeft = _b.topLeft, bottomLeft = _b.bottomLeft; // Now that we've found the three finder patterns we can determine the blockSize and the size of the QR code. // We'll use these to help find the alignment pattern but also later when we do the extraction. var dimension; var moduleSize; try { - (_b = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _b.dimension, moduleSize = _b.moduleSize); + (_a = computeDimension(topLeft, topRight, bottomLeft, matrix), dimension = _a.dimension, moduleSize = _a.moduleSize); } catch (e) { return null; @@ -10013,7 +10024,6 @@ function locate(matrix) { topLeft: { x: topLeft.x, y: topLeft.y }, topRight: { x: topRight.x, y: topRight.y }, }; - var _b; } exports.locate = locate; diff --git a/src/decoder/decodeData/index.ts b/src/decoder/decodeData/index.ts index f84163f2..49d2f2a3 100644 --- a/src/decoder/decodeData/index.ts +++ b/src/decoder/decodeData/index.ts @@ -17,7 +17,14 @@ export interface ECIChunk { assignmentNumber: number; } -export type Chunks = Array; +export interface StructuredAppend { + type: Mode.StructuredAppend; + currentSequence: number; + totalSequence: number; + parity: number; +} + +export type Chunks = Array; export interface DecodedQR { text: string; @@ -31,6 +38,7 @@ export enum Mode { Byte = "byte", Kanji = "kanji", ECI = "eci", + StructuredAppend = "structuredappend", } enum ModeByte { @@ -40,7 +48,7 @@ enum ModeByte { Byte = 0x4, Kanji = 0x8, ECI = 0x7, - // StructuredAppend = 0x3, + StructuredAppend = 0x3, // FNC1FirstPosition = 0x5, // FNC1SecondPosition = 0x9, } @@ -241,6 +249,13 @@ export function decode(data: Uint8ClampedArray, version: number): DecodedQR { bytes: kanjiResult.bytes, text: kanjiResult.text, }); + } else if (mode === ModeByte.StructuredAppend) { + result.chunks.push({ + type: Mode.StructuredAppend, + currentSequence: stream.readBits(4), + totalSequence: stream.readBits(4), + parity: stream.readBits(8), + }); } } } diff --git a/src/decoder/test-data/structuredAppend.png b/src/decoder/test-data/structuredAppend.png new file mode 100644 index 00000000..f47f0557 Binary files /dev/null and b/src/decoder/test-data/structuredAppend.png differ diff --git a/src/decoder/test.ts b/src/decoder/test.ts index dd3c4f96..3ab21fdd 100644 --- a/src/decoder/test.ts +++ b/src/decoder/test.ts @@ -100,4 +100,24 @@ describe("decode", () => { ], }); }); + it("Supports StructuredAppend chunks", async () => { + const data = await loadBinarized("./src/decoder/test-data/structuredAppend.png"); + console.log(data); + expect(decode(data)).toEqual({ + text: "Hello ", + bytes: [72, 101, 108, 108, 111, 32], + chunks: [ + { + currentSequence: 0, + parity: 32, + totalSequence: 1, + type: "structuredappend" + }, { + type: "byte", + bytes: [72, 101, 108, 108, 111, 32], + text: "Hello ", + }, + ], + }); + }); });