@sythos/js_barcode_universal 1.2.5 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_USAGE.md +33 -0
- package/LICENSE +11 -2
- package/NOTICE.md +10 -4
- package/README.md +601 -465
- package/bundle/sythos-barcode.esm.js +4669 -74
- package/bundle/sythos-barcode.js +4575 -72
- package/examples/create.html +1010 -730
- package/examples/read.html +1 -0
- package/licenses/README.md +40 -58
- package/licenses/aztec-code.license +15 -13
- package/licenses/aztec-rune.license +76 -0
- package/licenses/codabar.license +18 -15
- package/licenses/code-11.license +11 -9
- package/licenses/code-128.license +10 -8
- package/licenses/code-39.license +10 -8
- package/licenses/code-93.license +16 -14
- package/licenses/compact-pdf417.license +76 -0
- package/licenses/data-matrix.license +15 -13
- package/licenses/ean-13.license +10 -8
- package/licenses/ean-2.license +74 -0
- package/licenses/ean-5.license +74 -0
- package/licenses/ean-8.license +10 -8
- package/licenses/frameqr.license +84 -0
- package/licenses/gs1-128.license +10 -8
- package/licenses/gs1-databar.license +81 -0
- package/licenses/isbn.license +10 -8
- package/licenses/itf-14.license +10 -8
- package/licenses/itf.license +9 -7
- package/licenses/micro-qr.license +79 -0
- package/licenses/micropdf417.license +52 -67
- package/licenses/msi-plessey.license +13 -11
- package/licenses/pdf417.license +78 -37
- package/licenses/pharmacode.license +14 -12
- package/licenses/qr-code.license +9 -7
- package/licenses/rmqr.license +79 -0
- package/licenses/upc-a.license +12 -10
- package/licenses/upc-e.license +10 -8
- package/package.json +109 -88
- package/src/aztec/decoder.js +1 -0
- package/src/aztec/detector.js +1 -0
- package/src/aztec/encoder.js +1 -0
- package/src/aztec/high-level.js +1 -0
- package/src/aztec/index.js +1 -0
- package/src/aztec/tables.js +1 -0
- package/src/aztecrune/decoder.js +156 -0
- package/src/aztecrune/detector.js +167 -0
- package/src/aztecrune/encoder.js +122 -0
- package/src/aztecrune/index.js +50 -0
- package/src/aztecrune/tables.js +138 -0
- package/src/compactpdf417/decoder.js +129 -0
- package/src/compactpdf417/detector.js +140 -0
- package/src/compactpdf417/encoder.js +141 -0
- package/src/compactpdf417/index.js +22 -0
- package/src/compactpdf417/tables.js +174 -0
- package/src/core/bit-buffer.js +1 -0
- package/src/core/bit-matrix.js +1 -0
- package/src/core/errors.js +1 -0
- package/src/core/galois-field.js +1 -0
- package/src/core/index.js +1 -0
- package/src/core/reed-solomon.js +1 -0
- package/src/databar/codec.js +182 -0
- package/src/databar/decoder.js +115 -0
- package/src/databar/encoder.js +97 -0
- package/src/databar/gs1.js +178 -0
- package/src/databar/index.js +69 -0
- package/src/databar/patterns.js +144 -0
- package/src/databar/tables.js +125 -0
- package/src/datamatrix/decoder.js +263 -262
- package/src/datamatrix/detector.js +226 -225
- package/src/datamatrix/encoder.js +192 -191
- package/src/datamatrix/index.js +43 -42
- package/src/datamatrix/tables.js +124 -123
- package/src/frameqr/decoder.js +240 -0
- package/src/frameqr/detector.js +193 -0
- package/src/frameqr/encoder.js +157 -0
- package/src/frameqr/index.js +43 -0
- package/src/frameqr/tables.js +271 -0
- package/src/image/binarizer.js +1 -0
- package/src/image/grid-sampler.js +1 -0
- package/src/image/index.js +1 -0
- package/src/image/luminance.js +1 -0
- package/src/image/perspective.js +1 -0
- package/src/index.js +155 -2
- package/src/micropdf417/compaction.js +1 -0
- package/src/micropdf417/decoder.js +1 -0
- package/src/micropdf417/detector.js +1 -0
- package/src/micropdf417/encoder.js +1 -0
- package/src/micropdf417/error-correction.js +1 -0
- package/src/micropdf417/index.js +1 -0
- package/src/micropdf417/tables.js +1 -0
- package/src/microqr/decoder.js +246 -0
- package/src/microqr/detector.js +356 -0
- package/src/microqr/encoder.js +270 -0
- package/src/microqr/index.js +37 -0
- package/src/microqr/tables.js +317 -0
- package/src/oned/addons.js +421 -0
- package/src/oned/index.js +77 -59
- package/src/oned/patterns.js +1 -0
- package/src/oned/reader.js +1 -0
- package/src/oned/writers.js +1 -0
- package/src/pdf417/compaction.js +1 -0
- package/src/pdf417/decoder.js +1 -0
- package/src/pdf417/detector.js +1 -0
- package/src/pdf417/encoder.js +1 -0
- package/src/pdf417/error-correction.js +1 -0
- package/src/pdf417/index.js +5 -0
- package/src/pdf417/tables.js +1 -0
- package/src/qr/decoder.js +1 -0
- package/src/qr/detector.js +1 -0
- package/src/qr/encoder.js +1 -0
- package/src/qr/index.js +1 -0
- package/src/qr/tables.js +1 -0
- package/src/render/image-data.js +1 -0
- package/src/render/index.js +1 -0
- package/src/render/options.js +1 -0
- package/src/render/png.js +1 -0
- package/src/render/svg.js +1 -0
- package/src/render/webgl.js +1 -0
- package/src/render/webgpu.js +1 -0
- package/src/rmqr/decoder.js +102 -0
- package/src/rmqr/detector.js +91 -0
- package/src/rmqr/encoder.js +173 -0
- package/src/rmqr/index.js +38 -0
- package/src/rmqr/tables.js +155 -0
package/bundle/sythos-barcode.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Sythos Barcode Suite v1.
|
|
2
|
+
* Sythos Barcode Suite v1.4.1
|
|
3
3
|
*
|
|
4
4
|
* MIT License
|
|
5
5
|
*
|
|
@@ -1823,6 +1823,413 @@ __exports.encodeMSI = encodeMSI;
|
|
|
1823
1823
|
__exports.encodePharmacode = encodePharmacode;
|
|
1824
1824
|
};
|
|
1825
1825
|
|
|
1826
|
+
__modules["oned/addons.js"] = function (__require, __exports) {
|
|
1827
|
+
/**
|
|
1828
|
+
* UPC/EAN two- and five-digit supplements.
|
|
1829
|
+
*
|
|
1830
|
+
* Supplements are small symbols printed to the right of an EAN/UPC symbol.
|
|
1831
|
+
* They use the ordinary EAN odd/even digit patterns, a 1011 start guard and
|
|
1832
|
+
* 01 separators between digits. EAN-2 selects its parity from the numeric
|
|
1833
|
+
* value modulo four. EAN-5 derives a checksum from the five digits and uses
|
|
1834
|
+
* that value to select one of ten parity rows.
|
|
1835
|
+
*
|
|
1836
|
+
* This module intentionally keeps supplements separate from the base EAN/UPC
|
|
1837
|
+
* writers. `composeEANAddon` and the `*WithAddon` helpers are opt-in; existing
|
|
1838
|
+
* EAN/UPC APIs continue to return their original one-row matrices unchanged.
|
|
1839
|
+
*
|
|
1840
|
+
* @module oned/addons
|
|
1841
|
+
*/
|
|
1842
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
1843
|
+
const { EncodeError, FormatError } = __require("core/errors.js");
|
|
1844
|
+
const { EAN_L, EAN_G } = __require("oned/patterns.js");
|
|
1845
|
+
const { encodeEAN13, encodeEAN8, encodeUPCA, encodeUPCE } = __require("oned/writers.js");
|
|
1846
|
+
|
|
1847
|
+
/** EAN-2 parity rows, indexed by the two-digit value modulo four. */
|
|
1848
|
+
const EAN2_PARITY = Object.freeze([
|
|
1849
|
+
'AA', 'AB', 'BA', 'BB',
|
|
1850
|
+
]);
|
|
1851
|
+
|
|
1852
|
+
/** EAN-5 parity rows, indexed by the supplemental checksum. */
|
|
1853
|
+
const EAN5_PARITY = Object.freeze([
|
|
1854
|
+
'BBAAA', 'BABAA', 'BAABA', 'BAAAB', 'ABBAA',
|
|
1855
|
+
'AABBA', 'AAABB', 'ABABA', 'ABAAB', 'AABAB',
|
|
1856
|
+
]);
|
|
1857
|
+
|
|
1858
|
+
/** Start guard used by both UPC/EAN supplemental symbols. */
|
|
1859
|
+
const EAN_ADDON_START = '1011';
|
|
1860
|
+
|
|
1861
|
+
/** Separator inserted between adjacent supplemental digits. */
|
|
1862
|
+
const EAN_ADDON_SEPARATOR = '01';
|
|
1863
|
+
const EAN2_WIDTH = EAN_ADDON_START.length + 2 * 7 + EAN_ADDON_SEPARATOR.length;
|
|
1864
|
+
const EAN5_WIDTH = EAN_ADDON_START.length + 5 * 7 + EAN_ADDON_SEPARATOR.length * 4;
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* @param {unknown} value
|
|
1868
|
+
* @param {number} length
|
|
1869
|
+
* @param {string} format
|
|
1870
|
+
* @returns {string}
|
|
1871
|
+
*/
|
|
1872
|
+
function normalizeDigits(value, length, format) {
|
|
1873
|
+
const digits = String(value);
|
|
1874
|
+
if (!new RegExp(`^[0-9]{${length}}$`).test(digits)) {
|
|
1875
|
+
throw new EncodeError(`${format}: payload must contain exactly ${length} digits`);
|
|
1876
|
+
}
|
|
1877
|
+
return digits;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
/**
|
|
1881
|
+
* EAN-5 supplemental checksum.
|
|
1882
|
+
*
|
|
1883
|
+
* The first, third and fifth digits carry weight three; the second and fourth
|
|
1884
|
+
* carry weight nine. The checksum is not printed as a sixth digit: it selects
|
|
1885
|
+
* the odd/even parity row for the five encoded digits.
|
|
1886
|
+
*
|
|
1887
|
+
* @param {string|number} value Five digits.
|
|
1888
|
+
* @returns {number} Checksum value, 0-9.
|
|
1889
|
+
*/
|
|
1890
|
+
function ean5Checksum(value) {
|
|
1891
|
+
const digits = normalizeDigits(value, 5, 'EAN-5');
|
|
1892
|
+
const weighted =
|
|
1893
|
+
3 * (Number(digits[0]) + Number(digits[2]) + Number(digits[4])) +
|
|
1894
|
+
9 * (Number(digits[1]) + Number(digits[3]));
|
|
1895
|
+
return weighted % 10;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/** Alias for callers that use the EAN family check-digit terminology. */
|
|
1899
|
+
const ean5CheckDigit = ean5Checksum;
|
|
1900
|
+
|
|
1901
|
+
/**
|
|
1902
|
+
* Resolve the EAN-2 parity row for a payload.
|
|
1903
|
+
*
|
|
1904
|
+
* @param {string|number} value Two digits.
|
|
1905
|
+
* @returns {string} Two-character A/B parity row.
|
|
1906
|
+
*/
|
|
1907
|
+
function ean2Parity(value) {
|
|
1908
|
+
const digits = normalizeDigits(value, 2, 'EAN-2');
|
|
1909
|
+
return EAN2_PARITY[Number(digits) % 4];
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/**
|
|
1913
|
+
* Resolve the EAN-5 parity row for a payload.
|
|
1914
|
+
*
|
|
1915
|
+
* @param {string|number} value Five digits.
|
|
1916
|
+
* @returns {string} Five-character A/B parity row.
|
|
1917
|
+
*/
|
|
1918
|
+
function ean5Parity(value) {
|
|
1919
|
+
return EAN5_PARITY[ean5Checksum(value)];
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
/**
|
|
1923
|
+
* Expand an A/B parity row into EAN digit patterns.
|
|
1924
|
+
*
|
|
1925
|
+
* @param {string} digits
|
|
1926
|
+
* @param {string} parity
|
|
1927
|
+
* @returns {string}
|
|
1928
|
+
*/
|
|
1929
|
+
function encodeDigitPatterns(digits, parity) {
|
|
1930
|
+
let modules = EAN_ADDON_START;
|
|
1931
|
+
for (let i = 0; i < digits.length; i++) {
|
|
1932
|
+
if (i > 0) modules += EAN_ADDON_SEPARATOR;
|
|
1933
|
+
const table = parity[i] === 'A' ? EAN_L : EAN_G;
|
|
1934
|
+
modules += table[Number(digits[i])];
|
|
1935
|
+
}
|
|
1936
|
+
return modules;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Turn a module string into a one-row matrix and attach immutable source
|
|
1941
|
+
* metadata for composition/rendering callers.
|
|
1942
|
+
*
|
|
1943
|
+
* @param {string} modules
|
|
1944
|
+
* @param {{format: string, text: string, parity: string, checksum?: number}} metadata
|
|
1945
|
+
* @returns {BitMatrix}
|
|
1946
|
+
*/
|
|
1947
|
+
function toAddonMatrix(modules, metadata) {
|
|
1948
|
+
const matrix = new BitMatrix(modules.length, 1);
|
|
1949
|
+
for (let x = 0; x < modules.length; x++) {
|
|
1950
|
+
if (modules[x] === '1') matrix.set(x, 0);
|
|
1951
|
+
}
|
|
1952
|
+
matrix.eanAddon = Object.freeze({ ...metadata });
|
|
1953
|
+
return matrix;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/**
|
|
1957
|
+
* Encode an EAN-2 supplement.
|
|
1958
|
+
*
|
|
1959
|
+
* @param {string|number} value Exactly two decimal digits.
|
|
1960
|
+
* @returns {BitMatrix} A 20-module, one-row supplement.
|
|
1961
|
+
*/
|
|
1962
|
+
function encodeEAN2(value) {
|
|
1963
|
+
const digits = normalizeDigits(value, 2, 'EAN-2');
|
|
1964
|
+
const parity = ean2Parity(digits);
|
|
1965
|
+
const matrix = toAddonMatrix(encodeDigitPatterns(digits, parity), {
|
|
1966
|
+
format: 'ean2',
|
|
1967
|
+
text: digits,
|
|
1968
|
+
parity,
|
|
1969
|
+
});
|
|
1970
|
+
if (matrix.width !== EAN2_WIDTH) {
|
|
1971
|
+
throw new EncodeError(`EAN-2: internal width is ${matrix.width}, expected ${EAN2_WIDTH}`);
|
|
1972
|
+
}
|
|
1973
|
+
return matrix;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Encode an EAN-5 supplement.
|
|
1978
|
+
*
|
|
1979
|
+
* @param {string|number} value Exactly five decimal digits.
|
|
1980
|
+
* @returns {BitMatrix} A 47-module, one-row supplement.
|
|
1981
|
+
*/
|
|
1982
|
+
function encodeEAN5(value) {
|
|
1983
|
+
const digits = normalizeDigits(value, 5, 'EAN-5');
|
|
1984
|
+
const checksum = ean5Checksum(digits);
|
|
1985
|
+
const parity = EAN5_PARITY[checksum];
|
|
1986
|
+
const matrix = toAddonMatrix(encodeDigitPatterns(digits, parity), {
|
|
1987
|
+
format: 'ean5',
|
|
1988
|
+
text: digits,
|
|
1989
|
+
parity,
|
|
1990
|
+
checksum,
|
|
1991
|
+
});
|
|
1992
|
+
if (matrix.width !== EAN5_WIDTH) {
|
|
1993
|
+
throw new EncodeError(`EAN-5: internal width is ${matrix.width}, expected ${EAN5_WIDTH}`);
|
|
1994
|
+
}
|
|
1995
|
+
return matrix;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
/**
|
|
1999
|
+
* Encode either supported supplement length.
|
|
2000
|
+
*
|
|
2001
|
+
* @param {string|number} value Two or five decimal digits.
|
|
2002
|
+
* @returns {BitMatrix}
|
|
2003
|
+
*/
|
|
2004
|
+
function encodeEANAddon(value) {
|
|
2005
|
+
const digits = String(value);
|
|
2006
|
+
if (digits.length === 2) return encodeEAN2(digits);
|
|
2007
|
+
if (digits.length === 5) return encodeEAN5(digits);
|
|
2008
|
+
throw new EncodeError('EAN add-on: payload must contain exactly two or five digits');
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
/** Alias using the spelling used by some EAN documentation. */
|
|
2012
|
+
const encodeEANAddOn = encodeEANAddon;
|
|
2013
|
+
|
|
2014
|
+
/**
|
|
2015
|
+
* @param {BitMatrix} matrix
|
|
2016
|
+
* @param {number} offset
|
|
2017
|
+
* @param {string} pattern
|
|
2018
|
+
* @returns {boolean}
|
|
2019
|
+
*/
|
|
2020
|
+
function matches(matrix, offset, pattern) {
|
|
2021
|
+
if (offset < 0 || offset + pattern.length > matrix.width) return false;
|
|
2022
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
2023
|
+
if ((matrix.get(offset + i, 0) ? '1' : '0') !== pattern[i]) return false;
|
|
2024
|
+
}
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* @param {BitMatrix} matrix
|
|
2030
|
+
* @param {number} start
|
|
2031
|
+
* @param {2|5} digitCount
|
|
2032
|
+
* @returns {{format:string,text:string,parity:string,checksum?:number,end:number}|null}
|
|
2033
|
+
*/
|
|
2034
|
+
function decodeAt(matrix, start, digitCount) {
|
|
2035
|
+
const width = digitCount === 2 ? EAN2_WIDTH : EAN5_WIDTH;
|
|
2036
|
+
if (!matches(matrix, start, EAN_ADDON_START)) return null;
|
|
2037
|
+
if (start > 0 && matrix.get(start - 1, 0)) return null;
|
|
2038
|
+
|
|
2039
|
+
let offset = start + EAN_ADDON_START.length;
|
|
2040
|
+
let text = '';
|
|
2041
|
+
let parity = '';
|
|
2042
|
+
|
|
2043
|
+
for (let i = 0; i < digitCount; i++) {
|
|
2044
|
+
let found = null;
|
|
2045
|
+
for (let digit = 0; digit < 10; digit++) {
|
|
2046
|
+
for (const [letter, table] of [['A', EAN_L], ['B', EAN_G]]) {
|
|
2047
|
+
if (matches(matrix, offset, table[digit]) &&
|
|
2048
|
+
(!found || found.letter !== letter || found.digit !== digit)) {
|
|
2049
|
+
if (found) return null;
|
|
2050
|
+
found = { digit, letter };
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
if (!found) return null;
|
|
2055
|
+
text += String(found.digit);
|
|
2056
|
+
parity += found.letter;
|
|
2057
|
+
offset += 7;
|
|
2058
|
+
if (i < digitCount - 1) {
|
|
2059
|
+
if (!matches(matrix, offset, EAN_ADDON_SEPARATOR)) return null;
|
|
2060
|
+
offset += EAN_ADDON_SEPARATOR.length;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
if (offset - start !== width) return null;
|
|
2065
|
+
if (digitCount === 2) {
|
|
2066
|
+
if (EAN2_PARITY[Number(text) % 4] !== parity) return null;
|
|
2067
|
+
return { format: 'ean2', text, parity, end: offset };
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const checksum = ean5Checksum(text);
|
|
2071
|
+
if (EAN5_PARITY[checksum] !== parity) return null;
|
|
2072
|
+
return { format: 'ean5', text, parity, checksum, end: offset };
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/**
|
|
2076
|
+
* Validate and decode an EAN-2 supplement. A leading quiet zone is accepted;
|
|
2077
|
+
* the decoder searches the row for a valid start guard so a composed base
|
|
2078
|
+
* EAN/UPC matrix can be passed directly as well.
|
|
2079
|
+
*
|
|
2080
|
+
* @param {BitMatrix} matrix One-row, module-aligned supplement or composition.
|
|
2081
|
+
* @returns {{format:'ean2',text:string,parity:string}}
|
|
2082
|
+
* @throws {FormatError} When no valid supplement is found.
|
|
2083
|
+
*/
|
|
2084
|
+
function decodeEAN2(matrix) {
|
|
2085
|
+
if (!matrix || !Number.isInteger(matrix.width) || matrix.height !== 1) {
|
|
2086
|
+
throw new FormatError('EAN-2: expected a one-row module matrix');
|
|
2087
|
+
}
|
|
2088
|
+
for (let start = 0; start + EAN2_WIDTH <= matrix.width; start++) {
|
|
2089
|
+
const result = decodeAt(matrix, start, 2);
|
|
2090
|
+
if (result) {
|
|
2091
|
+
const { end, ...publicResult } = result;
|
|
2092
|
+
void end;
|
|
2093
|
+
return publicResult;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
throw new FormatError('EAN-2: start, parity or digit pattern is invalid');
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
/**
|
|
2100
|
+
* Validate and decode an EAN-5 supplement.
|
|
2101
|
+
*
|
|
2102
|
+
* @param {BitMatrix} matrix One-row, module-aligned supplement or composition.
|
|
2103
|
+
* @returns {{format:'ean5',text:string,parity:string,checksum:number}}
|
|
2104
|
+
* @throws {FormatError} When no valid supplement is found.
|
|
2105
|
+
*/
|
|
2106
|
+
function decodeEAN5(matrix) {
|
|
2107
|
+
if (!matrix || !Number.isInteger(matrix.width) || matrix.height !== 1) {
|
|
2108
|
+
throw new FormatError('EAN-5: expected a one-row module matrix');
|
|
2109
|
+
}
|
|
2110
|
+
for (let start = 0; start + EAN5_WIDTH <= matrix.width; start++) {
|
|
2111
|
+
const result = decodeAt(matrix, start, 5);
|
|
2112
|
+
if (result) {
|
|
2113
|
+
const { end, ...publicResult } = result;
|
|
2114
|
+
void end;
|
|
2115
|
+
return publicResult;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
throw new FormatError('EAN-5: start, parity, checksum or digit pattern is invalid');
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
/**
|
|
2122
|
+
* Decode either supported supplement length.
|
|
2123
|
+
*
|
|
2124
|
+
* @param {BitMatrix} matrix One-row, module-aligned supplement or composition.
|
|
2125
|
+
* @returns {{format:'ean2'|'ean5',text:string,parity:string,checksum?:number}}
|
|
2126
|
+
* @throws {FormatError} When neither supplement grammar matches.
|
|
2127
|
+
*/
|
|
2128
|
+
function decodeEANAddon(matrix) {
|
|
2129
|
+
try {
|
|
2130
|
+
return decodeEAN5(matrix);
|
|
2131
|
+
} catch (fiveError) {
|
|
2132
|
+
try {
|
|
2133
|
+
return decodeEAN2(matrix);
|
|
2134
|
+
} catch (twoError) {
|
|
2135
|
+
throw new FormatError(`EAN add-on: ${fiveError.message}; ${twoError.message}`);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
/** Alias using the spelling used by some EAN documentation. */
|
|
2141
|
+
const decodeEANAddOn = decodeEANAddon;
|
|
2142
|
+
|
|
2143
|
+
/**
|
|
2144
|
+
* Join a base EAN/UPC matrix and a supplement with the standard quiet gap.
|
|
2145
|
+
*
|
|
2146
|
+
* The helper does not alter either input. By default it inserts nine light
|
|
2147
|
+
* modules, the conventional separation between the base symbol and an add-on.
|
|
2148
|
+
* The result carries the supplement metadata under `eanAddon` for renderers or
|
|
2149
|
+
* callers that need to label the composed symbol.
|
|
2150
|
+
*
|
|
2151
|
+
* @param {BitMatrix} base Base EAN/UPC matrix.
|
|
2152
|
+
* @param {BitMatrix|string|number} addon Add-on matrix or two/five digits.
|
|
2153
|
+
* @param {{gap?:number}} [options]
|
|
2154
|
+
* @returns {BitMatrix}
|
|
2155
|
+
*/
|
|
2156
|
+
function composeEANAddon(base, addon, options = {}) {
|
|
2157
|
+
if (!base || !Number.isInteger(base.width) || base.height !== 1) {
|
|
2158
|
+
throw new TypeError('EAN add-on: base must be a one-row module matrix');
|
|
2159
|
+
}
|
|
2160
|
+
const addonMatrix = typeof addon === 'string' || typeof addon === 'number'
|
|
2161
|
+
? encodeEANAddon(addon)
|
|
2162
|
+
: addon;
|
|
2163
|
+
if (!addonMatrix || !Number.isInteger(addonMatrix.width) || addonMatrix.height !== 1) {
|
|
2164
|
+
throw new TypeError('EAN add-on: supplement must be a one-row module matrix or digits');
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
const gap = options.gap === undefined ? 9 : Number(options.gap);
|
|
2168
|
+
if (!Number.isInteger(gap) || gap < 1) {
|
|
2169
|
+
throw new EncodeError(`EAN add-on: gap must be a positive integer, got ${options.gap}`);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
const result = new BitMatrix(base.width + gap + addonMatrix.width, 1);
|
|
2173
|
+
for (let x = 0; x < base.width; x++) if (base.get(x, 0)) result.set(x, 0);
|
|
2174
|
+
for (let x = 0; x < addonMatrix.width; x++) {
|
|
2175
|
+
if (addonMatrix.get(x, 0)) result.set(base.width + gap + x, 0);
|
|
2176
|
+
}
|
|
2177
|
+
if (addonMatrix.eanAddon) result.eanAddon = { ...addonMatrix.eanAddon, gap };
|
|
2178
|
+
return result;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
/**
|
|
2182
|
+
* Compose an EAN-13 symbol with an EAN-2 or EAN-5 supplement.
|
|
2183
|
+
*
|
|
2184
|
+
* @param {string} value Base EAN-13 payload.
|
|
2185
|
+
* @param {string|number} addon Two or five supplemental digits.
|
|
2186
|
+
* @param {{gap?:number}} [options]
|
|
2187
|
+
* @returns {BitMatrix}
|
|
2188
|
+
*/
|
|
2189
|
+
function encodeEAN13WithAddon(value, addon, options = {}) {
|
|
2190
|
+
return composeEANAddon(encodeEAN13(value), addon, options);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
/** @see encodeEAN13WithAddon */
|
|
2194
|
+
function encodeEAN8WithAddon(value, addon, options = {}) {
|
|
2195
|
+
return composeEANAddon(encodeEAN8(value), addon, options);
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
/** @see encodeEAN13WithAddon */
|
|
2199
|
+
function encodeUPCAWithAddon(value, addon, options = {}) {
|
|
2200
|
+
return composeEANAddon(encodeUPCA(value), addon, options);
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/** @see encodeEAN13WithAddon */
|
|
2204
|
+
function encodeUPCEWithAddon(value, addon, options = {}) {
|
|
2205
|
+
return composeEANAddon(encodeUPCE(value), addon, options);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
__exports.EAN2_PARITY = EAN2_PARITY;
|
|
2209
|
+
__exports.EAN5_PARITY = EAN5_PARITY;
|
|
2210
|
+
__exports.EAN_ADDON_START = EAN_ADDON_START;
|
|
2211
|
+
__exports.EAN_ADDON_SEPARATOR = EAN_ADDON_SEPARATOR;
|
|
2212
|
+
__exports.EAN2_WIDTH = EAN2_WIDTH;
|
|
2213
|
+
__exports.EAN5_WIDTH = EAN5_WIDTH;
|
|
2214
|
+
__exports.ean5Checksum = ean5Checksum;
|
|
2215
|
+
__exports.ean5CheckDigit = ean5CheckDigit;
|
|
2216
|
+
__exports.ean2Parity = ean2Parity;
|
|
2217
|
+
__exports.ean5Parity = ean5Parity;
|
|
2218
|
+
__exports.encodeEAN2 = encodeEAN2;
|
|
2219
|
+
__exports.encodeEAN5 = encodeEAN5;
|
|
2220
|
+
__exports.encodeEANAddon = encodeEANAddon;
|
|
2221
|
+
__exports.encodeEANAddOn = encodeEANAddOn;
|
|
2222
|
+
__exports.decodeEAN2 = decodeEAN2;
|
|
2223
|
+
__exports.decodeEAN5 = decodeEAN5;
|
|
2224
|
+
__exports.decodeEANAddon = decodeEANAddon;
|
|
2225
|
+
__exports.decodeEANAddOn = decodeEANAddOn;
|
|
2226
|
+
__exports.composeEANAddon = composeEANAddon;
|
|
2227
|
+
__exports.encodeEAN13WithAddon = encodeEAN13WithAddon;
|
|
2228
|
+
__exports.encodeEAN8WithAddon = encodeEAN8WithAddon;
|
|
2229
|
+
__exports.encodeUPCAWithAddon = encodeUPCAWithAddon;
|
|
2230
|
+
__exports.encodeUPCEWithAddon = encodeUPCEWithAddon;
|
|
2231
|
+
};
|
|
2232
|
+
|
|
1826
2233
|
__modules["oned/reader.js"] = function (__require, __exports) {
|
|
1827
2234
|
/**
|
|
1828
2235
|
* Linear barcode reading.
|
|
@@ -2716,7 +3123,10 @@ __modules["oned/index.js"] = function (__require, __exports) {
|
|
|
2716
3123
|
* Linear symbologies.
|
|
2717
3124
|
*
|
|
2718
3125
|
* @module oned
|
|
2719
|
-
*/
|
|
3126
|
+
*/
|
|
3127
|
+
const __reexport1 = __require("oned/addons.js"); __exports.EAN2_PARITY = __reexport1.EAN2_PARITY; __exports.EAN5_PARITY = __reexport1.EAN5_PARITY; __exports.EAN2_WIDTH = __reexport1.EAN2_WIDTH; __exports.EAN5_WIDTH = __reexport1.EAN5_WIDTH; __exports.EAN_ADDON_START = __reexport1.EAN_ADDON_START; __exports.EAN_ADDON_SEPARATOR = __reexport1.EAN_ADDON_SEPARATOR; __exports.ean2Parity = __reexport1.ean2Parity; __exports.ean5Checksum = __reexport1.ean5Checksum; __exports.ean5CheckDigit = __reexport1.ean5CheckDigit; __exports.ean5Parity = __reexport1.ean5Parity; __exports.encodeEAN2 = __reexport1.encodeEAN2; __exports.encodeEAN5 = __reexport1.encodeEAN5; __exports.encodeEANAddon = __reexport1.encodeEANAddon; __exports.encodeEANAddOn = __reexport1.encodeEANAddOn; __exports.decodeEAN2 = __reexport1.decodeEAN2; __exports.decodeEAN5 = __reexport1.decodeEAN5; __exports.decodeEANAddon = __reexport1.decodeEANAddon; __exports.decodeEANAddOn = __reexport1.decodeEANAddOn; __exports.composeEANAddon = __reexport1.composeEANAddon; __exports.encodeEAN13WithAddon = __reexport1.encodeEAN13WithAddon; __exports.encodeEAN8WithAddon = __reexport1.encodeEAN8WithAddon; __exports.encodeUPCAWithAddon = __reexport1.encodeUPCAWithAddon; __exports.encodeUPCEWithAddon = __reexport1.encodeUPCEWithAddon;
|
|
3128
|
+
const __reexport2 = __require("oned/reader.js"); __exports.decodeOneD = __reexport2.decodeOneD; __exports.decodeOneDStrict = __reexport2.decodeOneDStrict; __exports.patternVariance = __reexport2.patternVariance; __exports.recordPattern = __reexport2.recordPattern; __exports.toNarrowWidePattern = __reexport2.toNarrowWidePattern;
|
|
3129
|
+
const { encodeEAN2, encodeEAN5 } = __require("oned/addons.js");
|
|
2720
3130
|
|
|
2721
3131
|
/**
|
|
2722
3132
|
* Writers by format id, for the top-level `encode()` dispatcher.
|
|
@@ -2727,7 +3137,7 @@ __modules["oned/index.js"] = function (__require, __exports) {
|
|
|
2727
3137
|
* failing at runtime.
|
|
2728
3138
|
*
|
|
2729
3139
|
* @type {Record<string, {encode: Function, readable: boolean, label: string}>}
|
|
2730
|
-
*/
|
|
3140
|
+
*/
|
|
2731
3141
|
ean13: { encode: encodeEAN13, readable: true, label: 'EAN-13' },
|
|
2732
3142
|
ean8: { encode: encodeEAN8, readable: true, label: 'EAN-8' },
|
|
2733
3143
|
upca: { encode: encodeUPCA, readable: true, label: 'UPC-A' },
|
|
@@ -2746,8 +3156,13 @@ __modules["oned/index.js"] = function (__require, __exports) {
|
|
|
2746
3156
|
codabar: { encode: encodeCodabar, readable: true, label: 'Codabar' },
|
|
2747
3157
|
code11: { encode: encodeCode11, readable: false, label: 'Code 11' },
|
|
2748
3158
|
msi: { encode: encodeMSI, readable: false, label: 'MSI Plessey' },
|
|
2749
|
-
pharmacode: { encode: encodePharmacode, readable: false, label: 'Pharmacode' },
|
|
2750
|
-
|
|
3159
|
+
pharmacode: { encode: encodePharmacode, readable: false, label: 'Pharmacode' },
|
|
3160
|
+
// Supplements are writable standalone matrices, but their reader is
|
|
3161
|
+
// intentionally exposed through the EAN/UPC add-on helpers rather than the
|
|
3162
|
+
// generic one-dimensional scan pipeline.
|
|
3163
|
+
ean2: { encode: encodeEAN2, readable: false, label: 'EAN-2 supplement' },
|
|
3164
|
+
ean5: { encode: encodeEAN5, readable: false, label: 'EAN-5 supplement' },
|
|
3165
|
+
};
|
|
2751
3166
|
|
|
2752
3167
|
__exports.ONED_FORMATS = ONED_FORMATS;
|
|
2753
3168
|
};
|
|
@@ -10255,90 +10670,4028 @@ const __reexport5 = __require("micropdf417/detector.js"); __exports.detectMicroP
|
|
|
10255
10670
|
|
|
10256
10671
|
};
|
|
10257
10672
|
|
|
10258
|
-
__modules["
|
|
10673
|
+
__modules["microqr/tables.js"] = function (__require, __exports) {
|
|
10259
10674
|
/**
|
|
10260
|
-
*
|
|
10675
|
+
* Micro QR Code structural facts and geometry.
|
|
10261
10676
|
*
|
|
10262
|
-
*
|
|
10677
|
+
* Only the irreducible public symbology values are tabulated. Grid capacity,
|
|
10678
|
+
* reserved areas and placement order are derived independently and checked by
|
|
10679
|
+
* {@link validateMicroQrTables}. M1 and M3 have a four-bit final data symbol
|
|
10680
|
+
* character; `dataBits` therefore is authoritative and must not be inferred as
|
|
10681
|
+
* `dataCodewords * 8` for those versions.
|
|
10682
|
+
*
|
|
10683
|
+
* @module microqr/tables
|
|
10263
10684
|
*/
|
|
10264
10685
|
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
10265
10686
|
|
|
10266
|
-
/**
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10687
|
+
/** Numeric version identifiers used by the encoder selection loop. */
|
|
10688
|
+
const MICROQR_VERSIONS = Object.freeze([1, 2, 3, 4]);
|
|
10689
|
+
const MICROQR_VERSION_NAMES = Object.freeze(['M1', 'M2', 'M3', 'M4']);
|
|
10690
|
+
const MICROQR_ECC_LEVELS = Object.freeze(['DETECT', 'L', 'M', 'Q']);
|
|
10691
|
+
const MICROQR_FORMAT_MASK = 0x4445;
|
|
10692
|
+
const MICROQR_FORMAT_GENERATOR = 0x537;
|
|
10693
|
+
|
|
10694
|
+
const symbol = (version, ecc, symbolNumber, totalCodewords, dataCodewords, dataBits, eccCodewords) => Object.freeze({
|
|
10695
|
+
version,
|
|
10696
|
+
ecc,
|
|
10697
|
+
symbolNumber,
|
|
10698
|
+
size: 9 + Number(version.slice(1)) * 2,
|
|
10699
|
+
totalCodewords,
|
|
10700
|
+
dataCodewords,
|
|
10701
|
+
dataBits,
|
|
10702
|
+
eccCodewords,
|
|
10703
|
+
blockCount: 1,
|
|
10704
|
+
shortDataCodewordBits: dataBits % 8 || 8,
|
|
10705
|
+
remainderBits: 0,
|
|
10706
|
+
});
|
|
10707
|
+
|
|
10708
|
+
/** The eight legal Micro QR version/error-correction combinations. */
|
|
10709
|
+
const MICROQR_SYMBOLS = Object.freeze([
|
|
10710
|
+
symbol('M1', 'DETECT', 0, 5, 3, 20, 2),
|
|
10711
|
+
symbol('M2', 'L', 1, 10, 5, 40, 5),
|
|
10712
|
+
symbol('M2', 'M', 2, 10, 4, 32, 6),
|
|
10713
|
+
symbol('M3', 'L', 3, 17, 11, 84, 6),
|
|
10714
|
+
symbol('M3', 'M', 4, 17, 9, 68, 8),
|
|
10715
|
+
symbol('M4', 'L', 5, 24, 16, 128, 8),
|
|
10716
|
+
symbol('M4', 'M', 6, 24, 14, 112, 10),
|
|
10717
|
+
symbol('M4', 'Q', 7, 24, 10, 80, 14),
|
|
10718
|
+
]);
|
|
10719
|
+
|
|
10720
|
+
const symbolByKey = new Map(MICROQR_SYMBOLS.map((entry) => [`${entry.version}:${entry.ecc}`, entry]));
|
|
10721
|
+
const symbolByNumber = new Map(MICROQR_SYMBOLS.map((entry) => [entry.symbolNumber, entry]));
|
|
10722
|
+
|
|
10723
|
+
function canonicalVersion(version) {
|
|
10724
|
+
const result = typeof version === 'number' ? `M${version}` : String(version).toUpperCase();
|
|
10725
|
+
if (!MICROQR_VERSION_NAMES.includes(result)) throw new RangeError(`Micro QR: version must be M1-M4, got ${version}`);
|
|
10726
|
+
return result;
|
|
10727
|
+
}
|
|
10728
|
+
|
|
10729
|
+
/** @param {string|number} version @returns {number} */
|
|
10730
|
+
function microQrVersionSize(version) {
|
|
10731
|
+
return 9 + Number(canonicalVersion(version).slice(1)) * 2;
|
|
10732
|
+
}
|
|
10733
|
+
|
|
10734
|
+
/** Resolve the format symbol number for a legal version/ECC pair. */
|
|
10735
|
+
function microQrSymbolNumber(version, ecc) {
|
|
10736
|
+
return microQrBlockLayout(version, ecc).symbolNumber;
|
|
10737
|
+
}
|
|
10738
|
+
|
|
10739
|
+
/** Resolve the immutable single-block layout for a legal version/ECC pair. */
|
|
10740
|
+
function microQrBlockLayout(version, ecc) {
|
|
10741
|
+
const canonical = canonicalVersion(version);
|
|
10742
|
+
const level = ecc == null && canonical === 'M1' ? 'DETECT' : String(ecc).toUpperCase();
|
|
10743
|
+
const entry = symbolByKey.get(`${canonical}:${level}`);
|
|
10744
|
+
if (!entry) throw new RangeError(`Micro QR: error correction level ${ecc} is not valid for ${canonical}`);
|
|
10745
|
+
return entry;
|
|
10746
|
+
}
|
|
10747
|
+
|
|
10748
|
+
/** @returns {number} Usable message bits, including mode/count overhead. */
|
|
10749
|
+
function microQrDataCapacityBits(version, ecc) {
|
|
10750
|
+
return microQrBlockLayout(version, ecc).dataBits;
|
|
10751
|
+
}
|
|
10274
10752
|
|
|
10275
10753
|
/**
|
|
10276
|
-
*
|
|
10277
|
-
*
|
|
10278
|
-
*
|
|
10279
|
-
* *before* the quiet zone is applied, so the margin ends up uniform on all
|
|
10280
|
-
* four sides — padding first would leave a quiet zone one module tall against
|
|
10281
|
-
* bars a hundred pixels tall, which no scanner would accept.
|
|
10282
|
-
*
|
|
10283
|
-
* @param {BitMatrix} matrix
|
|
10284
|
-
* @param {RenderOptions} options
|
|
10754
|
+
* Encode the five format data bits with BCH(15,5), then apply the Micro QR
|
|
10755
|
+
* format mask. `symbolNumber` occupies the high three data bits and `mask`
|
|
10756
|
+
* the low two.
|
|
10285
10757
|
*/
|
|
10286
|
-
function
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10758
|
+
function microQrFormatInfo(symbolNumber, mask, maybeMask) {
|
|
10759
|
+
// Public convenience overload: (version, ecc, mask).
|
|
10760
|
+
if (arguments.length === 3) {
|
|
10761
|
+
symbolNumber = microQrSymbolNumber(symbolNumber, mask);
|
|
10762
|
+
mask = maybeMask;
|
|
10763
|
+
}
|
|
10764
|
+
if (!Number.isInteger(symbolNumber) || symbolNumber < 0 || symbolNumber > 7) {
|
|
10765
|
+
throw new RangeError('Micro QR: symbol number must be an integer in 0..7');
|
|
10766
|
+
}
|
|
10767
|
+
if (!Number.isInteger(mask) || mask < 0 || mask > 3) {
|
|
10768
|
+
throw new RangeError('Micro QR: mask must be an integer in 0..3');
|
|
10769
|
+
}
|
|
10770
|
+
const data = (symbolNumber << 2) | mask;
|
|
10771
|
+
let remainder = data << 10;
|
|
10772
|
+
for (let bit = 14; bit >= 10; bit--) {
|
|
10773
|
+
if ((remainder & (1 << bit)) !== 0) remainder ^= MICROQR_FORMAT_GENERATOR << (bit - 10);
|
|
10774
|
+
}
|
|
10775
|
+
return ((data << 10) | remainder) ^ MICROQR_FORMAT_MASK;
|
|
10776
|
+
}
|
|
10292
10777
|
|
|
10293
|
-
|
|
10294
|
-
|
|
10778
|
+
function hammingDistance(a, b) {
|
|
10779
|
+
let bits = (a ^ b) & 0x7fff;
|
|
10780
|
+
let count = 0;
|
|
10781
|
+
while (bits) { bits &= bits - 1; count++; }
|
|
10782
|
+
return count;
|
|
10783
|
+
}
|
|
10295
10784
|
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10785
|
+
/** Decode/correct a 15-bit format word. Returns null beyond three errors. */
|
|
10786
|
+
function microQrDecodeFormatInfo(bits) {
|
|
10787
|
+
if (!Number.isInteger(bits) || bits < 0 || bits > 0x7fff) {
|
|
10788
|
+
throw new RangeError('Micro QR: format information must be a 15-bit integer');
|
|
10789
|
+
}
|
|
10790
|
+
let best = null;
|
|
10791
|
+
let bestDistance = 16;
|
|
10792
|
+
for (const entry of MICROQR_SYMBOLS) for (let mask = 0; mask < 4; mask++) {
|
|
10793
|
+
const expected = microQrFormatInfo(entry.symbolNumber, mask);
|
|
10794
|
+
const distance = hammingDistance(bits, expected);
|
|
10795
|
+
if (distance < bestDistance) {
|
|
10796
|
+
bestDistance = distance;
|
|
10797
|
+
best = { version: entry.version, ecc: entry.ecc, symbolNumber: entry.symbolNumber, mask, correctedBits: distance, bits: expected };
|
|
10305
10798
|
}
|
|
10306
10799
|
}
|
|
10800
|
+
return bestDistance <= 3 ? best : null;
|
|
10801
|
+
}
|
|
10307
10802
|
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10803
|
+
/**
|
|
10804
|
+
* Format modules in bit-number order, least significant bit first. Bits 0..7
|
|
10805
|
+
* run down column 8; bits 8..14 continue right-to-left along row 8.
|
|
10806
|
+
* Position (8,8) is shared by the two arms and appears once.
|
|
10807
|
+
*/
|
|
10808
|
+
function microQrFormatInfoPositions(sizeOrVersion) {
|
|
10809
|
+
const size = typeof sizeOrVersion === 'number' && sizeOrVersion >= 11
|
|
10810
|
+
? sizeOrVersion
|
|
10811
|
+
: microQrVersionSize(sizeOrVersion);
|
|
10812
|
+
if (![11, 13, 15, 17].includes(size)) throw new RangeError(`Micro QR: invalid symbol size ${size}`);
|
|
10813
|
+
const positions = [];
|
|
10814
|
+
for (let y = 1; y <= 8; y++) positions.push([8, y]);
|
|
10815
|
+
for (let x = 7; x >= 1; x--) positions.push([x, 8]);
|
|
10816
|
+
return positions;
|
|
10321
10817
|
}
|
|
10322
10818
|
|
|
10819
|
+
const reservedCache = new Map();
|
|
10820
|
+
const functionCache = new Map();
|
|
10821
|
+
|
|
10323
10822
|
/**
|
|
10324
|
-
*
|
|
10325
|
-
*
|
|
10326
|
-
*
|
|
10327
|
-
* #rrggbbaa, rgb(), rgba(), plus 'none' and 'transparent'.
|
|
10328
|
-
*
|
|
10329
|
-
* @param {string} colour
|
|
10330
|
-
* @returns {[number, number, number, number]}
|
|
10823
|
+
* Fixed dark function modules before format information is written. Light
|
|
10824
|
+
* separator and light timing modules remain unset; use
|
|
10825
|
+
* {@link microQrReservedModules} to distinguish them from payload modules.
|
|
10331
10826
|
*/
|
|
10332
|
-
function
|
|
10333
|
-
const
|
|
10827
|
+
function microQrFunctionModules(version) {
|
|
10828
|
+
const canonical = canonicalVersion(version);
|
|
10829
|
+
const cached = functionCache.get(canonical);
|
|
10830
|
+
if (cached) return cached;
|
|
10831
|
+
const size = microQrVersionSize(canonical);
|
|
10832
|
+
const matrix = new BitMatrix(size, size);
|
|
10833
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
10834
|
+
const outer = x === 0 || x === 6 || y === 0 || y === 6;
|
|
10835
|
+
const centre = x >= 2 && x <= 4 && y >= 2 && y <= 4;
|
|
10836
|
+
if (outer || centre) matrix.set(x, y);
|
|
10837
|
+
}
|
|
10838
|
+
for (let coordinate = 8; coordinate < size; coordinate += 2) {
|
|
10839
|
+
matrix.set(coordinate, 0);
|
|
10840
|
+
matrix.set(0, coordinate);
|
|
10841
|
+
}
|
|
10842
|
+
functionCache.set(canonical, matrix);
|
|
10843
|
+
return matrix;
|
|
10844
|
+
}
|
|
10334
10845
|
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10846
|
+
/** Shared immutable-in-use map of finder, separator, timing and format modules. */
|
|
10847
|
+
function microQrReservedModules(version) {
|
|
10848
|
+
const canonical = canonicalVersion(version);
|
|
10849
|
+
const cached = reservedCache.get(canonical);
|
|
10850
|
+
if (cached) return cached;
|
|
10851
|
+
const size = microQrVersionSize(canonical);
|
|
10852
|
+
const matrix = new BitMatrix(size, size);
|
|
10853
|
+
matrix.setRegion(0, 0, 8, 8); // 7x7 finder plus inner separator
|
|
10854
|
+
for (let coordinate = 8; coordinate < size; coordinate++) {
|
|
10855
|
+
matrix.set(coordinate, 0); // horizontal timing
|
|
10856
|
+
matrix.set(0, coordinate); // vertical timing
|
|
10857
|
+
}
|
|
10858
|
+
for (const [x, y] of microQrFormatInfoPositions(size)) matrix.set(x, y);
|
|
10859
|
+
reservedCache.set(canonical, matrix);
|
|
10860
|
+
return matrix;
|
|
10861
|
+
}
|
|
10338
10862
|
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10863
|
+
/** Number of modules carrying data or error-correction bits. */
|
|
10864
|
+
function microQrFreeModuleCount(version) {
|
|
10865
|
+
const size = microQrVersionSize(version);
|
|
10866
|
+
const reserved = microQrReservedModules(version);
|
|
10867
|
+
let count = 0;
|
|
10868
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
10869
|
+
if (!reserved.get(x, y)) count++;
|
|
10870
|
+
}
|
|
10871
|
+
return count;
|
|
10872
|
+
}
|
|
10873
|
+
|
|
10874
|
+
const orderCache = new Map();
|
|
10875
|
+
|
|
10876
|
+
/** Payload module coordinates as interleaved x,y pairs, MSB-first stream order. */
|
|
10877
|
+
function microQrDataModuleOrder(version) {
|
|
10878
|
+
const canonical = canonicalVersion(version);
|
|
10879
|
+
const cached = orderCache.get(canonical);
|
|
10880
|
+
if (cached) return cached;
|
|
10881
|
+
const size = microQrVersionSize(canonical);
|
|
10882
|
+
const reserved = microQrReservedModules(canonical);
|
|
10883
|
+
const order = new Int32Array(microQrFreeModuleCount(canonical) * 2);
|
|
10884
|
+
let offset = 0;
|
|
10885
|
+
let upward = true;
|
|
10886
|
+
for (let column = size - 1; column > 0; column -= 2) {
|
|
10887
|
+
for (let rowOffset = 0; rowOffset < size; rowOffset++) {
|
|
10888
|
+
const y = upward ? size - 1 - rowOffset : rowOffset;
|
|
10889
|
+
for (let side = 0; side < 2; side++) {
|
|
10890
|
+
const x = column - side;
|
|
10891
|
+
if (reserved.get(x, y)) continue;
|
|
10892
|
+
order[offset++] = x;
|
|
10893
|
+
order[offset++] = y;
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
upward = !upward;
|
|
10897
|
+
}
|
|
10898
|
+
orderCache.set(canonical, order);
|
|
10899
|
+
return order;
|
|
10900
|
+
}
|
|
10901
|
+
|
|
10902
|
+
/** The four Micro QR data-mask predicates. */
|
|
10903
|
+
function microQrMaskBit(mask, x, y) {
|
|
10904
|
+
switch (mask) {
|
|
10905
|
+
case 0: return (y & 1) === 0;
|
|
10906
|
+
case 1: return (((y >> 1) + Math.floor(x / 3)) & 1) === 0;
|
|
10907
|
+
case 2: return ((((y * x) & 1) + ((y * x) % 3)) & 1) === 0;
|
|
10908
|
+
case 3: return ((((y + x) & 1) + ((y * x) % 3)) & 1) === 0;
|
|
10909
|
+
default: throw new RangeError(`Micro QR: mask must be an integer in 0..3, got ${mask}`);
|
|
10910
|
+
}
|
|
10911
|
+
}
|
|
10912
|
+
|
|
10913
|
+
/** Return all internal table/geometry invariant failures. */
|
|
10914
|
+
function validateMicroQrTables() {
|
|
10915
|
+
const issues = [];
|
|
10916
|
+
if (MICROQR_SYMBOLS.length !== 8) issues.push('expected eight symbol/ECC combinations');
|
|
10917
|
+
const numbers = new Set();
|
|
10918
|
+
for (const entry of MICROQR_SYMBOLS) {
|
|
10919
|
+
const tag = `${entry.version}-${entry.ecc}`;
|
|
10920
|
+
if (numbers.has(entry.symbolNumber)) issues.push(`${tag}: duplicate symbol number`);
|
|
10921
|
+
numbers.add(entry.symbolNumber);
|
|
10922
|
+
if (entry.blockCount !== 1) issues.push(`${tag}: Micro QR must use one block`);
|
|
10923
|
+
if (entry.dataBits + entry.eccCodewords * 8 !== microQrFreeModuleCount(entry.version)) {
|
|
10924
|
+
issues.push(`${tag}: data/ECC bits do not fill the encoding region`);
|
|
10925
|
+
}
|
|
10926
|
+
if (entry.totalCodewords !== entry.dataCodewords + entry.eccCodewords) issues.push(`${tag}: codeword count mismatch`);
|
|
10927
|
+
if (entry.dataBits !== (entry.dataCodewords - 1) * 8 + entry.shortDataCodewordBits) issues.push(`${tag}: final data codeword mismatch`);
|
|
10928
|
+
if (entry.shortDataCodewordBits !== (entry.version === 'M1' || entry.version === 'M3' ? 4 : 8)) issues.push(`${tag}: wrong final data codeword width`);
|
|
10929
|
+
}
|
|
10930
|
+
for (const version of MICROQR_VERSIONS) {
|
|
10931
|
+
const size = microQrVersionSize(version);
|
|
10932
|
+
const positions = microQrFormatInfoPositions(size);
|
|
10933
|
+
const unique = new Set(positions.map(([x, y]) => `${x},${y}`));
|
|
10934
|
+
if (positions.length !== 15 || unique.size !== 15) issues.push(`${version}: format positions must be 15 unique modules`);
|
|
10935
|
+
const order = microQrDataModuleOrder(version);
|
|
10936
|
+
const orderUnique = new Set();
|
|
10937
|
+
for (let i = 0; i < order.length; i += 2) {
|
|
10938
|
+
const x = order[i], y = order[i + 1];
|
|
10939
|
+
if (microQrReservedModules(version).get(x, y)) issues.push(`${version}: placement enters reserved module ${x},${y}`);
|
|
10940
|
+
orderUnique.add(`${x},${y}`);
|
|
10941
|
+
}
|
|
10942
|
+
if (orderUnique.size * 2 !== order.length) issues.push(`${version}: placement repeats a module`);
|
|
10943
|
+
if (order.length !== microQrFreeModuleCount(version) * 2) issues.push(`${version}: placement does not cover encoding region`);
|
|
10944
|
+
const functions = microQrFunctionModules(version);
|
|
10945
|
+
for (let y = 0; y < size; y++) for (let x = 0; x < size; x++) {
|
|
10946
|
+
if (functions.get(x, y) && !microQrReservedModules(version).get(x, y)) {
|
|
10947
|
+
issues.push(`${version}: dark function module ${x},${y} is not reserved`);
|
|
10948
|
+
}
|
|
10949
|
+
}
|
|
10950
|
+
}
|
|
10951
|
+
for (const entry of MICROQR_SYMBOLS) for (let mask = 0; mask < 4; mask++) {
|
|
10952
|
+
const decoded = microQrDecodeFormatInfo(microQrFormatInfo(entry.symbolNumber, mask));
|
|
10953
|
+
if (!decoded || decoded.symbolNumber !== entry.symbolNumber || decoded.mask !== mask || decoded.correctedBits !== 0) {
|
|
10954
|
+
issues.push(`${entry.version}-${entry.ecc}: format round-trip failed for mask ${mask}`);
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10957
|
+
return issues;
|
|
10958
|
+
}
|
|
10959
|
+
|
|
10960
|
+
__exports.MICROQR_VERSIONS = MICROQR_VERSIONS;
|
|
10961
|
+
__exports.MICROQR_VERSION_NAMES = MICROQR_VERSION_NAMES;
|
|
10962
|
+
__exports.MICROQR_ECC_LEVELS = MICROQR_ECC_LEVELS;
|
|
10963
|
+
__exports.MICROQR_FORMAT_MASK = MICROQR_FORMAT_MASK;
|
|
10964
|
+
__exports.MICROQR_FORMAT_GENERATOR = MICROQR_FORMAT_GENERATOR;
|
|
10965
|
+
__exports.MICROQR_SYMBOLS = MICROQR_SYMBOLS;
|
|
10966
|
+
__exports.microQrVersionSize = microQrVersionSize;
|
|
10967
|
+
__exports.microQrSymbolNumber = microQrSymbolNumber;
|
|
10968
|
+
__exports.microQrBlockLayout = microQrBlockLayout;
|
|
10969
|
+
__exports.microQrDataCapacityBits = microQrDataCapacityBits;
|
|
10970
|
+
__exports.microQrFormatInfo = microQrFormatInfo;
|
|
10971
|
+
__exports.microQrDecodeFormatInfo = microQrDecodeFormatInfo;
|
|
10972
|
+
__exports.microQrFormatInfoPositions = microQrFormatInfoPositions;
|
|
10973
|
+
__exports.microQrFunctionModules = microQrFunctionModules;
|
|
10974
|
+
__exports.microQrReservedModules = microQrReservedModules;
|
|
10975
|
+
__exports.microQrFreeModuleCount = microQrFreeModuleCount;
|
|
10976
|
+
__exports.microQrDataModuleOrder = microQrDataModuleOrder;
|
|
10977
|
+
__exports.microQrMaskBit = microQrMaskBit;
|
|
10978
|
+
__exports.validateMicroQrTables = validateMicroQrTables;
|
|
10979
|
+
};
|
|
10980
|
+
|
|
10981
|
+
__modules["microqr/encoder.js"] = function (__require, __exports) {
|
|
10982
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
10983
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
10984
|
+
const { EncodeError } = __require("core/errors.js");
|
|
10985
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
10986
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
10987
|
+
const { MICROQR_VERSIONS, microQrBlockLayout, microQrDataModuleOrder, microQrFormatInfo, microQrFormatInfoPositions, microQrMaskBit, microQrSymbolNumber, microQrVersionSize } = __require("microqr/tables.js");
|
|
10988
|
+
const MICROQR_ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
10989
|
+
const MODE = { numeric: 0, alphanumeric: 1, byte: 2, kanji: 3 };
|
|
10990
|
+
const MODE_MIN_VERSION = { numeric: 1, alphanumeric: 2, byte: 3, kanji: 3 };
|
|
10991
|
+
const COUNT_BITS = {
|
|
10992
|
+
numeric: [0, 3, 4, 5, 6], alphanumeric: [0, 0, 3, 4, 5],
|
|
10993
|
+
byte: [0, 0, 0, 4, 5], kanji: [0, 0, 0, 3, 4],
|
|
10994
|
+
};
|
|
10995
|
+
|
|
10996
|
+
function parseVersion(value) {
|
|
10997
|
+
if (value == null) return null;
|
|
10998
|
+
const match = /^M?([1-4])$/i.exec(String(value));
|
|
10999
|
+
if (!match) throw new EncodeError(`Micro QR: version must be M1-M4, got ${value}`);
|
|
11000
|
+
return Number(match[1]);
|
|
11001
|
+
}
|
|
11002
|
+
|
|
11003
|
+
function versionNumber(version) {
|
|
11004
|
+
return typeof version === 'number' ? version : Number(String(version).slice(1));
|
|
11005
|
+
}
|
|
11006
|
+
|
|
11007
|
+
function latin1Bytes(text) {
|
|
11008
|
+
const bytes = new Uint8Array(text.length);
|
|
11009
|
+
for (let i = 0; i < text.length; i++) {
|
|
11010
|
+
const cp = text.charCodeAt(i);
|
|
11011
|
+
if (cp > 0xff) throw new EncodeError('Micro QR: byte mode supports ISO-8859-1 only (ECI is unavailable)');
|
|
11012
|
+
bytes[i] = cp;
|
|
11013
|
+
}
|
|
11014
|
+
return bytes;
|
|
11015
|
+
}
|
|
11016
|
+
|
|
11017
|
+
let sjisReverseMap;
|
|
11018
|
+
|
|
11019
|
+
function sjisToThirteenBits(sjis) {
|
|
11020
|
+
const trail = sjis & 0xff;
|
|
11021
|
+
if (trail < 0x40 || trail === 0x7f || trail > 0xfc) return -1;
|
|
11022
|
+
let adjusted;
|
|
11023
|
+
if (sjis >= 0x8140 && sjis <= 0x9ffc) adjusted = sjis - 0x8140;
|
|
11024
|
+
else if (sjis >= 0xe040 && sjis <= 0xebbf) adjusted = sjis - 0xc140;
|
|
11025
|
+
else return -1;
|
|
11026
|
+
const packed = (adjusted >>> 8) * 0xc0 + (adjusted & 0xff);
|
|
11027
|
+
return packed <= 0x1fff ? packed : -1;
|
|
11028
|
+
}
|
|
11029
|
+
|
|
11030
|
+
function getSjisReverseMap() {
|
|
11031
|
+
if (sjisReverseMap !== undefined) return sjisReverseMap;
|
|
11032
|
+
let decoder;
|
|
11033
|
+
try {
|
|
11034
|
+
decoder = new TextDecoder('shift_jis', { fatal: true });
|
|
11035
|
+
if (decoder.decode(new Uint8Array([0x82, 0xa0])) !== 'あ') return (sjisReverseMap = null);
|
|
11036
|
+
} catch {
|
|
11037
|
+
return (sjisReverseMap = null);
|
|
11038
|
+
}
|
|
11039
|
+
const result = new Map();
|
|
11040
|
+
const bytes = new Uint8Array(2);
|
|
11041
|
+
for (const [start, end] of [[0x8140, 0x9ffc], [0xe040, 0xebbf]]) {
|
|
11042
|
+
for (let value = start; value <= end; value++) {
|
|
11043
|
+
if (sjisToThirteenBits(value) < 0) continue;
|
|
11044
|
+
bytes[0] = value >>> 8;
|
|
11045
|
+
bytes[1] = value & 0xff;
|
|
11046
|
+
let character;
|
|
11047
|
+
try { character = decoder.decode(bytes); } catch { continue; }
|
|
11048
|
+
if (Array.from(character).length === 1 && !result.has(character)) result.set(character, value);
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
sjisReverseMap = result;
|
|
11052
|
+
return result;
|
|
11053
|
+
}
|
|
11054
|
+
|
|
11055
|
+
function kanjiValues(text) {
|
|
11056
|
+
const reverse = getSjisReverseMap();
|
|
11057
|
+
if (!reverse) return null;
|
|
11058
|
+
const values = [];
|
|
11059
|
+
for (const character of text) {
|
|
11060
|
+
const sjis = reverse.get(character);
|
|
11061
|
+
if (sjis == null) return null;
|
|
11062
|
+
values.push(sjisToThirteenBits(sjis));
|
|
11063
|
+
}
|
|
11064
|
+
return values;
|
|
11065
|
+
}
|
|
11066
|
+
|
|
11067
|
+
function chooseMode(text, forced) {
|
|
11068
|
+
const mode = forced == null ? (/^\d+$/.test(text) ? 'numeric' :
|
|
11069
|
+
[...text].every((ch) => MICROQR_ALPHANUMERIC.includes(ch)) ? 'alphanumeric' :
|
|
11070
|
+
kanjiValues(text) ? 'kanji' : 'byte') :
|
|
11071
|
+
String(forced).toLowerCase();
|
|
11072
|
+
if (!(mode in MODE)) throw new EncodeError(`Micro QR: unsupported mode "${forced}"`);
|
|
11073
|
+
if (mode === 'numeric' && !/^\d+$/.test(text)) throw new EncodeError('Micro QR: numeric mode accepts digits only');
|
|
11074
|
+
if (mode === 'alphanumeric' && ![...text].every((ch) => MICROQR_ALPHANUMERIC.includes(ch))) {
|
|
11075
|
+
throw new EncodeError('Micro QR: alphanumeric mode contains an unsupported character');
|
|
11076
|
+
}
|
|
11077
|
+
if (mode === 'kanji' && !kanjiValues(text)) {
|
|
11078
|
+
throw new EncodeError('Micro QR: kanji mode requires characters representable in the QR Shift_JIS ranges');
|
|
11079
|
+
}
|
|
11080
|
+
return mode;
|
|
11081
|
+
}
|
|
11082
|
+
|
|
11083
|
+
function encodePayload(text, mode) {
|
|
11084
|
+
const w = new BitWriter();
|
|
11085
|
+
if (mode === 'numeric') {
|
|
11086
|
+
for (let i = 0; i < text.length; i += 3) {
|
|
11087
|
+
const n = Math.min(3, text.length - i);
|
|
11088
|
+
w.put(Number(text.slice(i, i + n)), n === 3 ? 10 : n === 2 ? 7 : 4);
|
|
11089
|
+
}
|
|
11090
|
+
} else if (mode === 'alphanumeric') {
|
|
11091
|
+
let i = 0;
|
|
11092
|
+
for (; i + 1 < text.length; i += 2) {
|
|
11093
|
+
w.put(MICROQR_ALPHANUMERIC.indexOf(text[i]) * 45 + MICROQR_ALPHANUMERIC.indexOf(text[i + 1]), 11);
|
|
11094
|
+
}
|
|
11095
|
+
if (i < text.length) w.put(MICROQR_ALPHANUMERIC.indexOf(text[i]), 6);
|
|
11096
|
+
} else if (mode === 'byte') {
|
|
11097
|
+
w.putBytes(latin1Bytes(text));
|
|
11098
|
+
} else {
|
|
11099
|
+
for (const value of kanjiValues(text)) w.put(value, 13);
|
|
11100
|
+
}
|
|
11101
|
+
return w;
|
|
11102
|
+
}
|
|
11103
|
+
|
|
11104
|
+
function getBit(writer, index) {
|
|
11105
|
+
return ((writer.bytes[index >>> 3] >>> (7 - (index & 7))) & 1) === 1;
|
|
11106
|
+
}
|
|
11107
|
+
|
|
11108
|
+
function writeData(text, mode, version, layout) {
|
|
11109
|
+
const numericVersion = versionNumber(version);
|
|
11110
|
+
const payload = encodePayload(text, mode);
|
|
11111
|
+
const writer = new BitWriter();
|
|
11112
|
+
if (numericVersion > 1) writer.put(MODE[mode], numericVersion - 1);
|
|
11113
|
+
const count = mode === 'byte' ? latin1Bytes(text).length : [...text].length;
|
|
11114
|
+
const countWidth = COUNT_BITS[mode][numericVersion];
|
|
11115
|
+
if (countWidth === 0 || count >= 2 ** countWidth) return null;
|
|
11116
|
+
writer.put(count, countWidth);
|
|
11117
|
+
for (let i = 0; i < payload.length; i++) writer.putBit(getBit(payload, i));
|
|
11118
|
+
if (writer.length > layout.dataBits) return null;
|
|
11119
|
+
for (let i = 0, n = Math.min(2 * numericVersion + 1, layout.dataBits - writer.length); i < n; i++) writer.putBit(false);
|
|
11120
|
+
if (numericVersion !== 1 && numericVersion !== 3) {
|
|
11121
|
+
while ((writer.length & 7) && writer.length < layout.dataBits) writer.putBit(false);
|
|
11122
|
+
}
|
|
11123
|
+
if (numericVersion === 1 || numericVersion === 3) {
|
|
11124
|
+
while (writer.length < layout.dataBits) writer.putBit(false);
|
|
11125
|
+
return writer;
|
|
11126
|
+
}
|
|
11127
|
+
let pad = 0;
|
|
11128
|
+
while (writer.length + 8 <= layout.dataBits) writer.put(pad++ & 1 ? 0x11 : 0xec, 8);
|
|
11129
|
+
while (writer.length < layout.dataBits) writer.putBit(false);
|
|
11130
|
+
return writer;
|
|
11131
|
+
}
|
|
11132
|
+
|
|
11133
|
+
function finalMessage(dataWriter, layout) {
|
|
11134
|
+
const bytes = Array.from(dataWriter.toBytes());
|
|
11135
|
+
if (layout.shortDataCodewordBits === 4) bytes[bytes.length - 1] &= 0xf0;
|
|
11136
|
+
const ecc = rsEncode(bytes, layout.eccCodewords, GF256_QR, 0);
|
|
11137
|
+
const out = [];
|
|
11138
|
+
const full = layout.shortDataCodewordBits === 4 ? bytes.length - 1 : bytes.length;
|
|
11139
|
+
for (let i = 0; i < full; i++) for (let b = 7; b >= 0; b--) out.push((bytes[i] >>> b) & 1);
|
|
11140
|
+
if (layout.shortDataCodewordBits === 4) for (let b = 7; b >= 4; b--) out.push((bytes[bytes.length - 1] >>> b) & 1);
|
|
11141
|
+
for (const value of ecc) for (let b = 7; b >= 0; b--) out.push((value >>> b) & 1);
|
|
11142
|
+
return out;
|
|
11143
|
+
}
|
|
11144
|
+
|
|
11145
|
+
function drawFunctions(matrix) {
|
|
11146
|
+
const size = matrix.width;
|
|
11147
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
11148
|
+
const ring = x === 0 || x === 6 || y === 0 || y === 6;
|
|
11149
|
+
const core = x >= 2 && x <= 4 && y >= 2 && y <= 4;
|
|
11150
|
+
matrix.setValue(x, y, ring || core);
|
|
11151
|
+
}
|
|
11152
|
+
for (let i = 0; i < 8; i++) { matrix.unset(7, i); matrix.unset(i, 7); }
|
|
11153
|
+
for (let i = 8; i < size; i++) if ((i & 1) === 0) { matrix.set(i, 0); matrix.set(0, i); }
|
|
11154
|
+
}
|
|
11155
|
+
|
|
11156
|
+
function microMaskScore(matrix) {
|
|
11157
|
+
let right = 0, bottom = 0;
|
|
11158
|
+
for (let i = 1; i < matrix.width; i++) {
|
|
11159
|
+
if (matrix.get(matrix.width - 1, i)) right++;
|
|
11160
|
+
if (matrix.get(i, matrix.height - 1)) bottom++;
|
|
11161
|
+
}
|
|
11162
|
+
return Math.min(right, bottom) * 16 + Math.max(right, bottom);
|
|
11163
|
+
}
|
|
11164
|
+
|
|
11165
|
+
function buildMatrix(version, ecc, mask, bits) {
|
|
11166
|
+
const matrix = new BitMatrix(microQrVersionSize(version));
|
|
11167
|
+
drawFunctions(matrix);
|
|
11168
|
+
const order = microQrDataModuleOrder(version);
|
|
11169
|
+
for (let i = 0; i < bits.length; i++) {
|
|
11170
|
+
const x = order[i * 2], y = order[i * 2 + 1];
|
|
11171
|
+
matrix.setValue(x, y, (bits[i] === 1) !== microQrMaskBit(mask, x, y));
|
|
11172
|
+
}
|
|
11173
|
+
const format = microQrFormatInfo(microQrSymbolNumber(version, ecc), mask);
|
|
11174
|
+
const positions = microQrFormatInfoPositions(matrix.width);
|
|
11175
|
+
for (let i = 0; i < 15; i++) matrix.setValue(positions[i][0], positions[i][1], ((format >>> i) & 1) === 1);
|
|
11176
|
+
return matrix;
|
|
11177
|
+
}
|
|
11178
|
+
function encodeMicroQR(text, options = {}) {
|
|
11179
|
+
text = String(text);
|
|
11180
|
+
if (!text) throw new EncodeError('Micro QR: payload must not be empty');
|
|
11181
|
+
if (options.eci != null || options.gs1 === true) throw new EncodeError('Micro QR: ECI and GS1/FNC1 are unavailable');
|
|
11182
|
+
const mode = chooseMode(text, options.mode);
|
|
11183
|
+
const wantedVersion = parseVersion(options.version);
|
|
11184
|
+
const wantedEcc = options.ecc == null ? null : String(options.ecc).toUpperCase();
|
|
11185
|
+
if (wantedEcc === 'H') throw new EncodeError('Micro QR: error correction level H is unavailable');
|
|
11186
|
+
if (options.mask != null && (!Number.isInteger(options.mask) || options.mask < 0 || options.mask > 3)) {
|
|
11187
|
+
throw new EncodeError(`Micro QR: mask must be an integer 0-3, got ${options.mask}`);
|
|
11188
|
+
}
|
|
11189
|
+
|
|
11190
|
+
let selected;
|
|
11191
|
+
for (const version of MICROQR_VERSIONS) {
|
|
11192
|
+
if (wantedVersion != null && version !== wantedVersion) continue;
|
|
11193
|
+
const numericVersion = versionNumber(version);
|
|
11194
|
+
if (numericVersion < MODE_MIN_VERSION[mode]) continue;
|
|
11195
|
+
const levels = numericVersion === 1 ? ['DETECT'] : numericVersion < 4 ? ['L', 'M'] : ['L', 'M', 'Q'];
|
|
11196
|
+
for (const ecc of levels) {
|
|
11197
|
+
if (wantedEcc != null && ecc !== wantedEcc) continue;
|
|
11198
|
+
const layout = microQrBlockLayout(version, ecc);
|
|
11199
|
+
const data = writeData(text, mode, version, layout);
|
|
11200
|
+
if (data) { selected = { version, ecc, layout, data }; break; }
|
|
11201
|
+
}
|
|
11202
|
+
if (selected) break;
|
|
11203
|
+
}
|
|
11204
|
+
if (!selected) throw new EncodeError('Micro QR: payload does not fit the requested version/error level');
|
|
11205
|
+
const bits = finalMessage(selected.data, selected.layout);
|
|
11206
|
+
if (options.mask != null) return buildMatrix(selected.version, selected.ecc, options.mask, bits);
|
|
11207
|
+
let best, score = -1;
|
|
11208
|
+
for (let mask = 0; mask < 4; mask++) {
|
|
11209
|
+
const candidate = buildMatrix(selected.version, selected.ecc, mask, bits);
|
|
11210
|
+
const candidateScore = microMaskScore(candidate);
|
|
11211
|
+
if (candidateScore > score) { score = candidateScore; best = candidate; }
|
|
11212
|
+
}
|
|
11213
|
+
return best;
|
|
11214
|
+
}
|
|
11215
|
+
|
|
11216
|
+
__exports.MICROQR_ALPHANUMERIC = MICROQR_ALPHANUMERIC;
|
|
11217
|
+
__exports.encodeMicroQR = encodeMicroQR;
|
|
11218
|
+
};
|
|
11219
|
+
|
|
11220
|
+
__modules["microqr/decoder.js"] = function (__require, __exports) {
|
|
11221
|
+
/**
|
|
11222
|
+
* Micro QR decoder for an already sampled M1-M4 module matrix.
|
|
11223
|
+
*
|
|
11224
|
+
* @module microqr/decoder
|
|
11225
|
+
*/
|
|
11226
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
11227
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
11228
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
11229
|
+
const { microQrBlockLayout, microQrDataModuleOrder, microQrDecodeFormatInfo, microQrFormatInfoPositions, microQrMaskBit } = __require("microqr/tables.js");
|
|
11230
|
+
|
|
11231
|
+
const ALPHANUMERIC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
11232
|
+
const MODE_NAMES = ['numeric', 'alphanumeric', 'byte', 'kanji'];
|
|
11233
|
+
const COUNT_BITS = {
|
|
11234
|
+
numeric: [0, 3, 4, 5, 6],
|
|
11235
|
+
alphanumeric: [0, 0, 3, 4, 5],
|
|
11236
|
+
byte: [0, 0, 0, 4, 5],
|
|
11237
|
+
kanji: [0, 0, 0, 3, 4],
|
|
11238
|
+
};
|
|
11239
|
+
|
|
11240
|
+
class LimitedBitReader {
|
|
11241
|
+
constructor(bytes, limit) {
|
|
11242
|
+
this.bytes = bytes;
|
|
11243
|
+
this.limit = limit;
|
|
11244
|
+
this.offset = 0;
|
|
11245
|
+
}
|
|
11246
|
+
|
|
11247
|
+
available() { return this.limit - this.offset; }
|
|
11248
|
+
|
|
11249
|
+
read(count) {
|
|
11250
|
+
if (!Number.isInteger(count) || count < 1 || count > 32 || count > this.available()) {
|
|
11251
|
+
throw new FormatError(`Micro QR: needed ${count} bits, ${Math.max(0, this.available())} remain`);
|
|
11252
|
+
}
|
|
11253
|
+
let value = 0;
|
|
11254
|
+
for (let i = 0; i < count; i++, this.offset++) {
|
|
11255
|
+
value = (value << 1) | ((this.bytes[this.offset >>> 3] >>> (7 - (this.offset & 7))) & 1);
|
|
11256
|
+
}
|
|
11257
|
+
return value >>> 0;
|
|
11258
|
+
}
|
|
11259
|
+
}
|
|
11260
|
+
|
|
11261
|
+
function moduleAt(matrix, x, y, mirrored) {
|
|
11262
|
+
return mirrored ? matrix.get(y, x) : matrix.get(x, y);
|
|
11263
|
+
}
|
|
11264
|
+
|
|
11265
|
+
function readFormat(matrix, expectedVersion, mirrored) {
|
|
11266
|
+
let bits = 0;
|
|
11267
|
+
const positions = microQrFormatInfoPositions(matrix.width);
|
|
11268
|
+
for (let i = 0; i < positions.length; i++) {
|
|
11269
|
+
const [x, y] = positions[i];
|
|
11270
|
+
if (moduleAt(matrix, x, y, mirrored)) bits |= 1 << i;
|
|
11271
|
+
}
|
|
11272
|
+
const format = microQrDecodeFormatInfo(bits);
|
|
11273
|
+
if (!format) throw new FormatError('Micro QR: format information is unreadable');
|
|
11274
|
+
if (format.version !== expectedVersion) {
|
|
11275
|
+
throw new FormatError(
|
|
11276
|
+
`Micro QR: format identifies ${format.version}, but the matrix dimension identifies ${expectedVersion}`,
|
|
11277
|
+
);
|
|
11278
|
+
}
|
|
11279
|
+
return format;
|
|
11280
|
+
}
|
|
11281
|
+
|
|
11282
|
+
function readCodewords(matrix, layout, mask, mirrored) {
|
|
11283
|
+
const order = microQrDataModuleOrder(layout.version);
|
|
11284
|
+
const data = new Array(layout.dataCodewords).fill(0);
|
|
11285
|
+
const ecc = new Array(layout.eccCodewords).fill(0);
|
|
11286
|
+
let streamOffset = 0;
|
|
11287
|
+
|
|
11288
|
+
const readBit = () => {
|
|
11289
|
+
const x = order[streamOffset * 2];
|
|
11290
|
+
const y = order[streamOffset * 2 + 1];
|
|
11291
|
+
if (x === undefined || y === undefined) throw new FormatError('Micro QR: encoding region is truncated');
|
|
11292
|
+
streamOffset++;
|
|
11293
|
+
return moduleAt(matrix, x, y, mirrored) !== microQrMaskBit(mask, x, y) ? 1 : 0;
|
|
11294
|
+
};
|
|
11295
|
+
const readInto = (target, index, count, highBit = 7) => {
|
|
11296
|
+
for (let bit = highBit; bit > highBit - count; bit--) target[index] |= readBit() << bit;
|
|
11297
|
+
};
|
|
11298
|
+
|
|
11299
|
+
const fullData = layout.shortDataCodewordBits === 4 ? layout.dataCodewords - 1 : layout.dataCodewords;
|
|
11300
|
+
for (let i = 0; i < fullData; i++) readInto(data, i, 8);
|
|
11301
|
+
if (layout.shortDataCodewordBits === 4) readInto(data, data.length - 1, 4);
|
|
11302
|
+
for (let i = 0; i < ecc.length; i++) readInto(ecc, i, 8);
|
|
11303
|
+
|
|
11304
|
+
if (streamOffset !== order.length / 2) {
|
|
11305
|
+
throw new FormatError(`Micro QR: read ${streamOffset} of ${order.length / 2} encoding modules`);
|
|
11306
|
+
}
|
|
11307
|
+
return data.concat(ecc);
|
|
11308
|
+
}
|
|
11309
|
+
|
|
11310
|
+
function correctCodewords(received, layout) {
|
|
11311
|
+
const corrections = rsDecode(received, layout.eccCodewords, GF256_QR, 0);
|
|
11312
|
+
if (layout.version === 'M1' && corrections !== 0) {
|
|
11313
|
+
throw new ChecksumError('Micro QR: M1 provides error detection only');
|
|
11314
|
+
}
|
|
11315
|
+
return { data: Uint8Array.from(received.slice(0, layout.dataCodewords)), corrections };
|
|
11316
|
+
}
|
|
11317
|
+
|
|
11318
|
+
function decodeKanjiValue(value) {
|
|
11319
|
+
const combined = (Math.floor(value / 0xc0) << 8) | (value % 0xc0);
|
|
11320
|
+
const sjis = combined + (combined < 0x1f00 ? 0x8140 : 0xc140);
|
|
11321
|
+
const bytes = Uint8Array.of(sjis >>> 8, sjis & 0xff);
|
|
11322
|
+
try {
|
|
11323
|
+
return new TextDecoder('shift_jis', { fatal: true }).decode(bytes);
|
|
11324
|
+
} catch {
|
|
11325
|
+
throw new FormatError(`Micro QR: invalid Kanji value ${value}`);
|
|
11326
|
+
}
|
|
11327
|
+
}
|
|
11328
|
+
|
|
11329
|
+
function parsePayload(data, version, dataBits) {
|
|
11330
|
+
const reader = new LimitedBitReader(data, dataBits);
|
|
11331
|
+
const modeValue = version === 1 ? 0 : reader.read(version - 1);
|
|
11332
|
+
if (modeValue > 3 || (version === 2 && modeValue > 1)) {
|
|
11333
|
+
throw new FormatError(`Micro QR: mode indicator ${modeValue} is unavailable in M${version}`);
|
|
11334
|
+
}
|
|
11335
|
+
const mode = MODE_NAMES[modeValue];
|
|
11336
|
+
const countWidth = COUNT_BITS[mode][version];
|
|
11337
|
+
if (!countWidth) throw new FormatError(`Micro QR: ${mode} mode is unavailable in M${version}`);
|
|
11338
|
+
const count = reader.read(countWidth);
|
|
11339
|
+
if (count === 0) throw new FormatError('Micro QR: zero-length data segment');
|
|
11340
|
+
|
|
11341
|
+
let text = '';
|
|
11342
|
+
const rawBytes = [];
|
|
11343
|
+
if (mode === 'numeric') {
|
|
11344
|
+
let remaining = count;
|
|
11345
|
+
while (remaining >= 3) {
|
|
11346
|
+
const value = reader.read(10);
|
|
11347
|
+
if (value >= 1000) throw new FormatError(`Micro QR: invalid numeric triplet ${value}`);
|
|
11348
|
+
text += String(value).padStart(3, '0');
|
|
11349
|
+
remaining -= 3;
|
|
11350
|
+
}
|
|
11351
|
+
if (remaining === 2) {
|
|
11352
|
+
const value = reader.read(7);
|
|
11353
|
+
if (value >= 100) throw new FormatError(`Micro QR: invalid numeric pair ${value}`);
|
|
11354
|
+
text += String(value).padStart(2, '0');
|
|
11355
|
+
} else if (remaining === 1) {
|
|
11356
|
+
const value = reader.read(4);
|
|
11357
|
+
if (value >= 10) throw new FormatError(`Micro QR: invalid numeric digit ${value}`);
|
|
11358
|
+
text += String(value);
|
|
11359
|
+
}
|
|
11360
|
+
} else if (mode === 'alphanumeric') {
|
|
11361
|
+
let remaining = count;
|
|
11362
|
+
while (remaining >= 2) {
|
|
11363
|
+
const value = reader.read(11);
|
|
11364
|
+
if (value >= 45 * 45) throw new FormatError(`Micro QR: invalid alphanumeric pair ${value}`);
|
|
11365
|
+
text += ALPHANUMERIC[Math.floor(value / 45)] + ALPHANUMERIC[value % 45];
|
|
11366
|
+
remaining -= 2;
|
|
11367
|
+
}
|
|
11368
|
+
if (remaining === 1) {
|
|
11369
|
+
const value = reader.read(6);
|
|
11370
|
+
if (value >= 45) throw new FormatError(`Micro QR: invalid alphanumeric value ${value}`);
|
|
11371
|
+
text += ALPHANUMERIC[value];
|
|
11372
|
+
}
|
|
11373
|
+
} else if (mode === 'byte') {
|
|
11374
|
+
for (let i = 0; i < count; i++) {
|
|
11375
|
+
const value = reader.read(8);
|
|
11376
|
+
rawBytes.push(value);
|
|
11377
|
+
text += String.fromCharCode(value);
|
|
11378
|
+
}
|
|
11379
|
+
} else {
|
|
11380
|
+
for (let i = 0; i < count; i++) text += decodeKanjiValue(reader.read(13));
|
|
11381
|
+
}
|
|
11382
|
+
return { text, bytes: Uint8Array.from(rawBytes), mode };
|
|
11383
|
+
}
|
|
11384
|
+
|
|
11385
|
+
function decodeOrientation(matrix, expectedVersion, mirrored) {
|
|
11386
|
+
const format = readFormat(matrix, expectedVersion, mirrored);
|
|
11387
|
+
const layout = microQrBlockLayout(format.version, format.ecc);
|
|
11388
|
+
const received = readCodewords(matrix, layout, format.mask, mirrored);
|
|
11389
|
+
const { data, corrections } = correctCodewords(received, layout);
|
|
11390
|
+
const payload = parsePayload(data, Number(format.version.slice(1)), layout.dataBits);
|
|
11391
|
+
return {
|
|
11392
|
+
text: payload.text,
|
|
11393
|
+
bytes: payload.bytes,
|
|
11394
|
+
mode: payload.mode,
|
|
11395
|
+
version: format.version,
|
|
11396
|
+
ecc: format.ecc,
|
|
11397
|
+
mask: format.mask,
|
|
11398
|
+
corrections,
|
|
11399
|
+
formatCorrections: format.correctedBits,
|
|
11400
|
+
mirrored,
|
|
11401
|
+
};
|
|
11402
|
+
}
|
|
11403
|
+
|
|
11404
|
+
/** Decode a sampled Micro QR Code symbol without its quiet zone. */
|
|
11405
|
+
function decodeMicroQR(matrix) {
|
|
11406
|
+
if (!matrix || !Number.isInteger(matrix.width) || typeof matrix.get !== 'function') {
|
|
11407
|
+
throw new FormatError('Micro QR: no matrix supplied');
|
|
11408
|
+
}
|
|
11409
|
+
if (matrix.height !== matrix.width) {
|
|
11410
|
+
throw new FormatError(`Micro QR: symbol must be square, got ${matrix.width}x${matrix.height}`);
|
|
11411
|
+
}
|
|
11412
|
+
const version = (matrix.width - 9) / 2;
|
|
11413
|
+
if (!Number.isInteger(version) || version < 1 || version > 4) {
|
|
11414
|
+
throw new FormatError(`Micro QR: ${matrix.width} modules is not a valid M1-M4 symbol size`);
|
|
11415
|
+
}
|
|
11416
|
+
const expectedVersion = `M${version}`;
|
|
11417
|
+
try {
|
|
11418
|
+
return decodeOrientation(matrix, expectedVersion, false);
|
|
11419
|
+
} catch (primaryError) {
|
|
11420
|
+
try {
|
|
11421
|
+
return decodeOrientation(matrix, expectedVersion, true);
|
|
11422
|
+
} catch {
|
|
11423
|
+
throw primaryError;
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
}
|
|
11427
|
+
__exports.ChecksumError = ChecksumError; __exports.FormatError = FormatError;
|
|
11428
|
+
|
|
11429
|
+
__exports.decodeMicroQR = decodeMicroQR;
|
|
11430
|
+
};
|
|
11431
|
+
|
|
11432
|
+
__modules["microqr/detector.js"] = function (__require, __exports) {
|
|
11433
|
+
/**
|
|
11434
|
+
* Micro QR detection in binarized rasters.
|
|
11435
|
+
*
|
|
11436
|
+
* A Micro QR symbol has one 7x7 finder in its top-left corner. That alone is
|
|
11437
|
+
* not enough to distinguish it from one corner of a normal QR Code, so every
|
|
11438
|
+
* candidate is also required to have the Micro QR timing arms and a format
|
|
11439
|
+
* word which the decoder accepts. The decoder is deliberately the final
|
|
11440
|
+
* geometric arbiter; BCH and Reed--Solomon verification make accidental
|
|
11441
|
+
* acceptance of ordinary square artwork very unlikely.
|
|
11442
|
+
*
|
|
11443
|
+
* Finder geometry supplies two local axes. Timing arms refine their lengths,
|
|
11444
|
+
* while a small fourth-corner search lets projective sampling absorb mild
|
|
11445
|
+
* perspective despite the format having no remote alignment pattern.
|
|
11446
|
+
*
|
|
11447
|
+
* @module microqr/detector
|
|
11448
|
+
*/
|
|
11449
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11450
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
11451
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
11452
|
+
const { decodeMicroQR } = __require("microqr/decoder.js");
|
|
11453
|
+
|
|
11454
|
+
/** Legal Micro QR side lengths (M1 through M4). */
|
|
11455
|
+
const DIMENSIONS = [11, 13, 15, 17];
|
|
11456
|
+
|
|
11457
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
11458
|
+
|
|
11459
|
+
/**
|
|
11460
|
+
* @typedef {object} Detection
|
|
11461
|
+
* @property {Point[]} corners Outer corners in reading order.
|
|
11462
|
+
* @property {number} dimension Side length in modules.
|
|
11463
|
+
* @property {'M1'|'M2'|'M3'|'M4'} version
|
|
11464
|
+
* @property {number} moduleSize Estimated pixels per module at the finder.
|
|
11465
|
+
* @property {number} rotation Clockwise orientation of the source raster.
|
|
11466
|
+
* @property {boolean} inverted Whether the detected symbol used inverted polarity.
|
|
11467
|
+
* @property {BitMatrix} matrix Rectified, normally polarised module matrix.
|
|
11468
|
+
*/
|
|
11469
|
+
|
|
11470
|
+
function rotateVector(vector) {
|
|
11471
|
+
return { x: -vector.y, y: vector.x };
|
|
11472
|
+
}
|
|
11473
|
+
|
|
11474
|
+
function add(point, a, av, b, bv) {
|
|
11475
|
+
return { x: point.x + a.x * av + b.x * bv, y: point.y + a.y * av + b.y * bv };
|
|
11476
|
+
}
|
|
11477
|
+
|
|
11478
|
+
function sample(image, point) {
|
|
11479
|
+
const x = Math.round(point.x);
|
|
11480
|
+
const y = Math.round(point.y);
|
|
11481
|
+
if (x < 0 || y < 0 || x >= image.width || y >= image.height) return null;
|
|
11482
|
+
return image.get(x, y);
|
|
11483
|
+
}
|
|
11484
|
+
|
|
11485
|
+
function expectedFinder(x, y) {
|
|
11486
|
+
return x === 0 || y === 0 || x === 6 || y === 6 ||
|
|
11487
|
+
(x >= 2 && x <= 4 && y >= 2 && y <= 4);
|
|
11488
|
+
}
|
|
11489
|
+
|
|
11490
|
+
/** Connected components matching one polarity, capped to plausible centre blocks. */
|
|
11491
|
+
function components(image, value) {
|
|
11492
|
+
const seen = new Uint8Array(image.width * image.height);
|
|
11493
|
+
const result = [];
|
|
11494
|
+
const maximumArea = Math.max(16, Math.floor(image.width * image.height * 0.08));
|
|
11495
|
+
|
|
11496
|
+
for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
|
|
11497
|
+
const start = y * image.width + x;
|
|
11498
|
+
if (seen[start] || image.get(x, y) !== value) continue;
|
|
11499
|
+
|
|
11500
|
+
const queueX = [x];
|
|
11501
|
+
const queueY = [y];
|
|
11502
|
+
seen[start] = 1;
|
|
11503
|
+
let head = 0;
|
|
11504
|
+
let minX = x; let maxX = x; let minY = y; let maxY = y;
|
|
11505
|
+
|
|
11506
|
+
while (head < queueX.length) {
|
|
11507
|
+
const px = queueX[head];
|
|
11508
|
+
const py = queueY[head++];
|
|
11509
|
+
if (px < minX) minX = px;
|
|
11510
|
+
if (px > maxX) maxX = px;
|
|
11511
|
+
if (py < minY) minY = py;
|
|
11512
|
+
if (py > maxY) maxY = py;
|
|
11513
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
11514
|
+
if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height) continue;
|
|
11515
|
+
const index = ny * image.width + nx;
|
|
11516
|
+
if (!seen[index] && image.get(nx, ny) === value) {
|
|
11517
|
+
seen[index] = 1;
|
|
11518
|
+
queueX.push(nx);
|
|
11519
|
+
queueY.push(ny);
|
|
11520
|
+
}
|
|
11521
|
+
}
|
|
11522
|
+
}
|
|
11523
|
+
|
|
11524
|
+
const width = maxX - minX + 1;
|
|
11525
|
+
const height = maxY - minY + 1;
|
|
11526
|
+
const area = width * height;
|
|
11527
|
+
if (queueX.length > maximumArea || Math.min(width, height) < 2) continue;
|
|
11528
|
+
if (Math.max(width, height) > Math.min(width, height) * 1.7) continue;
|
|
11529
|
+
if (queueX.length < area * 0.42) continue;
|
|
11530
|
+
result.push({
|
|
11531
|
+
x: (minX + maxX) / 2,
|
|
11532
|
+
y: (minY + maxY) / 2,
|
|
11533
|
+
width,
|
|
11534
|
+
height,
|
|
11535
|
+
pixels: queueX.length,
|
|
11536
|
+
});
|
|
11537
|
+
}
|
|
11538
|
+
|
|
11539
|
+
return result.sort((a, b) => b.pixels - a.pixels).slice(0, 256);
|
|
11540
|
+
}
|
|
11541
|
+
|
|
11542
|
+
/** Score the complete 7x7 finder at module centres. */
|
|
11543
|
+
function finderScore(image, centre, u, v, pitch, inverted) {
|
|
11544
|
+
let correct = 0;
|
|
11545
|
+
let total = 0;
|
|
11546
|
+
for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
|
|
11547
|
+
const actual = sample(image, add(centre, u, (x - 3) * pitch, v, (y - 3) * pitch));
|
|
11548
|
+
if (actual === null) continue;
|
|
11549
|
+
const wanted = inverted ? !expectedFinder(x, y) : expectedFinder(x, y);
|
|
11550
|
+
if (actual === wanted) correct++;
|
|
11551
|
+
total++;
|
|
11552
|
+
}
|
|
11553
|
+
return total === 49 ? correct / total : 0;
|
|
11554
|
+
}
|
|
11555
|
+
|
|
11556
|
+
/** Validate separator, timing arms and a sparse quiet-zone outline. */
|
|
11557
|
+
function structureScore(image, centre, u, v, pitch, dimension, sx, sy, inverted) {
|
|
11558
|
+
let correct = 0;
|
|
11559
|
+
let total = 0;
|
|
11560
|
+
const check = (x, y, dark) => {
|
|
11561
|
+
const point = add(centre, u, (x - 3) * pitch * sx, v, (y - 3) * pitch * sy);
|
|
11562
|
+
const actual = sample(image, point);
|
|
11563
|
+
if (actual !== null && actual === (inverted ? !dark : dark)) correct++;
|
|
11564
|
+
total++;
|
|
11565
|
+
};
|
|
11566
|
+
|
|
11567
|
+
// The light separator lies between the finder and encoding region.
|
|
11568
|
+
for (let i = 0; i <= 7; i++) {
|
|
11569
|
+
check(7, i, false);
|
|
11570
|
+
check(i, 7, false);
|
|
11571
|
+
}
|
|
11572
|
+
// Both timing arms start dark at coordinate 8 and alternate to the edge.
|
|
11573
|
+
for (let i = 8; i < dimension; i++) {
|
|
11574
|
+
check(i, 0, (i & 1) === 0);
|
|
11575
|
+
check(0, i, (i & 1) === 0);
|
|
11576
|
+
}
|
|
11577
|
+
// A quiet-zone sample just beyond each edge rejects an isolated normal-QR
|
|
11578
|
+
// finder and most decorative squares without requiring a perfect crop.
|
|
11579
|
+
for (let i = 0; i < dimension; i += 2) {
|
|
11580
|
+
check(i, -1.25, false);
|
|
11581
|
+
check(-1.25, i, false);
|
|
11582
|
+
check(i, dimension + 0.75, false);
|
|
11583
|
+
check(dimension + 0.75, i, false);
|
|
11584
|
+
}
|
|
11585
|
+
return correct / total;
|
|
11586
|
+
}
|
|
11587
|
+
|
|
11588
|
+
function invert(matrix) {
|
|
11589
|
+
const out = matrix.clone();
|
|
11590
|
+
for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
|
|
11591
|
+
return out;
|
|
11592
|
+
}
|
|
11593
|
+
|
|
11594
|
+
function orientationDegrees(u) {
|
|
11595
|
+
const degrees = Math.atan2(u.y, u.x) * 180 / Math.PI;
|
|
11596
|
+
return ((Math.round(degrees / 90) * 90) % 360 + 360) % 360;
|
|
11597
|
+
}
|
|
11598
|
+
|
|
11599
|
+
function cornersFor(centre, u, v, pitch, dimension, sx, sy, dx = 0, dy = 0) {
|
|
11600
|
+
const tl = add(centre, u, -3.5 * pitch, v, -3.5 * pitch);
|
|
11601
|
+
const tr = add(tl, u, dimension * pitch * sx, v, 0);
|
|
11602
|
+
const bl = add(tl, u, 0, v, dimension * pitch * sy);
|
|
11603
|
+
const br = add(add(tr, v, dimension * pitch * sy, u, 0), u, dx * pitch, v, dy * pitch);
|
|
11604
|
+
return [tl, tr, br, bl];
|
|
11605
|
+
}
|
|
11606
|
+
|
|
11607
|
+
function candidateKey(detection) {
|
|
11608
|
+
const centre = detection.finderCentre;
|
|
11609
|
+
return `${Math.round(centre.x)},${Math.round(centre.y)},${detection.dimension}`;
|
|
11610
|
+
}
|
|
11611
|
+
|
|
11612
|
+
function sameCandidate(left, right) {
|
|
11613
|
+
if (left.dimension !== right.dimension) return false;
|
|
11614
|
+
const centre = (detection) => detection.corners.reduce(
|
|
11615
|
+
(sum, point) => ({ x: sum.x + point.x / 4, y: sum.y + point.y / 4 }),
|
|
11616
|
+
{ x: 0, y: 0 },
|
|
11617
|
+
);
|
|
11618
|
+
const a = centre(left);
|
|
11619
|
+
const b = centre(right);
|
|
11620
|
+
const tolerance = Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
11621
|
+
return Math.hypot(a.x - b.x, a.y - b.y) < tolerance;
|
|
11622
|
+
}
|
|
11623
|
+
|
|
11624
|
+
/**
|
|
11625
|
+
* Find Micro QR symbols in a binarized raster.
|
|
11626
|
+
*
|
|
11627
|
+
* The search accepts arbitrary in-plane angles, including all quarter-turns.
|
|
11628
|
+
* Non-integer scale is supported through centre sampling. Mild projective
|
|
11629
|
+
* distortion is handled by searching the unconstrained fourth corner.
|
|
11630
|
+
*
|
|
11631
|
+
* @param {BitMatrix} binaryImage Set bit = dark.
|
|
11632
|
+
* @returns {Detection[]} Best candidate first; empty when no symbol is found.
|
|
11633
|
+
*/
|
|
11634
|
+
function detectMicroQR(binaryImage) {
|
|
11635
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
11636
|
+
throw new NotFoundError('detectMicroQR: no image supplied');
|
|
11637
|
+
}
|
|
11638
|
+
|
|
11639
|
+
const detections = [];
|
|
11640
|
+
const seen = new Set();
|
|
11641
|
+
|
|
11642
|
+
for (const inverted of [false, true]) {
|
|
11643
|
+
for (const centre of components(binaryImage, !inverted)) {
|
|
11644
|
+
for (let degrees = 0; degrees < 180; degrees += 3) {
|
|
11645
|
+
const angle = degrees * Math.PI / 180;
|
|
11646
|
+
const axis = { x: Math.cos(angle), y: Math.sin(angle) };
|
|
11647
|
+
const perpendicular = rotateVector(axis);
|
|
11648
|
+
const footprint = Math.abs(axis.x) + Math.abs(axis.y);
|
|
11649
|
+
const pitch = ((centre.width + centre.height) / 2) / (3 * footprint);
|
|
11650
|
+
if (pitch < 0.75) continue;
|
|
11651
|
+
|
|
11652
|
+
// The finder is rotationally symmetric; four turns decide which pair
|
|
11653
|
+
// of arms points into the encoding region.
|
|
11654
|
+
for (let turn = 0, u = axis, v = perpendicular; turn < 4; turn++) {
|
|
11655
|
+
if (turn > 0) { u = v; v = { x: -u.y, y: u.x }; }
|
|
11656
|
+
const fScore = finderScore(binaryImage, centre, u, v, pitch, inverted);
|
|
11657
|
+
if (fScore < 0.9) continue;
|
|
11658
|
+
|
|
11659
|
+
for (const dimension of DIMENSIONS) {
|
|
11660
|
+
const scales = [0.84, 0.92, 1, 1.08, 1.16];
|
|
11661
|
+
const rankedX = scales.map((scale) => ({
|
|
11662
|
+
scale,
|
|
11663
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, scale, 1, inverted),
|
|
11664
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
11665
|
+
const rankedY = scales.map((scale) => ({
|
|
11666
|
+
scale,
|
|
11667
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, 1, scale, inverted),
|
|
11668
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
11669
|
+
|
|
11670
|
+
for (const xs of rankedX) for (const ys of rankedY) {
|
|
11671
|
+
const score = structureScore(binaryImage, centre, u, v, pitch, dimension, xs.scale, ys.scale, inverted);
|
|
11672
|
+
if (score < 0.78) continue;
|
|
11673
|
+
|
|
11674
|
+
// With a single finder there is no direct bottom-right anchor.
|
|
11675
|
+
// A compact search around the affine estimate lets the projective
|
|
11676
|
+
// sampler account for convergence of the remote edges.
|
|
11677
|
+
for (const delta of [[0, 0], [-0.75, 0], [0.75, 0], [0, -0.75], [0, 0.75],
|
|
11678
|
+
[-0.75, -0.75], [0.75, -0.75], [-0.75, 0.75], [0.75, 0.75]]) {
|
|
11679
|
+
const corners = cornersFor(
|
|
11680
|
+
centre, u, v, pitch, dimension, xs.scale, ys.scale, delta[0], delta[1]
|
|
11681
|
+
);
|
|
11682
|
+
let matrix;
|
|
11683
|
+
try { matrix = sampleQuad(binaryImage, dimension, corners, score < 0.9); }
|
|
11684
|
+
catch (error) { continue; }
|
|
11685
|
+
if (inverted) matrix = invert(matrix);
|
|
11686
|
+
|
|
11687
|
+
try {
|
|
11688
|
+
const decoded = decodeMicroQR(matrix);
|
|
11689
|
+
const version = decoded.version ?? `M${(dimension - 9) / 2}`;
|
|
11690
|
+
const detection = {
|
|
11691
|
+
corners,
|
|
11692
|
+
dimension,
|
|
11693
|
+
version,
|
|
11694
|
+
moduleSize: pitch,
|
|
11695
|
+
rotation: orientationDegrees(u),
|
|
11696
|
+
inverted,
|
|
11697
|
+
matrix,
|
|
11698
|
+
finderCentre: { x: centre.x, y: centre.y },
|
|
11699
|
+
score: fScore + score,
|
|
11700
|
+
};
|
|
11701
|
+
const key = candidateKey(detection);
|
|
11702
|
+
if (!seen.has(key) && !detections.some((entry) => sameCandidate(entry, detection))) {
|
|
11703
|
+
seen.add(key);
|
|
11704
|
+
detections.push(detection);
|
|
11705
|
+
}
|
|
11706
|
+
// Decoder validation settled this dimension and orientation.
|
|
11707
|
+
break;
|
|
11708
|
+
} catch (error) {
|
|
11709
|
+
/* Try the next perspective hypothesis. */
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11716
|
+
}
|
|
11717
|
+
}
|
|
11718
|
+
|
|
11719
|
+
detections.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
|
|
11720
|
+
for (const detection of detections) {
|
|
11721
|
+
delete detection.finderCentre;
|
|
11722
|
+
delete detection.score;
|
|
11723
|
+
}
|
|
11724
|
+
return detections;
|
|
11725
|
+
}
|
|
11726
|
+
|
|
11727
|
+
/**
|
|
11728
|
+
* Detect and decode all Micro QR symbols in a binarized raster.
|
|
11729
|
+
*
|
|
11730
|
+
* @param {BitMatrix} binaryImage
|
|
11731
|
+
* @returns {Array<object>}
|
|
11732
|
+
*/
|
|
11733
|
+
function detectAndDecodeMicroQR(binaryImage) {
|
|
11734
|
+
let detections;
|
|
11735
|
+
try { detections = detectMicroQR(binaryImage); }
|
|
11736
|
+
catch (error) { return []; }
|
|
11737
|
+
|
|
11738
|
+
const results = [];
|
|
11739
|
+
const seen = new Set();
|
|
11740
|
+
for (const detection of detections) {
|
|
11741
|
+
try {
|
|
11742
|
+
const decoded = decodeMicroQR(detection.matrix);
|
|
11743
|
+
const key = `${decoded.version ?? detection.version}|${decoded.text ?? ''}`;
|
|
11744
|
+
if (seen.has(key)) continue;
|
|
11745
|
+
seen.add(key);
|
|
11746
|
+
results.push(Object.assign({
|
|
11747
|
+
corners: detection.corners,
|
|
11748
|
+
rotation: detection.rotation,
|
|
11749
|
+
inverted: detection.inverted,
|
|
11750
|
+
}, decoded));
|
|
11751
|
+
} catch (error) {
|
|
11752
|
+
/* A failed candidate is a normal no-symbol result. */
|
|
11753
|
+
}
|
|
11754
|
+
}
|
|
11755
|
+
return results;
|
|
11756
|
+
}
|
|
11757
|
+
|
|
11758
|
+
__exports.detectMicroQR = detectMicroQR;
|
|
11759
|
+
__exports.detectAndDecodeMicroQR = detectAndDecodeMicroQR;
|
|
11760
|
+
};
|
|
11761
|
+
|
|
11762
|
+
__modules["microqr/index.js"] = function (__require, __exports) {
|
|
11763
|
+
/** Micro QR Code M1-M4 public module surface. @module microqr */
|
|
11764
|
+
const __reexport0 = __require("microqr/encoder.js"); __exports.encodeMicroQR = __reexport0.encodeMicroQR;
|
|
11765
|
+
const __reexport1 = __require("microqr/decoder.js"); __exports.decodeMicroQR = __reexport1.decodeMicroQR;
|
|
11766
|
+
const __reexport2 = __require("microqr/detector.js"); __exports.detectMicroQR = __reexport2.detectMicroQR; __exports.detectAndDecodeMicroQR = __reexport2.detectAndDecodeMicroQR;
|
|
11767
|
+
const __reexport3 = __require("microqr/tables.js"); __exports.validateMicroQrTables = __reexport3.validateMicroQrTables;
|
|
11768
|
+
|
|
11769
|
+
|
|
11770
|
+
};
|
|
11771
|
+
|
|
11772
|
+
__modules["rmqr/tables.js"] = function (__require, __exports) {
|
|
11773
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11774
|
+
|
|
11775
|
+
/** The 32 rMQR dimensions in ISO/IEC 23941 order (width, height). */
|
|
11776
|
+
const RMQR_SIZES = Object.freeze([
|
|
11777
|
+
[43, 7], [59, 7], [77, 7], [99, 7], [139, 7],
|
|
11778
|
+
[43, 9], [59, 9], [77, 9], [99, 9], [139, 9],
|
|
11779
|
+
[27, 11], [43, 11], [59, 11], [77, 11], [99, 11], [139, 11],
|
|
11780
|
+
[27, 13], [43, 13], [59, 13], [77, 13], [99, 13], [139, 13],
|
|
11781
|
+
[43, 15], [59, 15], [77, 15], [99, 15], [139, 15],
|
|
11782
|
+
[43, 17], [59, 17], [77, 17], [99, 17], [139, 17],
|
|
11783
|
+
]);
|
|
11784
|
+
|
|
11785
|
+
const REMAINDER_BITS = Object.freeze([0, 3, 5, 6, 1, 2, 3, 1, 4, 5, 2, 1, 0, 2, 7, 6, 4, 1, 6, 4, 3, 0, 1, 4, 6, 7, 2, 1, 2, 0, 3, 4]);
|
|
11786
|
+
const TOTAL_CODEWORDS = Object.freeze([13, 21, 32, 44, 68, 21, 33, 49, 66, 99, 15, 31, 47, 67, 89, 132, 21, 41, 60, 85, 113, 166, 51, 74, 103, 136, 199, 61, 88, 122, 160, 232]);
|
|
11787
|
+
|
|
11788
|
+
// Each block is [number of blocks, total codewords per block, data codewords per block].
|
|
11789
|
+
const M_BLOCKS = [
|
|
11790
|
+
[[1, 13, 6]], [[1, 21, 12]], [[1, 32, 20]], [[1, 44, 28]], [[1, 68, 44]],
|
|
11791
|
+
[[1, 21, 12]], [[1, 33, 21]], [[1, 49, 31]], [[1, 66, 42]], [[1, 49, 31], [1, 50, 32]],
|
|
11792
|
+
[[1, 15, 7]], [[1, 31, 19]], [[1, 47, 31]], [[1, 67, 43]], [[1, 44, 28], [1, 45, 29]], [[2, 66, 42]],
|
|
11793
|
+
[[1, 21, 14]], [[1, 41, 27]], [[1, 60, 38]], [[1, 42, 26], [1, 43, 27]], [[1, 56, 36], [1, 57, 37]], [[2, 55, 35], [1, 56, 36]],
|
|
11794
|
+
[[1, 51, 33]], [[1, 74, 48]], [[1, 51, 33], [1, 52, 34]], [[2, 68, 44]], [[2, 66, 42], [1, 67, 43]],
|
|
11795
|
+
[[1, 60, 39]], [[2, 44, 28]], [[2, 61, 39]], [[2, 53, 33], [1, 54, 34]], [[4, 58, 38]],
|
|
11796
|
+
];
|
|
11797
|
+
const H_BLOCKS = [
|
|
11798
|
+
[[1, 13, 3]], [[1, 21, 7]], [[1, 32, 10]], [[1, 44, 14]], [[2, 34, 12]],
|
|
11799
|
+
[[1, 21, 7]], [[1, 33, 11]], [[1, 24, 8], [1, 25, 9]], [[2, 33, 11]], [[3, 33, 11]],
|
|
11800
|
+
[[1, 15, 5]], [[1, 31, 11]], [[1, 23, 7], [1, 24, 8]], [[1, 33, 11], [1, 34, 12]], [[1, 44, 14], [1, 45, 15]], [[3, 44, 14]],
|
|
11801
|
+
[[1, 21, 7]], [[1, 41, 13]], [[2, 30, 10]], [[1, 42, 14], [1, 43, 15]], [[1, 37, 11], [2, 38, 12]], [[2, 41, 13], [2, 42, 14]],
|
|
11802
|
+
[[1, 25, 7], [1, 26, 8]], [[2, 37, 13]], [[2, 34, 10], [1, 35, 11]], [[4, 34, 12]], [[1, 39, 13], [4, 40, 14]],
|
|
11803
|
+
[[1, 30, 10], [1, 31, 11]], [[2, 44, 14]], [[1, 40, 12], [2, 41, 13]], [[4, 40, 14]], [[2, 38, 12], [4, 39, 13]],
|
|
11804
|
+
];
|
|
11805
|
+
|
|
11806
|
+
const COUNT_BITS = Object.freeze({
|
|
11807
|
+
numeric: [4, 5, 6, 7, 7, 5, 6, 7, 7, 8, 4, 6, 7, 7, 8, 8, 5, 6, 7, 7, 8, 8, 7, 7, 8, 8, 9, 7, 8, 8, 8, 9],
|
|
11808
|
+
alphanumeric: [3, 5, 5, 6, 6, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 5, 6, 6, 7, 7, 8, 6, 7, 7, 7, 8, 6, 7, 7, 8, 8],
|
|
11809
|
+
byte: [3, 4, 5, 5, 6, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 4, 6, 6, 7, 7, 7, 6, 6, 7, 7, 7, 6, 6, 7, 7, 8],
|
|
11810
|
+
kanji: [2, 3, 4, 5, 5, 3, 4, 5, 5, 6, 2, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 5, 5, 6, 6, 7, 5, 6, 6, 6, 7],
|
|
11811
|
+
});
|
|
11812
|
+
|
|
11813
|
+
const ALIGNMENT_BY_WIDTH = Object.freeze({ 27: [], 43: [21], 59: [19, 39], 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111] });
|
|
11814
|
+
|
|
11815
|
+
/** @param {number} version */
|
|
11816
|
+
function versionInfo(version) {
|
|
11817
|
+
if (!Number.isInteger(version) || version < 1 || version > 32) throw new RangeError(`rMQR: version must be 1-32, got ${version}`);
|
|
11818
|
+
const [width, height] = RMQR_SIZES[version - 1];
|
|
11819
|
+
const blockTable = (ecc) => ecc === 'M' ? M_BLOCKS[version - 1] : H_BLOCKS[version - 1];
|
|
11820
|
+
const blockLayout = (ecc) => {
|
|
11821
|
+
if (ecc !== 'M' && ecc !== 'H') throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
11822
|
+
const blocks = [];
|
|
11823
|
+
for (const [count, total, data] of blockTable(ecc)) for (let i = 0; i < count; i++) blocks.push({ total, data, ecc: total - data });
|
|
11824
|
+
return { blocks, totalCodewords: TOTAL_CODEWORDS[version - 1], totalDataCodewords: blocks.reduce((n, b) => n + b.data, 0), eccCodewords: blocks.reduce((n, b) => n + b.ecc, 0) };
|
|
11825
|
+
};
|
|
11826
|
+
return Object.freeze({ version, width, height, name: `R${height}x${width}`, indicator: version - 1, remainderBits: REMAINDER_BITS[version - 1], totalCodewords: TOTAL_CODEWORDS[version - 1], countBits(mode) { return COUNT_BITS[mode]?.[version - 1] ?? 0; }, blockLayout });
|
|
11827
|
+
}
|
|
11828
|
+
|
|
11829
|
+
/** @param {number} width @param {number} height */
|
|
11830
|
+
function versionForSize(width, height) {
|
|
11831
|
+
const i = RMQR_SIZES.findIndex(([w, h]) => w === width && h === height);
|
|
11832
|
+
return i < 0 ? null : versionInfo(i + 1);
|
|
11833
|
+
}
|
|
11834
|
+
|
|
11835
|
+
/** @param {number} version */
|
|
11836
|
+
function alignmentCoordinates(version) { return ALIGNMENT_BY_WIDTH[versionInfo(version).width] || []; }
|
|
11837
|
+
|
|
11838
|
+
function bchRemainder(value) {
|
|
11839
|
+
const generator = 0x1f25;
|
|
11840
|
+
let v = value << 12;
|
|
11841
|
+
while (v !== 0 && v.toString(2).length >= 13) v ^= generator << (v.toString(2).length - 13);
|
|
11842
|
+
return v;
|
|
11843
|
+
}
|
|
11844
|
+
|
|
11845
|
+
/** Unmasked 18-bit format sequence: 5-bit version indicator plus ECC bit. */
|
|
11846
|
+
function formatBits(version, ecc) {
|
|
11847
|
+
const v = versionInfo(version);
|
|
11848
|
+
if (ecc !== 'M' && ecc !== 'H') throw new RangeError(`rMQR: ECC must be M or H, got ${ecc}`);
|
|
11849
|
+
const data = v.indicator | (ecc === 'H' ? 1 << 5 : 0);
|
|
11850
|
+
return (data << 12) | bchRemainder(data);
|
|
11851
|
+
}
|
|
11852
|
+
const FORMAT_MASK_FINDER = 0b011111101010110010;
|
|
11853
|
+
const FORMAT_MASK_SUB = 0b100000101001111011;
|
|
11854
|
+
|
|
11855
|
+
/** rMQR has one fixed mask: floor(y/2)+floor(x/3) even. */
|
|
11856
|
+
function maskBit(x, y) { return (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0; }
|
|
11857
|
+
|
|
11858
|
+
/** Function modules; set bits are non-data cells. */
|
|
11859
|
+
function functionModules(version) {
|
|
11860
|
+
const v = versionInfo(version); const m = new BitMatrix(v.width, v.height); const { width: w, height: h } = v;
|
|
11861
|
+
m.setRegion(0, 0, w, 1); m.setRegion(0, h - 1, w, 1); m.setRegion(0, 1, 1, h - 2); m.setRegion(w - 1, 1, 1, h - 2);
|
|
11862
|
+
for (const cx of alignmentCoordinates(version)) { m.setRegion(cx - 1, 1, 3, 2); m.setRegion(cx - 1, h - 3, 3, 2); m.setRegion(cx, 3, 1, h - 6); }
|
|
11863
|
+
m.setRegion(1, 1, 7, h === 7 ? 5 : 7); m.setRegion(8, 1, 3, 5); m.setRegion(11, 1, 1, 3);
|
|
11864
|
+
m.setRegion(w - 5, h - 5, 4, 4); m.setRegion(w - 8, h - 6, 3, 5); m.setRegion(w - 5, h - 6, 3, 1);
|
|
11865
|
+
m.set(w - 2, 1); if (h > 9) m.set(1, h - 2);
|
|
11866
|
+
return m;
|
|
11867
|
+
}
|
|
11868
|
+
|
|
11869
|
+
/** Data coordinates in the standard right-to-left two-column traversal. */
|
|
11870
|
+
function dataModuleOrder(version) {
|
|
11871
|
+
const v = versionInfo(version); const fn = functionModules(version); const out = [];
|
|
11872
|
+
let cx = v.width - 2, cy = v.height - 6, dy = -1;
|
|
11873
|
+
// The data path starts beside the lower-right format area, then snakes
|
|
11874
|
+
// through each pair of columns between the one-module outer border.
|
|
11875
|
+
while (cx > 0) {
|
|
11876
|
+
for (const xx of [cx, cx - 1]) if (!fn.get(xx, cy)) out.push([xx, cy]);
|
|
11877
|
+
if (dy < 0 && cy === 1) { cx -= 2; dy = 1; }
|
|
11878
|
+
else if (dy > 0 && cy === v.height - 2) { cx -= 2; dy = -1; }
|
|
11879
|
+
else cy += dy;
|
|
11880
|
+
}
|
|
11881
|
+
return out;
|
|
11882
|
+
}
|
|
11883
|
+
function dataBitCapacity(version, ecc) { const b = versionInfo(version).blockLayout(ecc); return b.totalDataCodewords * 8; }
|
|
11884
|
+
function validateTables() {
|
|
11885
|
+
const problems = [];
|
|
11886
|
+
if (RMQR_SIZES.length !== 32) problems.push(`expected 32 sizes, got ${RMQR_SIZES.length}`);
|
|
11887
|
+
for (let i = 1; i <= 32; i++) {
|
|
11888
|
+
const v = versionInfo(i); const order = dataModuleOrder(i); const expected = v.totalCodewords * 8 + v.remainderBits;
|
|
11889
|
+
if (order.length !== expected) problems.push(`${v.name}: data modules ${order.length}, expected ${expected}`);
|
|
11890
|
+
for (const ecc of ['M', 'H']) { const b = v.blockLayout(ecc); if (b.totalCodewords !== v.totalCodewords) problems.push(`${v.name}-${ecc}: block total mismatch`); }
|
|
11891
|
+
}
|
|
11892
|
+
return problems;
|
|
11893
|
+
}
|
|
11894
|
+
|
|
11895
|
+
__exports.RMQR_SIZES = RMQR_SIZES;
|
|
11896
|
+
__exports.versionInfo = versionInfo;
|
|
11897
|
+
__exports.versionForSize = versionForSize;
|
|
11898
|
+
__exports.alignmentCoordinates = alignmentCoordinates;
|
|
11899
|
+
__exports.formatBits = formatBits;
|
|
11900
|
+
__exports.FORMAT_MASK_FINDER = FORMAT_MASK_FINDER;
|
|
11901
|
+
__exports.FORMAT_MASK_SUB = FORMAT_MASK_SUB;
|
|
11902
|
+
__exports.maskBit = maskBit;
|
|
11903
|
+
__exports.functionModules = functionModules;
|
|
11904
|
+
__exports.dataModuleOrder = dataModuleOrder;
|
|
11905
|
+
__exports.dataBitCapacity = dataBitCapacity;
|
|
11906
|
+
__exports.validateTables = validateTables;
|
|
11907
|
+
};
|
|
11908
|
+
|
|
11909
|
+
__modules["rmqr/encoder.js"] = function (__require, __exports) {
|
|
11910
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
11911
|
+
const { BitWriter } = __require("core/bit-buffer.js");
|
|
11912
|
+
const { EncodeError } = __require("core/errors.js");
|
|
11913
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
11914
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
11915
|
+
const { FORMAT_MASK_FINDER, FORMAT_MASK_SUB, dataBitCapacity, dataModuleOrder, formatBits, functionModules, maskBit, versionForSize, versionInfo } = __require("rmqr/tables.js");
|
|
11916
|
+
const ALPHANUMERIC_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
|
|
11917
|
+
const MODE = Object.freeze({ TERMINATOR: 0, NUMERIC: 1, ALPHANUMERIC: 2, BYTE: 3, KANJI: 4, FNC1: 5, FNC1_SECOND: 6, ECI: 7 });
|
|
11918
|
+
|
|
11919
|
+
function utf8Bytes(text) { return new TextEncoder().encode(text); }
|
|
11920
|
+
function latin1Bytes(text) { const out = new Uint8Array(text.length); for (let i = 0; i < text.length; i++) { const c = text.charCodeAt(i); if (c > 255) throw new EncodeError('rMQR: text is not ISO-8859-1'); out[i] = c; } return out; }
|
|
11921
|
+
function isAlpha(text) { for (const ch of text) if (ALPHANUMERIC_CHARS.indexOf(ch) < 0) return false; return true; }
|
|
11922
|
+
function isNumeric(text) { return /^[0-9]*$/.test(text); }
|
|
11923
|
+
|
|
11924
|
+
let sjisMap;
|
|
11925
|
+
function getSjisMap() {
|
|
11926
|
+
if (sjisMap !== undefined) return sjisMap;
|
|
11927
|
+
try {
|
|
11928
|
+
const decoder = new TextDecoder('shift_jis', { fatal: true });
|
|
11929
|
+
if (decoder.decode(new Uint8Array([0x82, 0xa0])) !== 'あ') return (sjisMap = null);
|
|
11930
|
+
const map = new Map(); const buf = new Uint8Array(2);
|
|
11931
|
+
for (const [lo, hi] of [[0x8140, 0x9ffc], [0xe040, 0xebbf]]) for (let sjis = lo; sjis <= hi; sjis++) {
|
|
11932
|
+
const trail = sjis & 255; if (trail < 0x40 || trail === 0x7f || trail > 0xfc) continue;
|
|
11933
|
+
buf[0] = sjis >> 8; buf[1] = trail; let text; try { text = decoder.decode(buf); } catch { continue; }
|
|
11934
|
+
if (Array.from(text).length === 1 && !map.has(text)) map.set(text, sjis);
|
|
11935
|
+
}
|
|
11936
|
+
return (sjisMap = map);
|
|
11937
|
+
} catch { return (sjisMap = null); }
|
|
11938
|
+
}
|
|
11939
|
+
function kanjiValue(ch) {
|
|
11940
|
+
const sjis = getSjisMap()?.get(ch); if (sjis === undefined) return -1;
|
|
11941
|
+
let v; if (sjis >= 0x8140 && sjis <= 0x9ffc) v = sjis - 0x8140; else if (sjis >= 0xe040 && sjis <= 0xebbf) v = sjis - 0xc140; else return -1;
|
|
11942
|
+
return ((v >> 8) * 0xc0) + (v & 0xff);
|
|
11943
|
+
}
|
|
11944
|
+
|
|
11945
|
+
function chooseMode(text, requested) {
|
|
11946
|
+
if (requested === 'numeric' || requested === 'alphanumeric' || requested === 'byte' || requested === 'kanji') return requested;
|
|
11947
|
+
if (isNumeric(text)) return 'numeric';
|
|
11948
|
+
if (isAlpha(text)) return 'alphanumeric';
|
|
11949
|
+
return 'byte';
|
|
11950
|
+
}
|
|
11951
|
+
|
|
11952
|
+
function modeBits(mode) { return MODE[mode.toUpperCase()] ?? MODE.BYTE; }
|
|
11953
|
+
function charCount(mode, text, bytes) { return mode === 'byte' ? bytes.length : mode === 'kanji' ? Array.from(text).length : text.length; }
|
|
11954
|
+
function payloadBits(mode, text, bytes) {
|
|
11955
|
+
if (mode === 'numeric') { let n = 0; for (let i = 0; i < text.length; i += 3) n += text.length - i >= 3 ? 10 : text.length - i === 2 ? 7 : 4; return n; }
|
|
11956
|
+
if (mode === 'alphanumeric') return Math.floor(text.length / 2) * 11 + (text.length & 1 ? 6 : 0);
|
|
11957
|
+
if (mode === 'kanji') return Array.from(text).length * 13;
|
|
11958
|
+
return bytes.length * 8;
|
|
11959
|
+
}
|
|
11960
|
+
|
|
11961
|
+
function putEci(writer, assignment) {
|
|
11962
|
+
writer.put(MODE.ECI, 3);
|
|
11963
|
+
if (assignment <= 127) writer.put(assignment, 8);
|
|
11964
|
+
else if (assignment <= 16383) writer.put(0x8000 | assignment, 16);
|
|
11965
|
+
else if (assignment <= 999999) writer.put(0xc00000 | assignment, 24);
|
|
11966
|
+
else throw new EncodeError(`rMQR: invalid ECI assignment ${assignment}`);
|
|
11967
|
+
}
|
|
11968
|
+
|
|
11969
|
+
function makeData(text, v, ecc, options) {
|
|
11970
|
+
const requested = options.mode;
|
|
11971
|
+
const mode = chooseMode(text, requested);
|
|
11972
|
+
const charset = options.charset || (mode === 'byte' && Array.from(text).every((ch) => ch.charCodeAt(0) <= 255) ? 'iso-8859-1' : 'utf-8');
|
|
11973
|
+
const bytes = mode === 'byte' ? (charset === 'iso-8859-1' ? latin1Bytes(text) : utf8Bytes(text)) : new Uint8Array();
|
|
11974
|
+
const countBits = v.countBits(mode);
|
|
11975
|
+
if (!countBits) throw new EncodeError(`rMQR: unsupported mode ${mode}`);
|
|
11976
|
+
const writer = new BitWriter();
|
|
11977
|
+
if (options.eci !== undefined) putEci(writer, options.eci);
|
|
11978
|
+
else if (mode === 'byte' && charset === 'utf-8') putEci(writer, 26);
|
|
11979
|
+
writer.put(modeBits(mode), 3); writer.put(charCount(mode, text, bytes), countBits); // header
|
|
11980
|
+
if (mode === 'numeric') for (let i = 0; i < text.length; i += 3) { const s = text.slice(i, i + 3); writer.put(Number(s), s.length === 3 ? 10 : s.length === 2 ? 7 : 4); }
|
|
11981
|
+
else if (mode === 'alphanumeric') for (let i = 0; i < text.length; i += 2) { const a = ALPHANUMERIC_CHARS.indexOf(text[i]); const b = i + 1 < text.length ? ALPHANUMERIC_CHARS.indexOf(text[i + 1]) : -1; if (a < 0 || (b < 0 && i + 1 < text.length)) throw new EncodeError('rMQR: invalid alphanumeric character'); writer.put(b < 0 ? a : a * 45 + b, b < 0 ? 6 : 11); }
|
|
11982
|
+
else if (mode === 'kanji') for (const ch of Array.from(text)) { const value = kanjiValue(ch); if (value < 0) throw new EncodeError(`rMQR: character ${ch} is not encodable in Kanji mode`); writer.put(value, 13); }
|
|
11983
|
+
else writer.putBytes(bytes);
|
|
11984
|
+
const capacity = dataBitCapacity(v.version, ecc);
|
|
11985
|
+
if (writer.length > capacity) throw new EncodeError(`rMQR: payload does not fit ${v.name}-${ecc}`);
|
|
11986
|
+
if (writer.length + 3 <= capacity) writer.put(0, 3);
|
|
11987
|
+
while (writer.length & 7) writer.putBit(false);
|
|
11988
|
+
const dataBytes = v.blockLayout(ecc).totalDataCodewords;
|
|
11989
|
+
let pad = 0xec; while (writer.toBytes().length < dataBytes) { writer.put(pad, 8); pad = pad === 0xec ? 0x11 : 0xec; }
|
|
11990
|
+
return writer.toBytes();
|
|
11991
|
+
}
|
|
11992
|
+
|
|
11993
|
+
function interleave(data, v, ecc) {
|
|
11994
|
+
const layout = v.blockLayout(ecc); const blocks = []; let offset = 0;
|
|
11995
|
+
for (const b of layout.blocks) { const d = Array.from(data.slice(offset, offset + b.data)); offset += b.data; blocks.push({ data: d, ecc: rsEncode(d, b.ecc, GF256_QR, 0) }); }
|
|
11996
|
+
const out = []; const maxData = Math.max(...blocks.map((b) => b.data.length)); const maxEcc = Math.max(...blocks.map((b) => b.ecc.length));
|
|
11997
|
+
for (let i = 0; i < maxData; i++) for (const b of blocks) if (i < b.data.length) out.push(b.data[i]);
|
|
11998
|
+
for (let i = 0; i < maxEcc; i++) for (const b of blocks) if (i < b.ecc.length) out.push(b.ecc[i]);
|
|
11999
|
+
return Uint8Array.from(out);
|
|
12000
|
+
}
|
|
12001
|
+
|
|
12002
|
+
function drawFunctions(m, version) {
|
|
12003
|
+
const v = versionInfo(version); const fn = functionModules(version); const { width: w, height: h } = v;
|
|
12004
|
+
const setIfData = (x, y, on) => { if (!fn.get(x, y)) m.setValue(x, y, on); };
|
|
12005
|
+
for (let x = 0; x < w; x++) { m.setValue(x, 0, (x & 1) === 0); m.setValue(x, h - 1, (x & 1) === 0); }
|
|
12006
|
+
for (const x of [0, w - 1, ...new Set([...(awaitableAlignment(version))])]) for (let y = 0; y < h; y++) setIfData(x, y, (y & 1) === 0);
|
|
12007
|
+
for (const cx of awaitableAlignment(version)) for (const y of [0, 1, h - 2, h - 1]) m.setValue(cx + (y === 0 || y === h - 1 ? 0 : 0), y, false);
|
|
12008
|
+
// Alignment patterns (top and bottom).
|
|
12009
|
+
for (const cx of awaitableAlignment(version)) for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) { const on = i === 0 || i === 2 || j === 0 || j === 2; m.setValue(cx + j - 1, i, on); m.setValue(cx + j - 1, h - 1 - i, on); }
|
|
12010
|
+
// Finder and separator.
|
|
12011
|
+
for (let i = 0; i < 7; i++) for (let j = 0; j < 7; j++) m.setValue(j, i, i === 0 || i === 6 || j === 0 || j === 6 || (i >= 2 && i <= 4 && j >= 2 && j <= 4));
|
|
12012
|
+
for (let n = 0; n < 8; n++) { if (n < h) m.setValue(7, n, false); if (h >= 9) m.setValue(n, 7, false); }
|
|
12013
|
+
for (let i = 0; i < 5; i++) for (let j = 0; j < 5; j++) m.setValue(w - j - 1, h - i - 1, i === 0 || i === 4 || j === 0 || j === 4 || (i === 2 && j === 2));
|
|
12014
|
+
m.set(w - 1, 0); m.set(w - 2, 0); m.set(w - 1, 1); if (h >= 11) { m.set(0, h - 1); m.set(1, h - 1); m.set(2, h - 1); m.set(0, h - 2); }
|
|
12015
|
+
}
|
|
12016
|
+
|
|
12017
|
+
// Kept as a local helper so drawFunctions stays independent of mutable tables.
|
|
12018
|
+
function awaitableAlignment(version) { return ({ 27: [], 43: [21], 59: [19, 39], 77: [25, 51], 99: [23, 49, 75], 139: [27, 55, 83, 111] })[versionInfo(version).width] || []; }
|
|
12019
|
+
|
|
12020
|
+
function drawFormat(m, version, ecc) {
|
|
12021
|
+
const v = versionInfo(version); let bits = formatBits(version, ecc) ^ FORMAT_MASK_FINDER;
|
|
12022
|
+
for (let n = 0; n < 18; n++) m.setValue(8 + Math.floor(n / 5), 1 + (n % 5), ((bits >>> n) & 1) !== 0);
|
|
12023
|
+
bits = formatBits(version, ecc) ^ FORMAT_MASK_SUB;
|
|
12024
|
+
for (let n = 0; n < 15; n++) m.setValue(v.width - 8 + Math.floor(n / 5), v.height - 6 + (n % 5), ((bits >>> n) & 1) !== 0);
|
|
12025
|
+
for (let n = 15; n < 18; n++) m.setValue(v.width - 5 + (n - 15), v.height - 6, ((bits >>> n) & 1) !== 0);
|
|
12026
|
+
}
|
|
12027
|
+
|
|
12028
|
+
function buildMatrix(version, ecc, codewords) {
|
|
12029
|
+
const v = versionInfo(version); const m = new BitMatrix(v.width, v.height); drawFunctions(m, version); drawFormat(m, version, ecc);
|
|
12030
|
+
const fn = functionModules(version); const order = dataModuleOrder(version); let bit = 0; for (const [x, y] of order) { let on = bit < codewords.length * 8 && ((codewords[bit >>> 3] >>> (7 - (bit & 7))) & 1) !== 0; if (maskBit(x, y)) on = !on; m.setValue(x, y, on); bit++; }
|
|
12031
|
+
return m;
|
|
12032
|
+
}
|
|
12033
|
+
|
|
12034
|
+
/** Encode text into a rMQR module matrix. */
|
|
12035
|
+
function encodeRMQR(text, options = {}) {
|
|
12036
|
+
if (typeof text !== 'string' || text.length === 0) throw new EncodeError('rMQR: text must be a non-empty string');
|
|
12037
|
+
const ecc = options.ecc || 'M'; if (ecc !== 'M' && ecc !== 'H') throw new EncodeError('rMQR: ECC must be M or H');
|
|
12038
|
+
let forced = options.version; if (typeof forced === 'string') { const match = /^R(\d+)x(\d+)$/i.exec(forced); if (!match) throw new EncodeError(`rMQR: invalid version ${forced}`); const info = versionForSize(Number(match[2]), Number(match[1])); if (!info) throw new EncodeError(`rMQR: unsupported version ${forced}`); forced = info.version; }
|
|
12039
|
+
if (forced !== undefined && (!Number.isInteger(forced) || forced < 1 || forced > 32)) throw new EncodeError('rMQR: version must be 1-32');
|
|
12040
|
+
const versions = forced ? [forced] : Array.from({ length: 32 }, (_, i) => i + 1);
|
|
12041
|
+
let selected = null;
|
|
12042
|
+
for (const n of versions) { const v = versionInfo(n); try { const data = makeData(text, v, ecc, options); selected = { v, data }; break; } catch (error) { if (forced) throw error; } }
|
|
12043
|
+
if (!selected) throw new EncodeError(`rMQR: text is too long for ECC ${ecc}`);
|
|
12044
|
+
const codewords = interleave(selected.data, selected.v, ecc); const matrix = buildMatrix(selected.v.version, ecc, codewords); matrix.rmqr = { version: selected.v.version, name: selected.v.name, ecc }; return matrix;
|
|
12045
|
+
}
|
|
12046
|
+
__exports.MODE = MODE;
|
|
12047
|
+
|
|
12048
|
+
__exports.ALPHANUMERIC_CHARS = ALPHANUMERIC_CHARS;
|
|
12049
|
+
__exports.encodeRMQR = encodeRMQR;
|
|
12050
|
+
};
|
|
12051
|
+
|
|
12052
|
+
__modules["rmqr/decoder.js"] = function (__require, __exports) {
|
|
12053
|
+
const { BitReader } = __require("core/bit-buffer.js");
|
|
12054
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
12055
|
+
const { GF256_QR } = __require("core/galois-field.js");
|
|
12056
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
12057
|
+
const { FORMAT_MASK_FINDER, FORMAT_MASK_SUB, dataModuleOrder, functionModules, maskBit, versionForSize, versionInfo, formatBits } = __require("rmqr/tables.js");
|
|
12058
|
+
const { ALPHANUMERIC_CHARS, MODE } = __require("rmqr/encoder.js");
|
|
12059
|
+
|
|
12060
|
+
function hamming(a, b) { let v = a ^ b, n = 0; while (v) { v &= v - 1; n++; } return n; }
|
|
12061
|
+
function readFormat(matrix, v) {
|
|
12062
|
+
let a = 0; for (let n = 0; n < 18; n++) if (matrix.get(8 + Math.floor(n / 5), 1 + (n % 5))) a |= 1 << n;
|
|
12063
|
+
let b = 0; for (let n = 0; n < 15; n++) if (matrix.get(v.width - 8 + Math.floor(n / 5), v.height - 6 + (n % 5))) b |= 1 << n;
|
|
12064
|
+
for (let n = 15; n < 18; n++) if (matrix.get(v.width - 5 + (n - 15), v.height - 6)) b |= 1 << n;
|
|
12065
|
+
const candidates = [];
|
|
12066
|
+
for (let version = 1; version <= 32; version++) for (const ecc of ['M', 'H']) {
|
|
12067
|
+
candidates.push({ version, ecc, finder: formatBits(version, ecc) ^ FORMAT_MASK_FINDER, sub: formatBits(version, ecc) ^ FORMAT_MASK_SUB });
|
|
12068
|
+
}
|
|
12069
|
+
let best = null;
|
|
12070
|
+
for (const c of candidates) for (const [value, expected] of [[a, c.finder], [b, c.sub]]) { const distance = hamming(value, expected); if (!best || distance < best.distance) best = { ...c, distance }; }
|
|
12071
|
+
if (!best || best.distance > 3) throw new FormatError('rMQR: format information is unreadable');
|
|
12072
|
+
return best;
|
|
12073
|
+
}
|
|
12074
|
+
|
|
12075
|
+
function readCodewords(matrix, v, mask, total) {
|
|
12076
|
+
const order = dataModuleOrder(v.version); const out = new Uint8Array(total); let bit = 0;
|
|
12077
|
+
for (const [x, y] of order) { if (bit >= total * 8) break; let on = matrix.get(x, y); if (maskBit(x, y)) on = !on; if (on) out[bit >>> 3] |= 0x80 >>> (bit & 7); bit++; }
|
|
12078
|
+
return out;
|
|
12079
|
+
}
|
|
12080
|
+
|
|
12081
|
+
function deinterleave(codewords, v, ecc) {
|
|
12082
|
+
const blocks = v.blockLayout(ecc).blocks; const arrays = blocks.map((b) => new Array(b.total).fill(0)); let offset = 0;
|
|
12083
|
+
const maxData = Math.max(...blocks.map((b) => b.data)); for (let i = 0; i < maxData; i++) for (let b = 0; b < blocks.length; b++) if (i < blocks[b].data) arrays[b][i] = codewords[offset++];
|
|
12084
|
+
const maxEcc = Math.max(...blocks.map((b) => b.ecc)); for (let i = 0; i < maxEcc; i++) for (let b = 0; b < blocks.length; b++) if (i < blocks[b].ecc) arrays[b][blocks[b].data + i] = codewords[offset++];
|
|
12085
|
+
const data = []; let corrections = 0;
|
|
12086
|
+
for (let b = 0; b < arrays.length; b++) { corrections += rsDecode(arrays[b], blocks[b].ecc, GF256_QR, 0); data.push(...arrays[b].slice(0, blocks[b].data)); }
|
|
12087
|
+
return { data: Uint8Array.from(data), corrections };
|
|
12088
|
+
}
|
|
12089
|
+
|
|
12090
|
+
function decodeBytes(bytes, eci) {
|
|
12091
|
+
try { return new TextDecoder(eci === 26 ? 'utf-8' : 'iso-8859-1', { fatal: false }).decode(bytes); } catch { return String.fromCharCode(...bytes); }
|
|
12092
|
+
}
|
|
12093
|
+
function parseSegments(data, v) {
|
|
12094
|
+
const reader = new BitReader(data); let text = ''; const raw = []; let eci = 3; let mode;
|
|
12095
|
+
while (reader.available() >= 3) {
|
|
12096
|
+
const peek = (() => { const save = { byteOffset: reader.byteOffset, bitOffset: reader.bitOffset }; const n = reader.read(3); reader.byteOffset = save.byteOffset; reader.bitOffset = save.bitOffset; return n; })();
|
|
12097
|
+
if (peek === 0) break;
|
|
12098
|
+
mode = reader.read(3);
|
|
12099
|
+
if (mode === MODE.ECI) { const first = reader.read(8); let value; if (!(first & 0x80)) value = first; else if ((first & 0xc0) === 0x80) value = ((first & 0x3f) << 8) | reader.read(8); else if ((first & 0xe0) === 0xc0) value = ((first & 0x1f) << 16) | reader.read(16); else throw new FormatError('rMQR: invalid ECI'); eci = value; continue; }
|
|
12100
|
+
const kind = mode === MODE.NUMERIC ? 'numeric' : mode === MODE.ALPHANUMERIC ? 'alphanumeric' : mode === MODE.BYTE ? 'byte' : mode === MODE.KANJI ? 'kanji' : null;
|
|
12101
|
+
if (!kind) throw new FormatError(`rMQR: unsupported mode ${mode}`);
|
|
12102
|
+
const count = reader.read(v.countBits(kind));
|
|
12103
|
+
if (kind === 'numeric') { let remaining = count; while (remaining >= 3) { const n = reader.read(10).toString().padStart(3, '0'); text += n; remaining -= 3; } if (remaining === 2) text += reader.read(7).toString().padStart(2, '0'); else if (remaining === 1) text += reader.read(4).toString(); }
|
|
12104
|
+
else if (kind === 'alphanumeric') { let remaining = count; while (remaining >= 2) { const n = reader.read(11); text += ALPHANUMERIC_CHARS[Math.floor(n / 45)] + ALPHANUMERIC_CHARS[n % 45]; remaining -= 2; } if (remaining) text += ALPHANUMERIC_CHARS[reader.read(6)]; }
|
|
12105
|
+
else if (kind === 'byte') { const b = new Uint8Array(count); for (let i = 0; i < count; i++) { b[i] = reader.read(8); raw.push(b[i]); } text += decodeBytes(b, eci); }
|
|
12106
|
+
else { const bytes = new Uint8Array(count * 2); for (let i = 0; i < count; i++) { const n = reader.read(13); const v2 = n; const high = Math.floor(v2 / 0xc0); const low = v2 % 0xc0; const sjis = high < 0x1f ? 0x8140 + (high << 8) + low : 0xc140 + (high << 8) + low; bytes[i * 2] = sjis >> 8; bytes[i * 2 + 1] = sjis & 255; } try { text += new TextDecoder('shift_jis').decode(bytes); } catch { text += String.fromCharCode(...bytes); } }
|
|
12107
|
+
}
|
|
12108
|
+
return { text, bytes: Uint8Array.from(raw) };
|
|
12109
|
+
}
|
|
12110
|
+
|
|
12111
|
+
/** Decode an exact rMQR module matrix (without quiet zone). */
|
|
12112
|
+
function decodeRMQR(matrix) {
|
|
12113
|
+
if (!matrix || !matrix.width || !matrix.height) throw new FormatError('rMQR: no matrix supplied');
|
|
12114
|
+
const v = versionForSize(matrix.width, matrix.height); if (!v) throw new FormatError(`rMQR: unsupported symbol size ${matrix.width}x${matrix.height}`);
|
|
12115
|
+
const info = readFormat(matrix, v); if (info.version !== v.version) throw new FormatError('rMQR: format/version mismatch');
|
|
12116
|
+
const codewords = readCodewords(matrix, v, 4, v.totalCodewords); const corrected = deinterleave(codewords, v, info.ecc); const parsed = parseSegments(corrected.data, v);
|
|
12117
|
+
return { ...parsed, version: v.version, name: v.name, ecc: info.ecc, mask: 4, corrections: corrected.corrections };
|
|
12118
|
+
}
|
|
12119
|
+
__exports.ChecksumError = ChecksumError;
|
|
12120
|
+
|
|
12121
|
+
__exports.decodeRMQR = decodeRMQR;
|
|
12122
|
+
};
|
|
12123
|
+
|
|
12124
|
+
__modules["rmqr/detector.js"] = function (__require, __exports) {
|
|
12125
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
12126
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
12127
|
+
const { decodeRMQR } = __require("rmqr/decoder.js");
|
|
12128
|
+
const { versionForSize } = __require("rmqr/tables.js");
|
|
12129
|
+
|
|
12130
|
+
function rotate90(source) {
|
|
12131
|
+
const out = new BitMatrix(source.height, source.width);
|
|
12132
|
+
for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) if (source.get(x, y)) out.set(source.height - 1 - y, x);
|
|
12133
|
+
return out;
|
|
12134
|
+
}
|
|
12135
|
+
function rotate180(source) { const out = new BitMatrix(source.width, source.height); for (let y = 0; y < source.height; y++) for (let x = 0; x < source.width; x++) if (source.get(x, y)) out.set(source.width - 1 - x, source.height - 1 - y); return out; }
|
|
12136
|
+
function rotate270(source) { return rotate90(rotate180(source)); }
|
|
12137
|
+
function crop(source, box) {
|
|
12138
|
+
const out = new BitMatrix(box.width, box.height); for (let y = 0; y < box.height; y++) for (let x = 0; x < box.width; x++) if (source.get(box.x + x, box.y + y)) out.set(x, y); return out;
|
|
12139
|
+
}
|
|
12140
|
+
function sample(matrix, width, height, scale, offsetX, offsetY) {
|
|
12141
|
+
const out = new BitMatrix(width, height); const half = Math.max(0, Math.floor(scale / 2));
|
|
12142
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
12143
|
+
let dark = 0, count = 0; const x0 = offsetX + x * scale, y0 = offsetY + y * scale;
|
|
12144
|
+
for (let yy = 0; yy < scale; yy++) for (let xx = 0; xx < scale; xx++) { if (matrix.get(x0 + xx, y0 + yy)) dark++; count++; }
|
|
12145
|
+
if (dark * 2 >= count) out.set(x, y);
|
|
12146
|
+
}
|
|
12147
|
+
return out;
|
|
12148
|
+
}
|
|
12149
|
+
function bounds(matrix) { return matrix.getBounds(); }
|
|
12150
|
+
|
|
12151
|
+
/** Detect an axis-aligned, clean rMQR raster and return the exact module matrix. */
|
|
12152
|
+
function detectRMQR(image, options = {}) {
|
|
12153
|
+
if (!image || !image.width || !image.height) throw new NotFoundError('rMQR: no raster supplied');
|
|
12154
|
+
if (options.perspective) throw new NotFoundError('rMQR: perspective detection is not available for clean-raster mode');
|
|
12155
|
+
const orientations = [image, rotate90(image), rotate180(image), rotate270(image)];
|
|
12156
|
+
for (let rotation = 0; rotation < orientations.length; rotation++) {
|
|
12157
|
+
const source = orientations[rotation]; const box = bounds(source); if (!box) continue;
|
|
12158
|
+
for (let version = 1; version <= 32; version++) {
|
|
12159
|
+
const v = versionForSize(box.width, box.height); // exact modules, scale 1
|
|
12160
|
+
if (v && v.version === version) {
|
|
12161
|
+
try { const matrix = crop(source, box); const result = decodeRMQR(matrix); return { matrix, result, rotation, corners: { x: box.x, y: box.y, width: box.width, height: box.height } }; } catch { /* try next orientation/candidate */ }
|
|
12162
|
+
}
|
|
12163
|
+
// Integer nearest-neighbour scale. The dark bounding box must still be an
|
|
12164
|
+
// exact multiple of the standard geometry; this rejects arbitrary text.
|
|
12165
|
+
const candidate = versionForSize(box.width, box.height);
|
|
12166
|
+
if (candidate) continue;
|
|
12167
|
+
const canonical = (awaitableVersion(version));
|
|
12168
|
+
const wScale = box.width / canonical.width;
|
|
12169
|
+
const hScale = box.height / canonical.height;
|
|
12170
|
+
if (!Number.isInteger(wScale) || wScale < 1 || wScale !== hScale) continue;
|
|
12171
|
+
const info = canonical;
|
|
12172
|
+
try { const matrix = sample(source, info.width, info.height, wScale, box.x, box.y); const result = decodeRMQR(matrix); return { matrix, result, rotation, scale: wScale, corners: { x: box.x, y: box.y, width: box.width, height: box.height } }; } catch { /* continue */ }
|
|
12173
|
+
}
|
|
12174
|
+
}
|
|
12175
|
+
throw new NotFoundError('rMQR: no clean axis-aligned symbol found');
|
|
12176
|
+
}
|
|
12177
|
+
|
|
12178
|
+
function awaitableVersion(version) {
|
|
12179
|
+
const sizes = [[43, 7], [59, 7], [77, 7], [99, 7], [139, 7], [43, 9], [59, 9], [77, 9], [99, 9], [139, 9], [27, 11], [43, 11], [59, 11], [77, 11], [99, 11], [139, 11], [27, 13], [43, 13], [59, 13], [77, 13], [99, 13], [139, 13], [43, 15], [59, 15], [77, 15], [99, 15], [139, 15], [43, 17], [59, 17], [77, 17], [99, 17], [139, 17]];
|
|
12180
|
+
return { version, width: sizes[version - 1][0], height: sizes[version - 1][1] };
|
|
12181
|
+
}
|
|
12182
|
+
|
|
12183
|
+
/** Detect and decode a raster in one call. */
|
|
12184
|
+
function detectAndDecodeRMQR(image, options = {}) { return detectRMQR(image, options).result; }
|
|
12185
|
+
|
|
12186
|
+
__exports.detectRMQR = detectRMQR;
|
|
12187
|
+
__exports.detectAndDecodeRMQR = detectAndDecodeRMQR;
|
|
12188
|
+
};
|
|
12189
|
+
|
|
12190
|
+
__modules["rmqr/index.js"] = function (__require, __exports) {
|
|
12191
|
+
const __reexport0 = __require("rmqr/encoder.js"); __exports.encodeRMQR = __reexport0.encodeRMQR; __exports.ALPHANUMERIC_CHARS = __reexport0.ALPHANUMERIC_CHARS;
|
|
12192
|
+
const __reexport1 = __require("rmqr/decoder.js"); __exports.decodeRMQR = __reexport1.decodeRMQR;
|
|
12193
|
+
const __reexport2 = __require("rmqr/detector.js"); __exports.detectRMQR = __reexport2.detectRMQR; __exports.detectAndDecodeRMQR = __reexport2.detectAndDecodeRMQR;
|
|
12194
|
+
const __reexport3 = __require("rmqr/tables.js"); __exports.RMQR_SIZES = __reexport3.RMQR_SIZES; __exports.versionInfo = __reexport3.versionInfo; __exports.versionForSize = __reexport3.versionForSize; __exports.alignmentCoordinates = __reexport3.alignmentCoordinates; __exports.dataModuleOrder = __reexport3.dataModuleOrder; __exports.functionModules = __reexport3.functionModules; __exports.dataBitCapacity = __reexport3.dataBitCapacity; __exports.formatBits = __reexport3.formatBits; __exports.maskBit = __reexport3.maskBit; __exports.validateTables = __reexport3.validateTables;
|
|
12195
|
+
|
|
12196
|
+
|
|
12197
|
+
};
|
|
12198
|
+
|
|
12199
|
+
__modules["frameqr/tables.js"] = function (__require, __exports) {
|
|
12200
|
+
/**
|
|
12201
|
+
* Structural contract for the Sythos Canvas QR profile.
|
|
12202
|
+
*
|
|
12203
|
+
* DENSO WAVE's public material describes FrameQR(R) as a proprietary symbol
|
|
12204
|
+
* with a freely shaped canvas, dedicated generation/reading software and no
|
|
12205
|
+
* compatibility with ordinary QR readers. It does not publish the bitstream,
|
|
12206
|
+
* placement or error-correction rules required for an interoperable encoder.
|
|
12207
|
+
* Consequently this module does not claim to implement DENSO FrameQR.
|
|
12208
|
+
*
|
|
12209
|
+
* The implementable profile below starts with an ISO/IEC 18004 QR Model 2
|
|
12210
|
+
* symbol at level H and clears a bounded group of data modules. Its worst-case
|
|
12211
|
+
* damage is calculated per Reed-Solomon block and must remain within the
|
|
12212
|
+
* standard QR correction radius. Function modules are never canvas modules.
|
|
12213
|
+
* This provides a deterministic, independently testable canvas QR profile, but
|
|
12214
|
+
* it is explicitly non-certified and not a substitute for proprietary FrameQR
|
|
12215
|
+
* generation or validation software.
|
|
12216
|
+
*
|
|
12217
|
+
* @module frameqr/tables
|
|
12218
|
+
*/
|
|
12219
|
+
const { blockLayout, dataModuleOrder, reservedModules, versionSize } = __require("qr/tables.js");
|
|
12220
|
+
|
|
12221
|
+
/** Public identity and compatibility boundary of the implementable profile. */
|
|
12222
|
+
const FRAMEQR_PROFILE = Object.freeze({
|
|
12223
|
+
id: 'sythos-canvas-qr/1',
|
|
12224
|
+
name: 'Sythos Canvas QR profile',
|
|
12225
|
+
certified: false,
|
|
12226
|
+
densoFrameQrCompatible: false,
|
|
12227
|
+
baseSymbology: 'QR Code Model 2',
|
|
12228
|
+
requiredEcc: 'H',
|
|
12229
|
+
standard: 'ISO/IEC 18004 QR Code baseline',
|
|
12230
|
+
});
|
|
12231
|
+
|
|
12232
|
+
/** Canvas shapes whose module membership is fully deterministic. */
|
|
12233
|
+
const FRAMEQR_CANVAS_SHAPES = Object.freeze(['square', 'circle', 'diamond']);
|
|
12234
|
+
|
|
12235
|
+
function oddAtMost(value, maximum) {
|
|
12236
|
+
let n = Math.max(1, Math.min(maximum, Math.floor(value)));
|
|
12237
|
+
if ((n & 1) === 0) n--;
|
|
12238
|
+
return Math.max(1, n);
|
|
12239
|
+
}
|
|
12240
|
+
|
|
12241
|
+
function assertSymbolSize(symbolSize) {
|
|
12242
|
+
if (!Number.isInteger(symbolSize) || symbolSize < 21 || symbolSize > 177 || (symbolSize - 17) % 4 !== 0) {
|
|
12243
|
+
throw new RangeError(`Frame QR profile: ${symbolSize} is not a QR Model 2 symbol size`);
|
|
12244
|
+
}
|
|
12245
|
+
}
|
|
12246
|
+
|
|
12247
|
+
/**
|
|
12248
|
+
* Canonicalise a canvas request.
|
|
12249
|
+
*
|
|
12250
|
+
* Coordinates and dimensions are module units. Odd dimensions make the centre
|
|
12251
|
+
* unambiguous. Only quarter turns are accepted because arbitrary-angle raster
|
|
12252
|
+
* membership would depend on renderer-specific sampling.
|
|
12253
|
+
*
|
|
12254
|
+
* @param {number} symbolSize QR module width/height (21..177).
|
|
12255
|
+
* @param {object} [canvas]
|
|
12256
|
+
* @returns {{shape:string,centerX:number,centerY:number,width:number,height:number,angle:number}}
|
|
12257
|
+
*/
|
|
12258
|
+
function normalizeCanvasSpec(symbolSize, canvas = {}) {
|
|
12259
|
+
assertSymbolSize(symbolSize);
|
|
12260
|
+
if (canvas === null || typeof canvas !== 'object' || Array.isArray(canvas)) {
|
|
12261
|
+
throw new TypeError('Frame QR profile: canvas must be an object');
|
|
12262
|
+
}
|
|
12263
|
+
|
|
12264
|
+
const shape = String(canvas.shape ?? 'square').toLowerCase();
|
|
12265
|
+
if (!FRAMEQR_CANVAS_SHAPES.includes(shape)) {
|
|
12266
|
+
throw new RangeError(`Frame QR profile: unsupported canvas shape "${shape}"`);
|
|
12267
|
+
}
|
|
12268
|
+
|
|
12269
|
+
const defaultSize = oddAtMost(Math.max(3, Math.round(symbolSize * 0.17)), symbolSize - 16);
|
|
12270
|
+
const requestedSize = canvas.size;
|
|
12271
|
+
const requestedWidth = canvas.width ?? requestedSize ?? defaultSize;
|
|
12272
|
+
const requestedHeight = canvas.height ?? requestedSize ?? defaultSize;
|
|
12273
|
+
const maximumDimension = symbolSize - 16;
|
|
12274
|
+
for (const [name, value] of [['width', requestedWidth], ['height', requestedHeight]]) {
|
|
12275
|
+
if (!Number.isFinite(Number(value)) || Number(value) < 1 || Number(value) > maximumDimension) {
|
|
12276
|
+
throw new RangeError(
|
|
12277
|
+
`Frame QR profile: canvas ${name} must be between 1 and ${maximumDimension} modules`
|
|
12278
|
+
);
|
|
12279
|
+
}
|
|
12280
|
+
}
|
|
12281
|
+
const width = oddAtMost(Number(requestedWidth), maximumDimension);
|
|
12282
|
+
const height = oddAtMost(Number(requestedHeight), maximumDimension);
|
|
12283
|
+
const centerX = Math.round(canvas.centerX ?? (symbolSize - 1) / 2);
|
|
12284
|
+
const centerY = Math.round(canvas.centerY ?? (symbolSize - 1) / 2);
|
|
12285
|
+
const angle = ((Number(canvas.angle ?? 0) % 360) + 360) % 360;
|
|
12286
|
+
|
|
12287
|
+
if (![0, 90, 180, 270].includes(angle)) {
|
|
12288
|
+
throw new RangeError('Frame QR profile: angle must be 0, 90, 180 or 270 degrees');
|
|
12289
|
+
}
|
|
12290
|
+
if (centerX < 8 || centerY < 8 || centerX >= symbolSize - 8 || centerY >= symbolSize - 8) {
|
|
12291
|
+
throw new RangeError('Frame QR profile: canvas centre must remain inside the finder-pattern boundary');
|
|
12292
|
+
}
|
|
12293
|
+
|
|
12294
|
+
return { shape, centerX, centerY, width, height, angle };
|
|
12295
|
+
}
|
|
12296
|
+
|
|
12297
|
+
/**
|
|
12298
|
+
* Enumerate canvas modules, including any overlaps with QR function modules.
|
|
12299
|
+
* A conforming encoder rejects such overlaps rather than damaging function
|
|
12300
|
+
* patterns.
|
|
12301
|
+
*
|
|
12302
|
+
* @param {number} symbolSize
|
|
12303
|
+
* @param {object} [canvas]
|
|
12304
|
+
* @returns {Array<[number, number]>}
|
|
12305
|
+
*/
|
|
12306
|
+
function canvasModules(symbolSize, canvas = {}) {
|
|
12307
|
+
const spec = normalizeCanvasSpec(symbolSize, canvas);
|
|
12308
|
+
const rotate = spec.angle === 90 || spec.angle === 270;
|
|
12309
|
+
const width = rotate ? spec.height : spec.width;
|
|
12310
|
+
const height = rotate ? spec.width : spec.height;
|
|
12311
|
+
const halfWidth = (width - 1) / 2;
|
|
12312
|
+
const halfHeight = (height - 1) / 2;
|
|
12313
|
+
const modules = [];
|
|
12314
|
+
|
|
12315
|
+
for (let dy = -halfHeight; dy <= halfHeight; dy++) {
|
|
12316
|
+
for (let dx = -halfWidth; dx <= halfWidth; dx++) {
|
|
12317
|
+
const nx = halfWidth === 0 ? 0 : dx / halfWidth;
|
|
12318
|
+
const ny = halfHeight === 0 ? 0 : dy / halfHeight;
|
|
12319
|
+
let inside;
|
|
12320
|
+
if (spec.shape === 'circle') inside = nx * nx + ny * ny <= 1 + Number.EPSILON;
|
|
12321
|
+
else if (spec.shape === 'diamond') inside = Math.abs(nx) + Math.abs(ny) <= 1 + Number.EPSILON;
|
|
12322
|
+
else inside = true;
|
|
12323
|
+
if (inside) modules.push([spec.centerX + dx, spec.centerY + dy]);
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
12326
|
+
return modules;
|
|
12327
|
+
}
|
|
12328
|
+
|
|
12329
|
+
/** Build the interleaved-codeword to RS-block map used by QR Model 2. */
|
|
12330
|
+
function codewordBlockMap(layout) {
|
|
12331
|
+
const dataCounts = new Array(layout.blockCount);
|
|
12332
|
+
for (let block = 0; block < layout.blockCount; block++) {
|
|
12333
|
+
dataCounts[block] = block < layout.group1Blocks
|
|
12334
|
+
? layout.group1DataCount
|
|
12335
|
+
: layout.group2DataCount;
|
|
12336
|
+
}
|
|
12337
|
+
|
|
12338
|
+
const map = [];
|
|
12339
|
+
const maxData = Math.max(...dataCounts);
|
|
12340
|
+
for (let i = 0; i < maxData; i++) {
|
|
12341
|
+
for (let block = 0; block < layout.blockCount; block++) {
|
|
12342
|
+
if (i < dataCounts[block]) map.push(block);
|
|
12343
|
+
}
|
|
12344
|
+
}
|
|
12345
|
+
for (let i = 0; i < layout.eccPerBlock; i++) {
|
|
12346
|
+
for (let block = 0; block < layout.blockCount; block++) map.push(block);
|
|
12347
|
+
}
|
|
12348
|
+
return map;
|
|
12349
|
+
}
|
|
12350
|
+
|
|
12351
|
+
/**
|
|
12352
|
+
* Calculate worst-case QR codeword damage caused by a canvas.
|
|
12353
|
+
* A codeword is counted if any of its modules is touched. This is conservative:
|
|
12354
|
+
* clearing an already-light module does no damage, but safety cannot depend on
|
|
12355
|
+
* one payload or mask.
|
|
12356
|
+
*
|
|
12357
|
+
* @param {number} version QR version 1..40.
|
|
12358
|
+
* @param {object} [canvas]
|
|
12359
|
+
*/
|
|
12360
|
+
function analyzeCanvasDamage(version, canvas = {}) {
|
|
12361
|
+
if (!Number.isInteger(version) || version < 1 || version > 40) {
|
|
12362
|
+
throw new RangeError(`Frame QR profile: version must be an integer 1-40, got ${version}`);
|
|
12363
|
+
}
|
|
12364
|
+
const symbolSize = versionSize(version);
|
|
12365
|
+
const spec = normalizeCanvasSpec(symbolSize, canvas);
|
|
12366
|
+
const modules = canvasModules(symbolSize, spec);
|
|
12367
|
+
const reserved = reservedModules(version);
|
|
12368
|
+
const reservedOverlaps = modules.filter(([x, y]) => reserved.get(x, y));
|
|
12369
|
+
|
|
12370
|
+
const moduleKeys = new Set(modules.map(([x, y]) => `${x},${y}`));
|
|
12371
|
+
const order = dataModuleOrder(version);
|
|
12372
|
+
const touchedCodewords = new Set();
|
|
12373
|
+
for (let bit = 0; bit < order.length / 2; bit++) {
|
|
12374
|
+
if (moduleKeys.has(`${order[bit * 2]},${order[bit * 2 + 1]}`)) touchedCodewords.add(bit >> 3);
|
|
12375
|
+
}
|
|
12376
|
+
|
|
12377
|
+
const layout = blockLayout(version, 'H');
|
|
12378
|
+
const blockMap = codewordBlockMap(layout);
|
|
12379
|
+
const touchedByBlockSets = Array.from({ length: layout.blockCount }, () => new Set());
|
|
12380
|
+
for (const codeword of touchedCodewords) {
|
|
12381
|
+
const block = blockMap[codeword];
|
|
12382
|
+
if (block !== undefined) touchedByBlockSets[block].add(codeword);
|
|
12383
|
+
}
|
|
12384
|
+
const touchedCodewordsByBlock = touchedByBlockSets.map((set) => set.size);
|
|
12385
|
+
const correctionBudgetPerBlock = Math.floor(layout.eccPerBlock / 2);
|
|
12386
|
+
const safe = reservedOverlaps.length === 0 &&
|
|
12387
|
+
touchedCodewordsByBlock.every((count) => count <= correctionBudgetPerBlock);
|
|
12388
|
+
|
|
12389
|
+
return {
|
|
12390
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12391
|
+
certified: false,
|
|
12392
|
+
version,
|
|
12393
|
+
symbolSize,
|
|
12394
|
+
canvas: spec,
|
|
12395
|
+
canvasModuleCount: modules.length,
|
|
12396
|
+
reservedOverlaps,
|
|
12397
|
+
touchedCodewordCount: touchedCodewords.size,
|
|
12398
|
+
touchedCodewordsByBlock,
|
|
12399
|
+
correctionBudgetPerBlock,
|
|
12400
|
+
safe,
|
|
12401
|
+
};
|
|
12402
|
+
}
|
|
12403
|
+
|
|
12404
|
+
/** Validate a canvas and return the non-certifying structural analysis. */
|
|
12405
|
+
function validateCanvasSpec(version, canvas = {}) {
|
|
12406
|
+
return analyzeCanvasDamage(version, canvas);
|
|
12407
|
+
}
|
|
12408
|
+
|
|
12409
|
+
/** Self-check the fixed profile contract and representative QR geometries. */
|
|
12410
|
+
function validateFrameQrTables() {
|
|
12411
|
+
const problems = [];
|
|
12412
|
+
if (FRAMEQR_PROFILE.certified !== false || FRAMEQR_PROFILE.densoFrameQrCompatible !== false) {
|
|
12413
|
+
problems.push('profile compatibility boundary must remain explicitly non-certified');
|
|
12414
|
+
}
|
|
12415
|
+
if (new Set(FRAMEQR_CANVAS_SHAPES).size !== FRAMEQR_CANVAS_SHAPES.length) {
|
|
12416
|
+
problems.push('canvas shape identifiers must be unique');
|
|
12417
|
+
}
|
|
12418
|
+
for (const version of [1, 2, 3, 4, 7, 10, 20, 30, 40]) {
|
|
12419
|
+
const size = versionSize(version);
|
|
12420
|
+
const spec = normalizeCanvasSpec(size);
|
|
12421
|
+
const modules = canvasModules(size, spec);
|
|
12422
|
+
const unique = new Set(modules.map(([x, y]) => `${x},${y}`));
|
|
12423
|
+
if (unique.size !== modules.length) problems.push(`v${version}: duplicate canvas modules`);
|
|
12424
|
+
if (modules.some(([x, y]) => x < 0 || y < 0 || x >= size || y >= size)) {
|
|
12425
|
+
problems.push(`v${version}: canvas escapes the symbol`);
|
|
12426
|
+
}
|
|
12427
|
+
const analysis = analyzeCanvasDamage(version, spec);
|
|
12428
|
+
if (analysis.touchedCodewordsByBlock.length !== blockLayout(version, 'H').blockCount) {
|
|
12429
|
+
problems.push(`v${version}: damage analysis block count mismatch`);
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12432
|
+
return problems;
|
|
12433
|
+
}
|
|
12434
|
+
|
|
12435
|
+
__exports.FRAMEQR_PROFILE = FRAMEQR_PROFILE;
|
|
12436
|
+
__exports.FRAMEQR_CANVAS_SHAPES = FRAMEQR_CANVAS_SHAPES;
|
|
12437
|
+
__exports.normalizeCanvasSpec = normalizeCanvasSpec;
|
|
12438
|
+
__exports.canvasModules = canvasModules;
|
|
12439
|
+
__exports.analyzeCanvasDamage = analyzeCanvasDamage;
|
|
12440
|
+
__exports.validateCanvasSpec = validateCanvasSpec;
|
|
12441
|
+
__exports.validateFrameQrTables = validateFrameQrTables;
|
|
12442
|
+
};
|
|
12443
|
+
|
|
12444
|
+
__modules["frameqr/encoder.js"] = function (__require, __exports) {
|
|
12445
|
+
/**
|
|
12446
|
+
* Sythos Canvas QR profile encoder.
|
|
12447
|
+
*
|
|
12448
|
+
* This encoder deliberately builds on this project's QR Code implementation
|
|
12449
|
+
* and then removes a conservatively bounded set of data modules for artwork.
|
|
12450
|
+
* It is not an implementation of DENSO FrameQR and makes no interoperability
|
|
12451
|
+
* claim for that proprietary format.
|
|
12452
|
+
*
|
|
12453
|
+
* @module frameqr/encoder
|
|
12454
|
+
*/
|
|
12455
|
+
const { EncodeError } = __require("core/errors.js");
|
|
12456
|
+
const { encodeQR } = __require("qr/encoder.js");
|
|
12457
|
+
const { FRAMEQR_PROFILE, canvasModules, normalizeCanvasSpec, validateCanvasSpec } = __require("frameqr/tables.js");
|
|
12458
|
+
|
|
12459
|
+
/**
|
|
12460
|
+
* @typedef {object} FrameQrEncodeOptions
|
|
12461
|
+
* @property {'H'} [ecc] The profile always uses QR error correction H.
|
|
12462
|
+
* @property {number} [version] Force a QR version 1-40.
|
|
12463
|
+
* @property {number} [mask] Force a QR mask 0-7.
|
|
12464
|
+
* @property {'auto'|'utf-8'|'iso-8859-1'} [charset] Byte mode interpretation.
|
|
12465
|
+
* @property {boolean} [kanji] Allow QR kanji mode.
|
|
12466
|
+
* @property {object} [canvas] Profile artwork reservation.
|
|
12467
|
+
*/
|
|
12468
|
+
|
|
12469
|
+
function versionFor(matrix) {
|
|
12470
|
+
return (matrix.width - 17) / 4;
|
|
12471
|
+
}
|
|
12472
|
+
|
|
12473
|
+
function clearCanvas(matrix, modules) {
|
|
12474
|
+
for (const [x, y] of modules) matrix.unset(x, y);
|
|
12475
|
+
}
|
|
12476
|
+
|
|
12477
|
+
/**
|
|
12478
|
+
* Encode a QR Code with a conservative artwork canvas according to the
|
|
12479
|
+
* non-certified Sythos Canvas QR profile.
|
|
12480
|
+
*
|
|
12481
|
+
* The profile forces QR H error correction and rejects a canvas whenever its
|
|
12482
|
+
* known codeword damage exceeds the per-block correction budget. When a
|
|
12483
|
+
* version is not forced, the smallest QR version that holds both payload and
|
|
12484
|
+
* safe canvas is selected. A decoder can reconstruct the reserved modules from
|
|
12485
|
+
* the returned profile metadata.
|
|
12486
|
+
*
|
|
12487
|
+
* @param {string} text
|
|
12488
|
+
* @param {FrameQrEncodeOptions} [options]
|
|
12489
|
+
* @returns {import('../core/bit-matrix.js').BitMatrix}
|
|
12490
|
+
* @throws {EncodeError} When the QR payload/options are invalid or the canvas
|
|
12491
|
+
* cannot safely fit the selected QR version.
|
|
12492
|
+
*/
|
|
12493
|
+
function encodeFrameQR(text, options = {}) {
|
|
12494
|
+
if (typeof text !== 'string') {
|
|
12495
|
+
throw new EncodeError('Sythos Canvas QR: text must be a string');
|
|
12496
|
+
}
|
|
12497
|
+
if (options.ecc !== undefined && options.ecc !== 'H') {
|
|
12498
|
+
throw new EncodeError('Sythos Canvas QR: ecc is fixed to H for this profile');
|
|
12499
|
+
}
|
|
12500
|
+
|
|
12501
|
+
const qrOptions = {
|
|
12502
|
+
mask: options.mask,
|
|
12503
|
+
charset: options.charset,
|
|
12504
|
+
kanji: options.kanji,
|
|
12505
|
+
ecc: 'H',
|
|
12506
|
+
};
|
|
12507
|
+
for (const key of Object.keys(qrOptions)) {
|
|
12508
|
+
if (qrOptions[key] === undefined) delete qrOptions[key];
|
|
12509
|
+
}
|
|
12510
|
+
|
|
12511
|
+
const versions = options.version === undefined
|
|
12512
|
+
? Array.from({ length: 40 }, (_, index) => index + 1)
|
|
12513
|
+
: [options.version];
|
|
12514
|
+
let capacityError = null;
|
|
12515
|
+
let unsafeAnalysis = null;
|
|
12516
|
+
let selected = null;
|
|
12517
|
+
|
|
12518
|
+
for (const version of versions) {
|
|
12519
|
+
let matrix;
|
|
12520
|
+
try {
|
|
12521
|
+
matrix = encodeQR(text, { ...qrOptions, version });
|
|
12522
|
+
} catch (error) {
|
|
12523
|
+
capacityError = error;
|
|
12524
|
+
continue;
|
|
12525
|
+
}
|
|
12526
|
+
|
|
12527
|
+
let canvas;
|
|
12528
|
+
let analysis;
|
|
12529
|
+
try {
|
|
12530
|
+
canvas = normalizeCanvasSpec(matrix.width, options.canvas);
|
|
12531
|
+
analysis = validateCanvasSpec(versionFor(matrix), canvas);
|
|
12532
|
+
} catch (error) {
|
|
12533
|
+
if (error instanceof EncodeError) throw error;
|
|
12534
|
+
throw new EncodeError(`Sythos Canvas QR: invalid canvas: ${error.message}`);
|
|
12535
|
+
}
|
|
12536
|
+
if (!analysis.safe) {
|
|
12537
|
+
unsafeAnalysis = analysis;
|
|
12538
|
+
continue;
|
|
12539
|
+
}
|
|
12540
|
+
selected = { matrix, canvas };
|
|
12541
|
+
break;
|
|
12542
|
+
}
|
|
12543
|
+
|
|
12544
|
+
if (!selected) {
|
|
12545
|
+
if (unsafeAnalysis) {
|
|
12546
|
+
throw new EncodeError(
|
|
12547
|
+
'Sythos Canvas QR: canvas is not safe for the selected QR version; ' +
|
|
12548
|
+
`it touches ${unsafeAnalysis.touchedCodewordCount} codewords and has ` +
|
|
12549
|
+
`a per-block correction budget of ${unsafeAnalysis.correctionBudgetPerBlock}`
|
|
12550
|
+
);
|
|
12551
|
+
}
|
|
12552
|
+
if (capacityError) throw capacityError;
|
|
12553
|
+
throw new EncodeError('Sythos Canvas QR: unable to select a QR version');
|
|
12554
|
+
}
|
|
12555
|
+
|
|
12556
|
+
const { matrix, canvas } = selected;
|
|
12557
|
+
clearCanvas(matrix, canvasModules(matrix.width, canvas));
|
|
12558
|
+
matrix.frameqr = {
|
|
12559
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12560
|
+
certified: false,
|
|
12561
|
+
canvas,
|
|
12562
|
+
};
|
|
12563
|
+
return matrix;
|
|
12564
|
+
}
|
|
12565
|
+
|
|
12566
|
+
__exports.encodeFrameQR = encodeFrameQR;
|
|
12567
|
+
};
|
|
12568
|
+
|
|
12569
|
+
__modules["frameqr/decoder.js"] = function (__require, __exports) {
|
|
12570
|
+
/**
|
|
12571
|
+
* Decoder for the Sythos Canvas QR profile.
|
|
12572
|
+
*
|
|
12573
|
+
* This is intentionally not a DENSO FrameQR decoder. The profile is a QR
|
|
12574
|
+
* symbol with a bounded artwork reservation and an explicit `frameqr` marker.
|
|
12575
|
+
* A normal QR matrix is rejected unless a detector (or another trusted
|
|
12576
|
+
* caller) supplies the profile and explicitly opts in to an unmarked matrix.
|
|
12577
|
+
*
|
|
12578
|
+
* The QR decoder is deliberately kept as the single payload decoder. It can
|
|
12579
|
+
* repair the bounded modules removed for the canvas because the encoder fixes
|
|
12580
|
+
* error correction to H and validates the reservation before clearing it.
|
|
12581
|
+
*
|
|
12582
|
+
* @module frameqr/decoder
|
|
12583
|
+
*/
|
|
12584
|
+
const { FormatError } = __require("core/errors.js");
|
|
12585
|
+
const { decodeQR } = __require("qr/decoder.js");
|
|
12586
|
+
const { FRAMEQR_PROFILE, normalizeCanvasSpec, validateCanvasSpec, analyzeCanvasDamage } = __require("frameqr/tables.js");
|
|
12587
|
+
|
|
12588
|
+
/** @param {unknown} value @returns {boolean} */
|
|
12589
|
+
function isObject(value) {
|
|
12590
|
+
return value !== null && typeof value === 'object';
|
|
12591
|
+
}
|
|
12592
|
+
|
|
12593
|
+
/**
|
|
12594
|
+
* Return the profile marker attached by the encoder. Older callers sometimes
|
|
12595
|
+
* use camel case, so accepting it here is harmless while the emitted marker
|
|
12596
|
+
* remains the canonical `frameqr` property.
|
|
12597
|
+
*
|
|
12598
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12599
|
+
* @returns {object|null}
|
|
12600
|
+
*/
|
|
12601
|
+
function markerOf(matrix) {
|
|
12602
|
+
const marker = matrix && (matrix.frameqr ?? matrix.frameQR);
|
|
12603
|
+
return isObject(marker) ? marker : null;
|
|
12604
|
+
}
|
|
12605
|
+
|
|
12606
|
+
/** @param {unknown} profile @returns {boolean} */
|
|
12607
|
+
function isExpectedProfile(profile) {
|
|
12608
|
+
if (profile === FRAMEQR_PROFILE.id) return true;
|
|
12609
|
+
return isObject(profile) && profile.id === FRAMEQR_PROFILE.id;
|
|
12610
|
+
}
|
|
12611
|
+
|
|
12612
|
+
/**
|
|
12613
|
+
* Normalize and validate the canvas metadata. Validation functions in the
|
|
12614
|
+
* table module throw on invalid input; a few consumers also return `false` or
|
|
12615
|
+
* a problem list, so those forms are handled defensively here.
|
|
12616
|
+
*
|
|
12617
|
+
* @param {number} symbolSize
|
|
12618
|
+
* @param {number} version
|
|
12619
|
+
* @param {object} canvas
|
|
12620
|
+
* @returns {{canvas: object, damage: object}}
|
|
12621
|
+
*/
|
|
12622
|
+
function validateCanvas(symbolSize, version, canvas) {
|
|
12623
|
+
if (!isObject(canvas)) throw new FormatError('Sythos Canvas QR: canvas metadata is missing');
|
|
12624
|
+
|
|
12625
|
+
let normalized;
|
|
12626
|
+
try {
|
|
12627
|
+
normalized = normalizeCanvasSpec(symbolSize, canvas);
|
|
12628
|
+
const validation = validateCanvasSpec(version, normalized);
|
|
12629
|
+
if (
|
|
12630
|
+
validation === false ||
|
|
12631
|
+
(Array.isArray(validation) && validation.length > 0) ||
|
|
12632
|
+
(isObject(validation) && validation.valid === false) ||
|
|
12633
|
+
(isObject(validation) && validation.safe === false)
|
|
12634
|
+
) {
|
|
12635
|
+
throw new Error('canvas geometry is outside the profile limits');
|
|
12636
|
+
}
|
|
12637
|
+
} catch (error) {
|
|
12638
|
+
if (error instanceof FormatError) throw error;
|
|
12639
|
+
throw new FormatError(`Sythos Canvas QR: invalid canvas metadata: ${error.message}`);
|
|
12640
|
+
}
|
|
12641
|
+
|
|
12642
|
+
let damage;
|
|
12643
|
+
try {
|
|
12644
|
+
damage = analyzeCanvasDamage(version, normalized);
|
|
12645
|
+
} catch (error) {
|
|
12646
|
+
throw new FormatError(`Sythos Canvas QR: cannot analyse canvas damage: ${error.message}`);
|
|
12647
|
+
}
|
|
12648
|
+
return { canvas: normalized, damage };
|
|
12649
|
+
}
|
|
12650
|
+
|
|
12651
|
+
/**
|
|
12652
|
+
* Resolve the profile marker and canvas from the matrix/options pair.
|
|
12653
|
+
*
|
|
12654
|
+
* `allowUnmarked` is deliberately opt-in. It exists for a detector that has
|
|
12655
|
+
* already verified the canvas signature after sampling a photograph; it is
|
|
12656
|
+
* not enabled by the public default and therefore cannot silently relabel an
|
|
12657
|
+
* ordinary QR Code as FrameQR.
|
|
12658
|
+
*
|
|
12659
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12660
|
+
* @param {object} options
|
|
12661
|
+
* @returns {{profile: string, canvas: object, damage: object, certified: false}}
|
|
12662
|
+
*/
|
|
12663
|
+
function resolveProfile(matrix, options) {
|
|
12664
|
+
const marker = markerOf(matrix);
|
|
12665
|
+
const suppliedProfile = options.profile;
|
|
12666
|
+
const markerProfile = marker ? marker.profile : undefined;
|
|
12667
|
+
if (markerProfile !== undefined && suppliedProfile !== undefined) {
|
|
12668
|
+
const markerId = isObject(markerProfile) ? markerProfile.id : markerProfile;
|
|
12669
|
+
const suppliedId = isObject(suppliedProfile) ? suppliedProfile.id : suppliedProfile;
|
|
12670
|
+
if (markerId !== suppliedId) {
|
|
12671
|
+
throw new FormatError('Sythos Canvas QR: matrix and requested profile markers disagree');
|
|
12672
|
+
}
|
|
12673
|
+
}
|
|
12674
|
+
const profile = markerProfile ?? suppliedProfile;
|
|
12675
|
+
|
|
12676
|
+
if (!isExpectedProfile(profile)) {
|
|
12677
|
+
throw new FormatError(
|
|
12678
|
+
'Sythos Canvas QR: profile marker is missing or is not the Sythos Canvas QR profile'
|
|
12679
|
+
);
|
|
12680
|
+
}
|
|
12681
|
+
if (marker && marker.certified === true) {
|
|
12682
|
+
throw new FormatError(
|
|
12683
|
+
'Sythos Canvas QR: certified FrameQR input is not supported by this profile decoder'
|
|
12684
|
+
);
|
|
12685
|
+
}
|
|
12686
|
+
if (!marker && !options.allowUnmarked) {
|
|
12687
|
+
throw new FormatError(
|
|
12688
|
+
'Sythos Canvas QR: unmarked QR matrix rejected; provide a verified profile marker'
|
|
12689
|
+
);
|
|
12690
|
+
}
|
|
12691
|
+
|
|
12692
|
+
const markerCanvas = marker ? marker.canvas : undefined;
|
|
12693
|
+
const canvas = options.canvas ?? markerCanvas;
|
|
12694
|
+
const version = (matrix.width - 17) / 4;
|
|
12695
|
+
if (!Number.isInteger(version) || version < 1 || version > 40) {
|
|
12696
|
+
throw new FormatError(`Sythos Canvas QR: invalid QR symbol size ${matrix.width}`);
|
|
12697
|
+
}
|
|
12698
|
+
const checked = validateCanvas(matrix.width, version, canvas);
|
|
12699
|
+
if (markerCanvas && options.canvas) {
|
|
12700
|
+
let marked;
|
|
12701
|
+
try {
|
|
12702
|
+
marked = normalizeCanvasSpec(matrix.width, markerCanvas);
|
|
12703
|
+
} catch (error) {
|
|
12704
|
+
throw new FormatError(`Sythos Canvas QR: invalid marker canvas: ${error.message}`);
|
|
12705
|
+
}
|
|
12706
|
+
const fields = ['shape', 'centerX', 'centerY', 'width', 'height', 'angle'];
|
|
12707
|
+
if (fields.some((field) => marked[field] !== checked.canvas[field])) {
|
|
12708
|
+
throw new FormatError('Sythos Canvas QR: matrix and requested canvas metadata disagree');
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12711
|
+
return {
|
|
12712
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12713
|
+
canvas: checked.canvas,
|
|
12714
|
+
damage: checked.damage,
|
|
12715
|
+
certified: false,
|
|
12716
|
+
};
|
|
12717
|
+
}
|
|
12718
|
+
|
|
12719
|
+
/**
|
|
12720
|
+
* Decode a Sythos Canvas QR matrix.
|
|
12721
|
+
*
|
|
12722
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
12723
|
+
* Square QR modules, normally returned by `encodeFrameQR` or a FrameQR
|
|
12724
|
+
* detector. The encoder's `frameqr` metadata is required by default.
|
|
12725
|
+
* @param {object} [options]
|
|
12726
|
+
* @param {object} [options.canvas] Explicit canvas metadata for a sampled
|
|
12727
|
+
* matrix when the source marker was not preserved.
|
|
12728
|
+
* @param {string|object} [options.profile] Expected profile identifier.
|
|
12729
|
+
* @param {boolean} [options.allowUnmarked=false] Explicit detector opt-in for
|
|
12730
|
+
* a matrix whose marker was lost during image sampling.
|
|
12731
|
+
* @returns {import('../qr/decoder.js').DecodeResult & {
|
|
12732
|
+
* format: 'frameqr', profile: string, certified: false,
|
|
12733
|
+
* frame: object, canvas: object, canvasDamage: object
|
|
12734
|
+
* }}
|
|
12735
|
+
* @throws {FormatError} If the profile marker/canvas is invalid or the input
|
|
12736
|
+
* is an ordinary QR Code.
|
|
12737
|
+
*/
|
|
12738
|
+
function decodeFrameQR(matrix, options = {}) {
|
|
12739
|
+
if (!matrix || !Number.isInteger(matrix.width) || !Number.isInteger(matrix.height)) {
|
|
12740
|
+
throw new FormatError('Sythos Canvas QR: no matrix supplied');
|
|
12741
|
+
}
|
|
12742
|
+
if (matrix.width !== matrix.height) {
|
|
12743
|
+
throw new FormatError(
|
|
12744
|
+
`Sythos Canvas QR: symbol must be square, got ${matrix.width}x${matrix.height}`
|
|
12745
|
+
);
|
|
12746
|
+
}
|
|
12747
|
+
|
|
12748
|
+
const profile = resolveProfile(matrix, options);
|
|
12749
|
+
let decoded;
|
|
12750
|
+
try {
|
|
12751
|
+
decoded = decodeQR(matrix);
|
|
12752
|
+
} catch (error) {
|
|
12753
|
+
// Preserve the original QR/RS error when possible, but give callers a
|
|
12754
|
+
// profile-specific context without exposing implementation details.
|
|
12755
|
+
if (error instanceof FormatError) {
|
|
12756
|
+
throw new FormatError(`Sythos Canvas QR: payload is not recoverable: ${error.message}`);
|
|
12757
|
+
}
|
|
12758
|
+
throw error;
|
|
12759
|
+
}
|
|
12760
|
+
|
|
12761
|
+
return {
|
|
12762
|
+
...decoded,
|
|
12763
|
+
format: 'frameqr',
|
|
12764
|
+
profile: profile.profile,
|
|
12765
|
+
certified: profile.certified,
|
|
12766
|
+
frame: profile.canvas,
|
|
12767
|
+
canvas: profile.canvas,
|
|
12768
|
+
canvasDamage: profile.damage,
|
|
12769
|
+
};
|
|
12770
|
+
}
|
|
12771
|
+
__exports.isExpectedProfile = isExpectedProfile;
|
|
12772
|
+
|
|
12773
|
+
__exports.decodeFrameQR = decodeFrameQR;
|
|
12774
|
+
};
|
|
12775
|
+
|
|
12776
|
+
__modules["frameqr/detector.js"] = function (__require, __exports) {
|
|
12777
|
+
/**
|
|
12778
|
+
* Detector for the non-certified Sythos Canvas QR profile.
|
|
12779
|
+
*
|
|
12780
|
+
* The profile deliberately reuses QR Model 2 geometry. Finder localisation and
|
|
12781
|
+
* projective sampling therefore use the QR detector; the additional profile
|
|
12782
|
+
* check is the light canvas signature. A sampled image cannot carry the
|
|
12783
|
+
* encoder's in-memory marker, so the detector reconstructs the expected
|
|
12784
|
+
* metadata, verifies that every reserved canvas module is light, and only then
|
|
12785
|
+
* opts into the profile decoder. This rejects ordinary QR symbols whose centre
|
|
12786
|
+
* merely happens to contain a plausible payload.
|
|
12787
|
+
*
|
|
12788
|
+
* Detection is verified for clean binarized rasters, integer scaling, quiet
|
|
12789
|
+
* zones, and in-plane quarter turns. Arbitrary photographic perspective is not
|
|
12790
|
+
* claimed by this module.
|
|
12791
|
+
*
|
|
12792
|
+
* @module frameqr/detector
|
|
12793
|
+
*/
|
|
12794
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
12795
|
+
const { sampleQuad } = __require("image/grid-sampler.js");
|
|
12796
|
+
const { detectQR } = __require("qr/detector.js");
|
|
12797
|
+
const { decodeFrameQR } = __require("frameqr/decoder.js");
|
|
12798
|
+
const { FRAMEQR_PROFILE, canvasModules, normalizeCanvasSpec } = __require("frameqr/tables.js");
|
|
12799
|
+
|
|
12800
|
+
/** @typedef {{x:number, y:number}} Point */
|
|
12801
|
+
|
|
12802
|
+
/** @typedef {object} FrameQRDetection
|
|
12803
|
+
* @property {Point[]} corners Outer corners in reading order.
|
|
12804
|
+
* @property {number} dimension QR modules per side.
|
|
12805
|
+
* @property {number} version QR Model 2 version.
|
|
12806
|
+
* @property {number} moduleSize Estimated pixels per module.
|
|
12807
|
+
* @property {number} rotation Clockwise in-plane orientation in degrees.
|
|
12808
|
+
* @property {BitMatrix} matrix Rectified profile matrix.
|
|
12809
|
+
* @property {object} canvas Normalized canvas specification.
|
|
12810
|
+
* @property {string} profile Profile identifier.
|
|
12811
|
+
* @property {false} certified Always false for this implementation.
|
|
12812
|
+
*/
|
|
12813
|
+
|
|
12814
|
+
function orientationDegrees(corners) {
|
|
12815
|
+
const [tl, tr] = corners;
|
|
12816
|
+
const angle = Math.atan2(tr.y - tl.y, tr.x - tl.x) * 180 / Math.PI;
|
|
12817
|
+
return ((Math.round(angle / 90) * 90) % 360 + 360) % 360;
|
|
12818
|
+
}
|
|
12819
|
+
/**
|
|
12820
|
+
* Return the number of canvas modules that are dark in a sampled matrix.
|
|
12821
|
+
*
|
|
12822
|
+
* Encoded profile symbols clear the whole reserved canvas. A non-zero count
|
|
12823
|
+
* means either a normal QR symbol or a raster whose canvas was overwritten by
|
|
12824
|
+
* artwork; both are rejected because decoding that image would be speculative.
|
|
12825
|
+
*/
|
|
12826
|
+
function canvasDarkCount(matrix, canvas) {
|
|
12827
|
+
let dark = 0;
|
|
12828
|
+
for (const [x, y] of canvasModules(matrix.width, canvas)) {
|
|
12829
|
+
if (matrix.get(x, y)) dark++;
|
|
12830
|
+
}
|
|
12831
|
+
return dark;
|
|
12832
|
+
}
|
|
12833
|
+
|
|
12834
|
+
function sameCandidate(left, right) {
|
|
12835
|
+
if (left.dimension !== right.dimension) return false;
|
|
12836
|
+
const a = left.corners[0];
|
|
12837
|
+
const b = right.corners[0];
|
|
12838
|
+
return Math.hypot(a.x - b.x, a.y - b.y) <= Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
12839
|
+
}
|
|
12840
|
+
|
|
12841
|
+
/**
|
|
12842
|
+
* Detect Sythos Canvas QR symbols in a binarized raster.
|
|
12843
|
+
*
|
|
12844
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage Set bit = dark.
|
|
12845
|
+
* @param {object} [options]
|
|
12846
|
+
* @param {object} [options.canvas] Explicit canvas metadata for non-default
|
|
12847
|
+
* shapes/size. Without it, the profile's canonical centered square is used.
|
|
12848
|
+
* @param {boolean} [options.voting=false] Use majority sampling per module.
|
|
12849
|
+
* @returns {FrameQRDetection[]} Best candidate first; empty when no verified
|
|
12850
|
+
* profile signature is found.
|
|
12851
|
+
*/
|
|
12852
|
+
function detectFrameQR(binaryImage, options = {}) {
|
|
12853
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
12854
|
+
throw new NotFoundError('detectFrameQR: no image supplied');
|
|
12855
|
+
}
|
|
12856
|
+
|
|
12857
|
+
let candidates;
|
|
12858
|
+
try { candidates = detectQR(binaryImage); } catch { return []; }
|
|
12859
|
+
const detections = [];
|
|
12860
|
+
|
|
12861
|
+
for (const candidate of candidates) {
|
|
12862
|
+
// QR detector dimensions are already constrained to legal Model 2 sizes.
|
|
12863
|
+
let canvas;
|
|
12864
|
+
try { canvas = normalizeCanvasSpec(candidate.dimension, options.canvas); }
|
|
12865
|
+
catch { continue; }
|
|
12866
|
+
|
|
12867
|
+
for (const voting of [Boolean(options.voting), !Boolean(options.voting)]) {
|
|
12868
|
+
let matrix;
|
|
12869
|
+
try { matrix = sampleQuad(binaryImage, candidate.dimension, candidate.corners, voting); }
|
|
12870
|
+
catch { continue; }
|
|
12871
|
+
|
|
12872
|
+
// The signature check is intentionally strict. It prevents an ordinary
|
|
12873
|
+
// QR symbol from being relabelled as Canvas QR by the decoder's explicit
|
|
12874
|
+
// allowUnmarked escape hatch.
|
|
12875
|
+
if (canvasDarkCount(matrix, canvas) !== 0) continue;
|
|
12876
|
+
|
|
12877
|
+
const marked = matrix;
|
|
12878
|
+
marked.frameqr = {
|
|
12879
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12880
|
+
certified: false,
|
|
12881
|
+
canvas,
|
|
12882
|
+
};
|
|
12883
|
+
let decoded;
|
|
12884
|
+
try {
|
|
12885
|
+
decoded = decodeFrameQR(marked, {
|
|
12886
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12887
|
+
canvas,
|
|
12888
|
+
allowUnmarked: true,
|
|
12889
|
+
});
|
|
12890
|
+
} catch { continue; }
|
|
12891
|
+
|
|
12892
|
+
const detection = {
|
|
12893
|
+
corners: candidate.corners,
|
|
12894
|
+
dimension: candidate.dimension,
|
|
12895
|
+
version: candidate.version,
|
|
12896
|
+
moduleSize: candidate.moduleSize,
|
|
12897
|
+
rotation: orientationDegrees(candidate.corners),
|
|
12898
|
+
matrix: marked,
|
|
12899
|
+
canvas,
|
|
12900
|
+
profile: FRAMEQR_PROFILE.id,
|
|
12901
|
+
certified: false,
|
|
12902
|
+
result: decoded,
|
|
12903
|
+
};
|
|
12904
|
+
if (!detections.some((entry) => sameCandidate(entry, detection))) detections.push(detection);
|
|
12905
|
+
break;
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
|
|
12909
|
+
detections.sort((a, b) => b.moduleSize - a.moduleSize);
|
|
12910
|
+
return detections;
|
|
12911
|
+
}
|
|
12912
|
+
|
|
12913
|
+
/**
|
|
12914
|
+
* Detect and decode all verified Canvas QR symbols in one call.
|
|
12915
|
+
*
|
|
12916
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
12917
|
+
* @param {object} [options]
|
|
12918
|
+
* @returns {Array<object>}
|
|
12919
|
+
*/
|
|
12920
|
+
function detectAndDecodeFrameQR(binaryImage, options = {}) {
|
|
12921
|
+
let detections;
|
|
12922
|
+
try { detections = detectFrameQR(binaryImage, options); } catch { return []; }
|
|
12923
|
+
const results = [];
|
|
12924
|
+
const seen = new Set();
|
|
12925
|
+
for (const detection of detections) {
|
|
12926
|
+
const result = detection.result;
|
|
12927
|
+
const key = `${result.version}|${result.text}`;
|
|
12928
|
+
if (seen.has(key)) continue;
|
|
12929
|
+
seen.add(key);
|
|
12930
|
+
results.push({ ...result, corners: detection.corners, rotation: detection.rotation });
|
|
12931
|
+
}
|
|
12932
|
+
return results;
|
|
12933
|
+
}
|
|
12934
|
+
|
|
12935
|
+
__exports.detectFrameQR = detectFrameQR;
|
|
12936
|
+
__exports.detectAndDecodeFrameQR = detectAndDecodeFrameQR;
|
|
12937
|
+
};
|
|
12938
|
+
|
|
12939
|
+
__modules["frameqr/index.js"] = function (__require, __exports) {
|
|
12940
|
+
const __reexport0 = __require("frameqr/encoder.js"); __exports.encodeFrameQR = __reexport0.encodeFrameQR;
|
|
12941
|
+
const __reexport1 = __require("frameqr/decoder.js"); __exports.decodeFrameQR = __reexport1.decodeFrameQR;
|
|
12942
|
+
const __reexport2 = __require("frameqr/detector.js"); __exports.detectFrameQR = __reexport2.detectFrameQR; __exports.detectAndDecodeFrameQR = __reexport2.detectAndDecodeFrameQR;
|
|
12943
|
+
const __reexport3 = __require("frameqr/tables.js"); __exports.FRAMEQR_PROFILE = __reexport3.FRAMEQR_PROFILE; __exports.FRAMEQR_CANVAS_SHAPES = __reexport3.FRAMEQR_CANVAS_SHAPES; __exports.canvasModules = __reexport3.canvasModules; __exports.normalizeCanvasSpec = __reexport3.normalizeCanvasSpec; __exports.analyzeCanvasDamage = __reexport3.analyzeCanvasDamage; __exports.validateCanvasSpec = __reexport3.validateCanvasSpec; __exports.validateFrameQrTables = __reexport3.validateFrameQrTables;
|
|
12944
|
+
|
|
12945
|
+
|
|
12946
|
+
};
|
|
12947
|
+
|
|
12948
|
+
__modules["aztecrune/tables.js"] = function (__require, __exports) {
|
|
12949
|
+
/**
|
|
12950
|
+
* Aztec Rune geometry.
|
|
12951
|
+
*
|
|
12952
|
+
* Aztec Rune is the 11x11, one-byte member of the Aztec family. Two 4-bit
|
|
12953
|
+
* data words and five 4-bit Reed-Solomon check words are XORed with the fixed
|
|
12954
|
+
* Aztec Rune mask and written clockwise on the outer data ring. The finder and
|
|
12955
|
+
* corner orientation marks are independent of the value.
|
|
12956
|
+
*
|
|
12957
|
+
* The constants below are derived from the public Aztec Code specification and
|
|
12958
|
+
* are kept separate from the general Aztec implementation so a normal Aztec
|
|
12959
|
+
* decoder can never accidentally treat an 11x11 Rune as a regular symbol.
|
|
12960
|
+
*
|
|
12961
|
+
* @module aztecrune/tables
|
|
12962
|
+
*/
|
|
12963
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
12964
|
+
const { fieldForWordSize } = __require("aztec/tables.js");
|
|
12965
|
+
const AZTEC_RUNE_SIZE = 11;
|
|
12966
|
+
const AZTEC_RUNE_DATA_BITS = 8;
|
|
12967
|
+
const AZTEC_RUNE_WORD_SIZE = 4;
|
|
12968
|
+
const AZTEC_RUNE_DATA_CODEWORDS = 2;
|
|
12969
|
+
const AZTEC_RUNE_ECC_CODEWORDS = 5;
|
|
12970
|
+
const AZTEC_RUNE_TOTAL_CODEWORDS = AZTEC_RUNE_DATA_CODEWORDS + AZTEC_RUNE_ECC_CODEWORDS;
|
|
12971
|
+
const AZTEC_RUNE_MASK = 0b1010;
|
|
12972
|
+
|
|
12973
|
+
/**
|
|
12974
|
+
* Data-module coordinates in wire order: clockwise, starting at the top.
|
|
12975
|
+
* Every side contributes seven modules, for 28 bits in total.
|
|
12976
|
+
*/
|
|
12977
|
+
const AZTEC_RUNE_DATA_POSITIONS = Object.freeze([
|
|
12978
|
+
...Array.from({ length: 7 }, (_, i) => [i + 2, 0]),
|
|
12979
|
+
...Array.from({ length: 7 }, (_, i) => [10, i + 2]),
|
|
12980
|
+
...Array.from({ length: 7 }, (_, i) => [8 - i, 10]),
|
|
12981
|
+
...Array.from({ length: 7 }, (_, i) => [0, 8 - i]),
|
|
12982
|
+
].map(([x, y]) => Object.freeze([x, y])));
|
|
12983
|
+
|
|
12984
|
+
const DATA_KEYS = new Set(AZTEC_RUNE_DATA_POSITIONS.map(([x, y]) => `${x},${y}`));
|
|
12985
|
+
|
|
12986
|
+
/** @param {number} x @param {number} y @returns {boolean} */
|
|
12987
|
+
function isAztecRuneDataPosition(x, y) {
|
|
12988
|
+
return DATA_KEYS.has(`${x},${y}`);
|
|
12989
|
+
}
|
|
12990
|
+
/**
|
|
12991
|
+
* Return the value of a structural module. Data positions return `null`.
|
|
12992
|
+
* The bull's-eye has five square rings including its one-module centre; its
|
|
12993
|
+
* even-radius rings are dark. The four corner groups are the orientation marks
|
|
12994
|
+
* described by the Aztec Rune specification.
|
|
12995
|
+
*
|
|
12996
|
+
* @param {number} x @param {number} y
|
|
12997
|
+
* @returns {boolean|null}
|
|
12998
|
+
*/
|
|
12999
|
+
function aztecRuneStructuralValue(x, y) {
|
|
13000
|
+
if (!Number.isInteger(x) || !Number.isInteger(y) || x < 0 || y < 0 || x >= AZTEC_RUNE_SIZE || y >= AZTEC_RUNE_SIZE) {
|
|
13001
|
+
return null;
|
|
13002
|
+
}
|
|
13003
|
+
if (isAztecRuneDataPosition(x, y)) return null;
|
|
13004
|
+
|
|
13005
|
+
const distance = Math.max(Math.abs(x - 5), Math.abs(y - 5));
|
|
13006
|
+
if (distance <= 4) return (distance & 1) === 0;
|
|
13007
|
+
|
|
13008
|
+
// Corner orientation groups, clockwise from the upper-left corner:
|
|
13009
|
+
// three dark, white-dark-dark, dark-white-white, three white.
|
|
13010
|
+
if ((x === 0 && y <= 1) || (y === 0 && x <= 1)) return true;
|
|
13011
|
+
if ((x === 10 && y <= 1) || (y === 0 && x === 10)) return true;
|
|
13012
|
+
if (x === 9 && y === 0) return false;
|
|
13013
|
+
if (x === 10 && y === 10) return false;
|
|
13014
|
+
if (y === 10 && x >= 0 && x <= 1) return false;
|
|
13015
|
+
if (x === 0 && y >= 9) return false;
|
|
13016
|
+
if (x === 10 && y === 9) return true;
|
|
13017
|
+
|
|
13018
|
+
// All non-data cells are covered by the bull's-eye or the four corners.
|
|
13019
|
+
return false;
|
|
13020
|
+
}
|
|
13021
|
+
|
|
13022
|
+
/** Build only the fixed finder/orientation structure. */
|
|
13023
|
+
function buildAztecRuneStructure() {
|
|
13024
|
+
const matrix = new BitMatrix(AZTEC_RUNE_SIZE);
|
|
13025
|
+
for (let y = 0; y < AZTEC_RUNE_SIZE; y++) {
|
|
13026
|
+
for (let x = 0; x < AZTEC_RUNE_SIZE; x++) {
|
|
13027
|
+
const value = aztecRuneStructuralValue(x, y);
|
|
13028
|
+
if (value === true) matrix.set(x, y);
|
|
13029
|
+
}
|
|
13030
|
+
}
|
|
13031
|
+
return matrix;
|
|
13032
|
+
}
|
|
13033
|
+
|
|
13034
|
+
/** Return the field used by Rune data/check words. */
|
|
13035
|
+
function aztecRuneField() {
|
|
13036
|
+
return fieldForWordSize(AZTEC_RUNE_WORD_SIZE);
|
|
13037
|
+
}
|
|
13038
|
+
|
|
13039
|
+
/** Validate the fixed Rune contract and coordinate layout. */
|
|
13040
|
+
function validateAztecRuneTables() {
|
|
13041
|
+
const problems = [];
|
|
13042
|
+
if (AZTEC_RUNE_SIZE !== 11) problems.push('Rune symbol size must be 11');
|
|
13043
|
+
if (AZTEC_RUNE_TOTAL_CODEWORDS * AZTEC_RUNE_WORD_SIZE !== 28) problems.push('Rune outer ring must carry 28 bits');
|
|
13044
|
+
if (AZTEC_RUNE_DATA_POSITIONS.length !== 28) problems.push('Rune data ring must contain 28 modules');
|
|
13045
|
+
if (new Set(AZTEC_RUNE_DATA_POSITIONS.map(([x, y]) => `${x},${y}`)).size !== 28) problems.push('Rune data coordinates must be unique');
|
|
13046
|
+
const structure = buildAztecRuneStructure();
|
|
13047
|
+
for (let y = 0; y < AZTEC_RUNE_SIZE; y++) for (let x = 0; x < AZTEC_RUNE_SIZE; x++) {
|
|
13048
|
+
if (isAztecRuneDataPosition(x, y)) continue;
|
|
13049
|
+
const expected = aztecRuneStructuralValue(x, y) === true;
|
|
13050
|
+
if (structure.get(x, y) !== expected) problems.push(`Rune structure mismatch at ${x},${y}`);
|
|
13051
|
+
}
|
|
13052
|
+
return problems;
|
|
13053
|
+
}
|
|
13054
|
+
|
|
13055
|
+
__exports.AZTEC_RUNE_SIZE = AZTEC_RUNE_SIZE;
|
|
13056
|
+
__exports.AZTEC_RUNE_DATA_BITS = AZTEC_RUNE_DATA_BITS;
|
|
13057
|
+
__exports.AZTEC_RUNE_WORD_SIZE = AZTEC_RUNE_WORD_SIZE;
|
|
13058
|
+
__exports.AZTEC_RUNE_DATA_CODEWORDS = AZTEC_RUNE_DATA_CODEWORDS;
|
|
13059
|
+
__exports.AZTEC_RUNE_ECC_CODEWORDS = AZTEC_RUNE_ECC_CODEWORDS;
|
|
13060
|
+
__exports.AZTEC_RUNE_TOTAL_CODEWORDS = AZTEC_RUNE_TOTAL_CODEWORDS;
|
|
13061
|
+
__exports.AZTEC_RUNE_MASK = AZTEC_RUNE_MASK;
|
|
13062
|
+
__exports.AZTEC_RUNE_DATA_POSITIONS = AZTEC_RUNE_DATA_POSITIONS;
|
|
13063
|
+
__exports.isAztecRuneDataPosition = isAztecRuneDataPosition;
|
|
13064
|
+
__exports.aztecRuneStructuralValue = aztecRuneStructuralValue;
|
|
13065
|
+
__exports.buildAztecRuneStructure = buildAztecRuneStructure;
|
|
13066
|
+
__exports.aztecRuneField = aztecRuneField;
|
|
13067
|
+
__exports.validateAztecRuneTables = validateAztecRuneTables;
|
|
13068
|
+
};
|
|
13069
|
+
|
|
13070
|
+
__modules["aztecrune/encoder.js"] = function (__require, __exports) {
|
|
13071
|
+
/**
|
|
13072
|
+
* Aztec Rune encoder.
|
|
13073
|
+
*
|
|
13074
|
+
* A Rune carries exactly one byte. Numeric strings are accepted for convenient
|
|
13075
|
+
* human-readable input and are normalized to the canonical three-digit text;
|
|
13076
|
+
* binary callers can pass one byte in an ArrayBuffer or typed-array view.
|
|
13077
|
+
*
|
|
13078
|
+
* @module aztecrune/encoder
|
|
13079
|
+
*/
|
|
13080
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
13081
|
+
const { EncodeError } = __require("core/errors.js");
|
|
13082
|
+
const { rsEncode } = __require("core/reed-solomon.js");
|
|
13083
|
+
const { AZTEC_RUNE_DATA_POSITIONS, AZTEC_RUNE_ECC_CODEWORDS, AZTEC_RUNE_MASK, AZTEC_RUNE_SIZE, aztecRuneField, buildAztecRuneStructure } = __require("aztecrune/tables.js");
|
|
13084
|
+
|
|
13085
|
+
/** @param {unknown} value @returns {number} */
|
|
13086
|
+
function normalizeAztecRuneValue(value) {
|
|
13087
|
+
if (typeof value === 'number') {
|
|
13088
|
+
if (Number.isInteger(value) && value >= 0 && value <= 255) return value;
|
|
13089
|
+
throw new EncodeError('Aztec Rune: numeric value must be an integer from 0 to 255');
|
|
13090
|
+
}
|
|
13091
|
+
|
|
13092
|
+
if (typeof value === 'string') {
|
|
13093
|
+
if (!/^\d{1,3}$/.test(value)) throw new EncodeError('Aztec Rune: text input must contain one to three decimal digits');
|
|
13094
|
+
const numeric = Number(value);
|
|
13095
|
+
if (numeric > 255) throw new EncodeError('Aztec Rune: decimal value must be from 000 to 255');
|
|
13096
|
+
return numeric;
|
|
13097
|
+
}
|
|
13098
|
+
|
|
13099
|
+
let bytes = null;
|
|
13100
|
+
if (value instanceof ArrayBuffer) bytes = new Uint8Array(value);
|
|
13101
|
+
else if (ArrayBuffer.isView(value)) bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
13102
|
+
if (bytes) {
|
|
13103
|
+
if (bytes.length !== 1) throw new EncodeError('Aztec Rune: binary input must contain exactly one byte');
|
|
13104
|
+
return bytes[0];
|
|
13105
|
+
}
|
|
13106
|
+
throw new EncodeError('Aztec Rune: expected a number, decimal string or one-byte binary value');
|
|
13107
|
+
}
|
|
13108
|
+
|
|
13109
|
+
/** @param {number} value @returns {number[]} */
|
|
13110
|
+
function codewordsForValue(value) {
|
|
13111
|
+
const data = [value >>> 4, value & 0x0f];
|
|
13112
|
+
const parity = rsEncode(data, AZTEC_RUNE_ECC_CODEWORDS, aztecRuneField(), 1);
|
|
13113
|
+
return data.concat(parity).map((word) => word ^ AZTEC_RUNE_MASK);
|
|
13114
|
+
}
|
|
13115
|
+
|
|
13116
|
+
/** @param {BitMatrix} matrix @param {number[]} codewords */
|
|
13117
|
+
function writeDataRing(matrix, codewords) {
|
|
13118
|
+
for (let word = 0; word < codewords.length; word++) {
|
|
13119
|
+
for (let bit = 0; bit < 4; bit++) {
|
|
13120
|
+
const at = word * 4 + bit;
|
|
13121
|
+
const [x, y] = AZTEC_RUNE_DATA_POSITIONS[at];
|
|
13122
|
+
matrix.setValue(x, y, ((codewords[word] >>> (3 - bit)) & 1) !== 0);
|
|
13123
|
+
}
|
|
13124
|
+
}
|
|
13125
|
+
}
|
|
13126
|
+
|
|
13127
|
+
/**
|
|
13128
|
+
* Encode one byte as an Aztec Rune.
|
|
13129
|
+
*
|
|
13130
|
+
* @param {number|string|ArrayBuffer|ArrayBufferView} value
|
|
13131
|
+
* @param {{inverted?: boolean}} [options]
|
|
13132
|
+
* @returns {import('../core/bit-matrix.js').BitMatrix & {
|
|
13133
|
+
* format: 'aztecrune', value: number, inverted: boolean
|
|
13134
|
+
* }}
|
|
13135
|
+
*/
|
|
13136
|
+
function encodeAztecRune(value, options = {}) {
|
|
13137
|
+
const numeric = normalizeAztecRuneValue(value);
|
|
13138
|
+
if (options.inverted !== undefined && typeof options.inverted !== 'boolean') {
|
|
13139
|
+
throw new EncodeError('Aztec Rune: inverted must be boolean');
|
|
13140
|
+
}
|
|
13141
|
+
const inverted = options.inverted === true;
|
|
13142
|
+
const matrix = buildAztecRuneStructure();
|
|
13143
|
+
writeDataRing(matrix, codewordsForValue(numeric));
|
|
13144
|
+
if (inverted) {
|
|
13145
|
+
for (let y = 0; y < AZTEC_RUNE_SIZE; y++) for (let x = 0; x < AZTEC_RUNE_SIZE; x++) matrix.flip(x, y);
|
|
13146
|
+
}
|
|
13147
|
+
matrix.format = 'aztecrune';
|
|
13148
|
+
matrix.value = numeric;
|
|
13149
|
+
matrix.inverted = inverted;
|
|
13150
|
+
return matrix;
|
|
13151
|
+
}
|
|
13152
|
+
__exports.codewordsForValue = codewordsForValue;
|
|
13153
|
+
|
|
13154
|
+
__exports.normalizeAztecRuneValue = normalizeAztecRuneValue;
|
|
13155
|
+
__exports.encodeAztecRune = encodeAztecRune;
|
|
13156
|
+
};
|
|
13157
|
+
|
|
13158
|
+
__modules["aztecrune/decoder.js"] = function (__require, __exports) {
|
|
13159
|
+
/**
|
|
13160
|
+
* Aztec Rune decoder.
|
|
13161
|
+
*
|
|
13162
|
+
* The input is a square 11x11 module matrix. Structural modules are checked
|
|
13163
|
+
* before the seven masked GF(16) codewords are Reed-Solomon corrected. The
|
|
13164
|
+
* decoder tries the four in-plane quarter turns and both polarities, allowing
|
|
13165
|
+
* it to consume an oriented sampled image while still rejecting ordinary
|
|
13166
|
+
* Aztec/QR matrices.
|
|
13167
|
+
*
|
|
13168
|
+
* @module aztecrune/decoder
|
|
13169
|
+
*/
|
|
13170
|
+
const { FormatError } = __require("core/errors.js");
|
|
13171
|
+
const { rsDecode } = __require("core/reed-solomon.js");
|
|
13172
|
+
const { AZTEC_RUNE_DATA_CODEWORDS, AZTEC_RUNE_DATA_POSITIONS, AZTEC_RUNE_ECC_CODEWORDS, AZTEC_RUNE_MASK, AZTEC_RUNE_SIZE, aztecRuneField, aztecRuneStructuralValue } = __require("aztecrune/tables.js");
|
|
13173
|
+
|
|
13174
|
+
/** @param {import('../core/bit-matrix.js').BitMatrix} source @param {number} turns */
|
|
13175
|
+
function rotateClockwise(source, turns) {
|
|
13176
|
+
let current = source.clone();
|
|
13177
|
+
for (let turn = 0; turn < turns; turn++) {
|
|
13178
|
+
const out = new source.constructor(current.height, current.width);
|
|
13179
|
+
for (let y = 0; y < current.height; y++) for (let x = 0; x < current.width; x++) {
|
|
13180
|
+
if (current.get(x, y)) out.set(current.height - 1 - y, x);
|
|
13181
|
+
}
|
|
13182
|
+
current = out;
|
|
13183
|
+
}
|
|
13184
|
+
return current;
|
|
13185
|
+
}
|
|
13186
|
+
|
|
13187
|
+
/** @param {import('../core/bit-matrix.js').BitMatrix} matrix */
|
|
13188
|
+
function invert(matrix) {
|
|
13189
|
+
const out = matrix.clone();
|
|
13190
|
+
for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
|
|
13191
|
+
return out;
|
|
13192
|
+
}
|
|
13193
|
+
|
|
13194
|
+
/** @param {import('../core/bit-matrix.js').BitMatrix} matrix @param {boolean} inverted */
|
|
13195
|
+
function structureMatches(matrix, inverted) {
|
|
13196
|
+
for (let y = 0; y < AZTEC_RUNE_SIZE; y++) for (let x = 0; x < AZTEC_RUNE_SIZE; x++) {
|
|
13197
|
+
const expected = aztecRuneStructuralValue(x, y);
|
|
13198
|
+
if (expected === null) continue;
|
|
13199
|
+
if (matrix.get(x, y) !== (inverted ? !expected : expected)) return false;
|
|
13200
|
+
}
|
|
13201
|
+
return true;
|
|
13202
|
+
}
|
|
13203
|
+
|
|
13204
|
+
/** @param {import('../core/bit-matrix.js').BitMatrix} matrix @param {boolean} inverted */
|
|
13205
|
+
function readCodewords(matrix, inverted) {
|
|
13206
|
+
const words = new Array(2 + AZTEC_RUNE_ECC_CODEWORDS).fill(0);
|
|
13207
|
+
for (let at = 0; at < AZTEC_RUNE_DATA_POSITIONS.length; at++) {
|
|
13208
|
+
const [x, y] = AZTEC_RUNE_DATA_POSITIONS[at];
|
|
13209
|
+
const bit = matrix.get(x, y) !== inverted;
|
|
13210
|
+
words[at >>> 2] = (words[at >>> 2] << 1) | (bit ? 1 : 0);
|
|
13211
|
+
}
|
|
13212
|
+
return words.map((word) => word ^ AZTEC_RUNE_MASK);
|
|
13213
|
+
}
|
|
13214
|
+
|
|
13215
|
+
/** @param {number} value @param {boolean} inverted @param {number} rotation @param {number} corrections */
|
|
13216
|
+
function result(value, inverted, rotation, corrections) {
|
|
13217
|
+
return {
|
|
13218
|
+
format: 'aztecrune',
|
|
13219
|
+
value,
|
|
13220
|
+
text: String(value).padStart(3, '0'),
|
|
13221
|
+
bytes: Uint8Array.of(value),
|
|
13222
|
+
dimension: AZTEC_RUNE_SIZE,
|
|
13223
|
+
inverted,
|
|
13224
|
+
rotation,
|
|
13225
|
+
corrections,
|
|
13226
|
+
};
|
|
13227
|
+
}
|
|
13228
|
+
|
|
13229
|
+
/**
|
|
13230
|
+
* Decode a square Aztec Rune matrix.
|
|
13231
|
+
*
|
|
13232
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
13233
|
+
* @param {{inverted?: boolean|'auto', rotation?: number|'auto'}} [options]
|
|
13234
|
+
* @returns {{format:'aztecrune',value:number,text:string,bytes:Uint8Array,dimension:number,inverted:boolean,rotation:number,corrections:number}}
|
|
13235
|
+
* @throws {FormatError} For non-Rune geometry, structure or uncorrectable data.
|
|
13236
|
+
*/
|
|
13237
|
+
function decodeAztecRune(matrix, options = {}) {
|
|
13238
|
+
if (!matrix || matrix.width !== AZTEC_RUNE_SIZE || matrix.height !== AZTEC_RUNE_SIZE) {
|
|
13239
|
+
throw new FormatError(`Aztec Rune: expected an ${AZTEC_RUNE_SIZE}x${AZTEC_RUNE_SIZE} module matrix`);
|
|
13240
|
+
}
|
|
13241
|
+
const invertedModes = options.inverted === true ? [true] : options.inverted === false ? [false] : [false, true];
|
|
13242
|
+
const rotations = Number.isInteger(options.rotation)
|
|
13243
|
+
? [((options.rotation % 360) + 360) % 360]
|
|
13244
|
+
: [0, 90, 180, 270];
|
|
13245
|
+
|
|
13246
|
+
let checksumError = null;
|
|
13247
|
+
for (const requestedRotation of rotations) {
|
|
13248
|
+
if (requestedRotation % 90 !== 0) continue;
|
|
13249
|
+
// To canonicalize an input rotated clockwise by k degrees, rotate it
|
|
13250
|
+
// counter-clockwise by k. `turns` is the clockwise operation we apply.
|
|
13251
|
+
const turns = (4 - requestedRotation / 90) % 4;
|
|
13252
|
+
const canonicalRotation = requestedRotation;
|
|
13253
|
+
const rotated = turns === 0 ? matrix.clone() : rotateClockwise(matrix, turns);
|
|
13254
|
+
for (const inverted of invertedModes) {
|
|
13255
|
+
const canonical = inverted ? invert(rotated) : rotated;
|
|
13256
|
+
if (!structureMatches(canonical, false)) continue;
|
|
13257
|
+
const words = readCodewords(canonical, false);
|
|
13258
|
+
try {
|
|
13259
|
+
const corrections = rsDecode(words, AZTEC_RUNE_ECC_CODEWORDS, aztecRuneField(), 1);
|
|
13260
|
+
const value = (words[0] << 4) | words[1];
|
|
13261
|
+
return result(value, inverted, canonicalRotation, corrections);
|
|
13262
|
+
} catch (error) {
|
|
13263
|
+
checksumError = error;
|
|
13264
|
+
}
|
|
13265
|
+
}
|
|
13266
|
+
}
|
|
13267
|
+
|
|
13268
|
+
if (checksumError) {
|
|
13269
|
+
throw new FormatError(`Aztec Rune: Reed-Solomon check failed: ${checksumError.message}`);
|
|
13270
|
+
}
|
|
13271
|
+
throw new FormatError('Aztec Rune: structural pattern or orientation is invalid');
|
|
13272
|
+
}
|
|
13273
|
+
__exports.readCodewords = readCodewords; __exports.structureMatches = structureMatches;
|
|
13274
|
+
|
|
13275
|
+
__exports.decodeAztecRune = decodeAztecRune;
|
|
13276
|
+
};
|
|
13277
|
+
|
|
13278
|
+
__modules["aztecrune/detector.js"] = function (__require, __exports) {
|
|
13279
|
+
/**
|
|
13280
|
+
* Detector for Aztec Rune symbols.
|
|
13281
|
+
*
|
|
13282
|
+
* Rune has a fixed 11x11 geometry and a distinctive five-ring bull's-eye. This
|
|
13283
|
+
* detector targets binarized, module-aligned rasters (including an integer
|
|
13284
|
+
* scale, quiet zone and quarter turns). Arbitrary perspective and severe blur
|
|
13285
|
+
* remain detector-level limitations; callers needing those conditions should
|
|
13286
|
+
* pass a rectified 11x11 matrix to decodeAztecRune.
|
|
13287
|
+
*
|
|
13288
|
+
* @module aztecrune/detector
|
|
13289
|
+
*/
|
|
13290
|
+
const { NotFoundError } = __require("core/errors.js");
|
|
13291
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
13292
|
+
const { decodeAztecRune } = __require("aztecrune/decoder.js");
|
|
13293
|
+
const { AZTEC_RUNE_SIZE } = __require("aztecrune/tables.js");
|
|
13294
|
+
|
|
13295
|
+
/** @typedef {{x:number,y:number}} Point */
|
|
13296
|
+
|
|
13297
|
+
/**
|
|
13298
|
+
* Find connected components of a polarity. Components touching the image
|
|
13299
|
+
* border are ignored when they exceed the plausible module area; this avoids
|
|
13300
|
+
* returning the light background as a candidate while retaining an inverted
|
|
13301
|
+
* Rune's isolated light centre module.
|
|
13302
|
+
*/
|
|
13303
|
+
function components(image, value) {
|
|
13304
|
+
const width = image.width;
|
|
13305
|
+
const height = image.height;
|
|
13306
|
+
const seen = new Uint8Array(width * height);
|
|
13307
|
+
const limit = Math.max(64, Math.floor(width * height * 0.04));
|
|
13308
|
+
const found = [];
|
|
13309
|
+
|
|
13310
|
+
for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) {
|
|
13311
|
+
const start = y * width + x;
|
|
13312
|
+
if (seen[start] || image.get(x, y) !== value) continue;
|
|
13313
|
+
const queue = [[x, y]];
|
|
13314
|
+
seen[start] = 1;
|
|
13315
|
+
let head = 0;
|
|
13316
|
+
let minX = x; let maxX = x; let minY = y; let maxY = y;
|
|
13317
|
+
let count = 0;
|
|
13318
|
+
let touchesBorder = false;
|
|
13319
|
+
|
|
13320
|
+
while (head < queue.length) {
|
|
13321
|
+
const [px, py] = queue[head++];
|
|
13322
|
+
count++;
|
|
13323
|
+
minX = Math.min(minX, px); maxX = Math.max(maxX, px);
|
|
13324
|
+
minY = Math.min(minY, py); maxY = Math.max(maxY, py);
|
|
13325
|
+
if (px === 0 || py === 0 || px === width - 1 || py === height - 1) touchesBorder = true;
|
|
13326
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
13327
|
+
if (nx < 0 || ny < 0 || nx >= width || ny >= height) continue;
|
|
13328
|
+
const at = ny * width + nx;
|
|
13329
|
+
if (!seen[at] && image.get(nx, ny) === value) {
|
|
13330
|
+
seen[at] = 1;
|
|
13331
|
+
queue.push([nx, ny]);
|
|
13332
|
+
}
|
|
13333
|
+
}
|
|
13334
|
+
}
|
|
13335
|
+
|
|
13336
|
+
const boxWidth = maxX - minX + 1;
|
|
13337
|
+
const boxHeight = maxY - minY + 1;
|
|
13338
|
+
if (count <= limit && !touchesBorder &&
|
|
13339
|
+
Math.abs(boxWidth - boxHeight) <= Math.max(1, Math.ceil(Math.max(boxWidth, boxHeight) * 0.35)) &&
|
|
13340
|
+
count >= boxWidth * boxHeight * 0.45) {
|
|
13341
|
+
found.push({ x: (minX + maxX) / 2, y: (minY + maxY) / 2, width: boxWidth, height: boxHeight, count });
|
|
13342
|
+
}
|
|
13343
|
+
}
|
|
13344
|
+
return found.sort((a, b) => b.count - a.count);
|
|
13345
|
+
}
|
|
13346
|
+
/** @param {import('../core/bit-matrix.js').BitMatrix} image @param {Point} center @param {number} pitch */
|
|
13347
|
+
function sample(image, center, pitch) {
|
|
13348
|
+
const matrix = new BitMatrix(AZTEC_RUNE_SIZE);
|
|
13349
|
+
for (let y = 0; y < AZTEC_RUNE_SIZE; y++) for (let x = 0; x < AZTEC_RUNE_SIZE; x++) {
|
|
13350
|
+
const px = Math.round(center.x + (x - 5) * pitch);
|
|
13351
|
+
const py = Math.round(center.y + (y - 5) * pitch);
|
|
13352
|
+
if (px >= 0 && py >= 0 && px < image.width && py < image.height && image.get(px, py)) matrix.set(x, y);
|
|
13353
|
+
}
|
|
13354
|
+
return matrix;
|
|
13355
|
+
}
|
|
13356
|
+
|
|
13357
|
+
function corners(center, pitch) {
|
|
13358
|
+
const half = AZTEC_RUNE_SIZE * pitch / 2;
|
|
13359
|
+
return [
|
|
13360
|
+
{ x: center.x - half, y: center.y - half },
|
|
13361
|
+
{ x: center.x + half, y: center.y - half },
|
|
13362
|
+
{ x: center.x + half, y: center.y + half },
|
|
13363
|
+
{ x: center.x - half, y: center.y + half },
|
|
13364
|
+
];
|
|
13365
|
+
}
|
|
13366
|
+
|
|
13367
|
+
function sameCandidate(left, right) {
|
|
13368
|
+
return Math.hypot(left.center.x - right.center.x, left.center.y - right.center.y) <= Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
13369
|
+
}
|
|
13370
|
+
|
|
13371
|
+
/**
|
|
13372
|
+
* Detect the most prominent Aztec Rune in a binarized image.
|
|
13373
|
+
*
|
|
13374
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
13375
|
+
* @returns {{corners:Point[],dimension:11,moduleSize:number,matrix:BitMatrix,result:object}|null}
|
|
13376
|
+
*/
|
|
13377
|
+
function detectAztecRune(binaryImage) {
|
|
13378
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
13379
|
+
throw new NotFoundError('detectAztecRune: no image supplied');
|
|
13380
|
+
}
|
|
13381
|
+
const candidates = [];
|
|
13382
|
+
for (const value of [true, false]) {
|
|
13383
|
+
for (const component of components(binaryImage, value)) {
|
|
13384
|
+
const pitch = (component.width + component.height) / 2;
|
|
13385
|
+
if (pitch < 0.8) continue;
|
|
13386
|
+
const matrix = sample(binaryImage, component, pitch);
|
|
13387
|
+
let decoded;
|
|
13388
|
+
try { decoded = decodeAztecRune(matrix); } catch { continue; }
|
|
13389
|
+
const candidate = {
|
|
13390
|
+
center: component,
|
|
13391
|
+
corners: corners(component, pitch),
|
|
13392
|
+
dimension: AZTEC_RUNE_SIZE,
|
|
13393
|
+
moduleSize: pitch,
|
|
13394
|
+
matrix,
|
|
13395
|
+
result: decoded,
|
|
13396
|
+
};
|
|
13397
|
+
if (!candidates.some((entry) => sameCandidate(entry, candidate))) candidates.push(candidate);
|
|
13398
|
+
}
|
|
13399
|
+
}
|
|
13400
|
+
candidates.sort((a, b) => b.moduleSize - a.moduleSize);
|
|
13401
|
+
const best = candidates[0];
|
|
13402
|
+
if (!best) return null;
|
|
13403
|
+
delete best.center;
|
|
13404
|
+
return best;
|
|
13405
|
+
}
|
|
13406
|
+
|
|
13407
|
+
/** Detect and decode one Aztec Rune, or return `null` when none is verified. */
|
|
13408
|
+
function detectAndDecodeAztecRune(binaryImage) {
|
|
13409
|
+
let detection;
|
|
13410
|
+
try { detection = detectAztecRune(binaryImage); } catch { return null; }
|
|
13411
|
+
if (!detection) return null;
|
|
13412
|
+
return { ...detection.result, corners: detection.corners, moduleSize: detection.moduleSize };
|
|
13413
|
+
}
|
|
13414
|
+
|
|
13415
|
+
__exports.detectAztecRune = detectAztecRune;
|
|
13416
|
+
__exports.detectAndDecodeAztecRune = detectAndDecodeAztecRune;
|
|
13417
|
+
};
|
|
13418
|
+
|
|
13419
|
+
__modules["aztecrune/index.js"] = function (__require, __exports) {
|
|
13420
|
+
/** Aztec Rune entry points. @module aztecrune */
|
|
13421
|
+
const __reexport0 = __require("aztecrune/encoder.js"); __exports.encodeAztecRune = __reexport0.encodeAztecRune; __exports.normalizeAztecRuneValue = __reexport0.normalizeAztecRuneValue;
|
|
13422
|
+
const __reexport1 = __require("aztecrune/decoder.js"); __exports.decodeAztecRune = __reexport1.decodeAztecRune;
|
|
13423
|
+
const __reexport2 = __require("aztecrune/detector.js"); __exports.detectAztecRune = __reexport2.detectAztecRune; __exports.detectAndDecodeAztecRune = __reexport2.detectAndDecodeAztecRune;
|
|
13424
|
+
const __reexport3 = __require("aztecrune/tables.js"); __exports.AZTEC_RUNE_SIZE = __reexport3.AZTEC_RUNE_SIZE; __exports.AZTEC_RUNE_DATA_BITS = __reexport3.AZTEC_RUNE_DATA_BITS; __exports.AZTEC_RUNE_WORD_SIZE = __reexport3.AZTEC_RUNE_WORD_SIZE; __exports.AZTEC_RUNE_DATA_CODEWORDS = __reexport3.AZTEC_RUNE_DATA_CODEWORDS; __exports.AZTEC_RUNE_ECC_CODEWORDS = __reexport3.AZTEC_RUNE_ECC_CODEWORDS; __exports.AZTEC_RUNE_TOTAL_CODEWORDS = __reexport3.AZTEC_RUNE_TOTAL_CODEWORDS; __exports.AZTEC_RUNE_MASK = __reexport3.AZTEC_RUNE_MASK; __exports.AZTEC_RUNE_DATA_POSITIONS = __reexport3.AZTEC_RUNE_DATA_POSITIONS; __exports.aztecRuneStructuralValue = __reexport3.aztecRuneStructuralValue; __exports.buildAztecRuneStructure = __reexport3.buildAztecRuneStructure; __exports.aztecRuneField = __reexport3.aztecRuneField; __exports.validateAztecRuneTables = __reexport3.validateAztecRuneTables;
|
|
13425
|
+
|
|
13426
|
+
|
|
13427
|
+
};
|
|
13428
|
+
|
|
13429
|
+
__modules["compactpdf417/tables.js"] = function (__require, __exports) {
|
|
13430
|
+
/**
|
|
13431
|
+
* Compact (truncated) PDF417 layout.
|
|
13432
|
+
*
|
|
13433
|
+
* Compact PDF417 is a geometric variant of PDF417, not a new high-level
|
|
13434
|
+
* compaction mode. It keeps the same codeword, cluster, row-indicator,
|
|
13435
|
+
* Reed–Solomon and Text/Byte/Numeric compaction rules, while omitting each
|
|
13436
|
+
* row's right row indicator and replacing the normal stop pattern with a
|
|
13437
|
+
* single dark module. The resulting symbol remains distinguishable from the
|
|
13438
|
+
* ordinary PDF417 layout by its width and row terminator.
|
|
13439
|
+
*
|
|
13440
|
+
* @module compactpdf417/tables
|
|
13441
|
+
*/
|
|
13442
|
+
const { EncodeError } = __require("core/errors.js");
|
|
13443
|
+
|
|
13444
|
+
/** PDF417 start pattern, in module widths. */
|
|
13445
|
+
const COMPACT_PDF417_START = '81111113';
|
|
13446
|
+
|
|
13447
|
+
/** Binary module representation of the start pattern. */
|
|
13448
|
+
const COMPACT_PDF417_START_BITS = '11111111010101000';
|
|
13449
|
+
|
|
13450
|
+
/** Compact PDF417's reduced stop is one dark module. */
|
|
13451
|
+
const COMPACT_PDF417_STOP_MODULES = 1;
|
|
13452
|
+
|
|
13453
|
+
/** Minimum/maximum number of rows and data columns. */
|
|
13454
|
+
const COMPACT_PDF417_LIMITS = Object.freeze({
|
|
13455
|
+
minRows: 3,
|
|
13456
|
+
maxRows: 90,
|
|
13457
|
+
minColumns: 1,
|
|
13458
|
+
maxColumns: 30,
|
|
13459
|
+
maxCodewords: 928,
|
|
13460
|
+
});
|
|
13461
|
+
|
|
13462
|
+
/**
|
|
13463
|
+
* Return the printed width in modules for a compact symbol.
|
|
13464
|
+
*
|
|
13465
|
+
* 17 start + 17 left indicator + 17 per codeword column + 1 reduced stop.
|
|
13466
|
+
* @param {number} columns
|
|
13467
|
+
*/
|
|
13468
|
+
function compactPdf417Width(columns) {
|
|
13469
|
+
if (!Number.isInteger(columns) || columns < 1 || columns > 30) {
|
|
13470
|
+
throw new RangeError(`Compact PDF417: columns must be an integer in 1..30, got ${columns}`);
|
|
13471
|
+
}
|
|
13472
|
+
return 35 + columns * 17;
|
|
13473
|
+
}
|
|
13474
|
+
|
|
13475
|
+
/**
|
|
13476
|
+
* Validate a compact matrix geometry and return its dimensions.
|
|
13477
|
+
*
|
|
13478
|
+
* @param {number} width
|
|
13479
|
+
* @param {number} height
|
|
13480
|
+
* @param {number} rowHeight
|
|
13481
|
+
*/
|
|
13482
|
+
function compactPdf417Geometry(width, height, rowHeight = 3) {
|
|
13483
|
+
if (!Number.isInteger(width) || !Number.isInteger(height)) {
|
|
13484
|
+
throw new RangeError('Compact PDF417: matrix dimensions must be integers');
|
|
13485
|
+
}
|
|
13486
|
+
if (!Number.isInteger(rowHeight) || rowHeight < 1) {
|
|
13487
|
+
throw new RangeError('Compact PDF417: rowHeight must be a positive integer');
|
|
13488
|
+
}
|
|
13489
|
+
if ((width - 35) % 17 !== 0) {
|
|
13490
|
+
throw new RangeError(`Compact PDF417: width ${width} does not match the compact layout`);
|
|
13491
|
+
}
|
|
13492
|
+
const columns = (width - 35) / 17;
|
|
13493
|
+
const rows = height / rowHeight;
|
|
13494
|
+
if (!Number.isInteger(rows) || rows < 3 || rows > 90) {
|
|
13495
|
+
throw new RangeError(`Compact PDF417: rows must be an integer in 3..90, got ${rows}`);
|
|
13496
|
+
}
|
|
13497
|
+
if (columns < 1 || columns > 30) {
|
|
13498
|
+
throw new RangeError(`Compact PDF417: columns must be an integer in 1..30, got ${columns}`);
|
|
13499
|
+
}
|
|
13500
|
+
return { rows, columns, rowHeight, width, height };
|
|
13501
|
+
}
|
|
13502
|
+
|
|
13503
|
+
/**
|
|
13504
|
+
* Validate encoder dimensions before codeword packing.
|
|
13505
|
+
* @param {object} options
|
|
13506
|
+
*/
|
|
13507
|
+
function validateCompactPdf417Options(options = {}) {
|
|
13508
|
+
const { minRows, maxRows, minColumns, maxColumns } = COMPACT_PDF417_LIMITS;
|
|
13509
|
+
const rowHeight = options.rowHeight ?? 3;
|
|
13510
|
+
if (!Number.isInteger(rowHeight) || rowHeight < 3) {
|
|
13511
|
+
throw new EncodeError('Compact PDF417: rowHeight must be an integer of at least 3');
|
|
13512
|
+
}
|
|
13513
|
+
for (const [name, value, min, max] of [
|
|
13514
|
+
['rows', options.rows, minRows, maxRows],
|
|
13515
|
+
['columns', options.columns, minColumns, maxColumns],
|
|
13516
|
+
]) {
|
|
13517
|
+
if (value !== undefined && (!Number.isInteger(value) || value < min || value > max)) {
|
|
13518
|
+
throw new EncodeError(`Compact PDF417: ${name} must be an integer in ${min}..${max}`);
|
|
13519
|
+
}
|
|
13520
|
+
}
|
|
13521
|
+
if (options.aspectRatio !== undefined &&
|
|
13522
|
+
(!Number.isFinite(options.aspectRatio) || options.aspectRatio <= 0)) {
|
|
13523
|
+
throw new EncodeError('Compact PDF417: aspectRatio must be positive');
|
|
13524
|
+
}
|
|
13525
|
+
return rowHeight;
|
|
13526
|
+
}
|
|
13527
|
+
|
|
13528
|
+
/**
|
|
13529
|
+
* The row indicator values used by ordinary PDF417 are retained unchanged.
|
|
13530
|
+
* Compact symbols omit only the right indicator, so the left indicator still
|
|
13531
|
+
* carries the row group, row count, error-correction level and column count.
|
|
13532
|
+
*/
|
|
13533
|
+
function compactPdf417Indicators(row, rows, columns, level) {
|
|
13534
|
+
const group = Math.floor(row / 3);
|
|
13535
|
+
const y = Math.floor((rows - 1) / 3);
|
|
13536
|
+
const z = level * 3 + (rows - 1) % 3;
|
|
13537
|
+
const v = columns - 1;
|
|
13538
|
+
if (row % 3 === 0) return 30 * group + y;
|
|
13539
|
+
if (row % 3 === 1) return 30 * group + z;
|
|
13540
|
+
return 30 * group + v;
|
|
13541
|
+
}
|
|
13542
|
+
|
|
13543
|
+
/**
|
|
13544
|
+
* Return all standard ECC levels which agree with the observed left
|
|
13545
|
+
* indicators. This is kept as a table helper so decoder and tests share the
|
|
13546
|
+
* same compact-layout rule.
|
|
13547
|
+
*/
|
|
13548
|
+
function compactPdf417MatchingLevels(leftIndicators, rows, columns) {
|
|
13549
|
+
const matches = [];
|
|
13550
|
+
for (let level = 0; level <= 8; level++) {
|
|
13551
|
+
if (leftIndicators.every((value, row) =>
|
|
13552
|
+
value === compactPdf417Indicators(row, rows, columns, level))) matches.push(level);
|
|
13553
|
+
}
|
|
13554
|
+
return matches;
|
|
13555
|
+
}
|
|
13556
|
+
|
|
13557
|
+
/** Validate representative layout invariants. */
|
|
13558
|
+
function validateCompactPdf417Tables() {
|
|
13559
|
+
const problems = [];
|
|
13560
|
+
for (const columns of [1, 2, 3, 10, 30]) {
|
|
13561
|
+
const width = compactPdf417Width(columns);
|
|
13562
|
+
if (width !== 35 + columns * 17) problems.push(`columns ${columns}: width mismatch`);
|
|
13563
|
+
}
|
|
13564
|
+
for (const rows of [3, 4, 5, 6, 90]) {
|
|
13565
|
+
const left = Array.from({ length: rows }, (_, row) => compactPdf417Indicators(row, rows, 3, 2));
|
|
13566
|
+
if (compactPdf417MatchingLevels(left, rows, 3).length !== 1) {
|
|
13567
|
+
problems.push(`rows ${rows}: row indicators do not identify one ECC level`);
|
|
13568
|
+
}
|
|
13569
|
+
}
|
|
13570
|
+
return problems;
|
|
13571
|
+
}
|
|
13572
|
+
|
|
13573
|
+
__exports.COMPACT_PDF417_START = COMPACT_PDF417_START;
|
|
13574
|
+
__exports.COMPACT_PDF417_START_BITS = COMPACT_PDF417_START_BITS;
|
|
13575
|
+
__exports.COMPACT_PDF417_STOP_MODULES = COMPACT_PDF417_STOP_MODULES;
|
|
13576
|
+
__exports.COMPACT_PDF417_LIMITS = COMPACT_PDF417_LIMITS;
|
|
13577
|
+
__exports.compactPdf417Width = compactPdf417Width;
|
|
13578
|
+
__exports.compactPdf417Geometry = compactPdf417Geometry;
|
|
13579
|
+
__exports.validateCompactPdf417Options = validateCompactPdf417Options;
|
|
13580
|
+
__exports.compactPdf417Indicators = compactPdf417Indicators;
|
|
13581
|
+
__exports.compactPdf417MatchingLevels = compactPdf417MatchingLevels;
|
|
13582
|
+
__exports.validateCompactPdf417Tables = validateCompactPdf417Tables;
|
|
13583
|
+
};
|
|
13584
|
+
|
|
13585
|
+
__modules["compactpdf417/encoder.js"] = function (__require, __exports) {
|
|
13586
|
+
/** Compact PDF417 (truncated PDF417) encoder. @module compactpdf417/encoder */
|
|
13587
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
13588
|
+
const { EncodeError } = __require("core/errors.js");
|
|
13589
|
+
const { compactPdf417 } = __require("pdf417/compaction.js");
|
|
13590
|
+
const { pdf417ErrorCorrection, pdf417EccLength } = __require("pdf417/error-correction.js");
|
|
13591
|
+
const { pdf417PatternForCodeword } = __require("pdf417/tables.js");
|
|
13592
|
+
const { COMPACT_PDF417_START, compactPdf417Indicators, compactPdf417Width, validateCompactPdf417Options } = __require("compactpdf417/tables.js");
|
|
13593
|
+
|
|
13594
|
+
function appendWidths(matrix, y, x, sequence, height) {
|
|
13595
|
+
let dark = true;
|
|
13596
|
+
for (const digit of sequence) {
|
|
13597
|
+
const width = digit.charCodeAt(0) - 48;
|
|
13598
|
+
if (!Number.isInteger(width) || width < 1 || width > 8) {
|
|
13599
|
+
throw new EncodeError('Compact PDF417: invalid module-width sequence');
|
|
13600
|
+
}
|
|
13601
|
+
if (dark) matrix.setRegion(x, y, width, height);
|
|
13602
|
+
x += width;
|
|
13603
|
+
dark = !dark;
|
|
13604
|
+
}
|
|
13605
|
+
return x;
|
|
13606
|
+
}
|
|
13607
|
+
|
|
13608
|
+
function patternSequence(pattern) {
|
|
13609
|
+
return pattern.toString(2).padStart(17, '0').replace(/0+|1+/g, (run) => String(run.length));
|
|
13610
|
+
}
|
|
13611
|
+
|
|
13612
|
+
function dimensions(needed, level, options, rowHeight) {
|
|
13613
|
+
const ecc = pdf417EccLength(level);
|
|
13614
|
+
let best = null;
|
|
13615
|
+
for (let rows = options.rows ?? 3; rows <= (options.rows ?? 90); rows++) {
|
|
13616
|
+
for (let columns = options.columns ?? 1; columns <= (options.columns ?? 30); columns++) {
|
|
13617
|
+
if (rows * columns > 928 || rows * columns - ecc < needed) continue;
|
|
13618
|
+
const ratio = compactPdf417Width(columns) / (rows * rowHeight);
|
|
13619
|
+
const score = (rows * columns - ecc - needed) * 10 +
|
|
13620
|
+
Math.abs(ratio - (options.aspectRatio ?? 3));
|
|
13621
|
+
if (!best || score < best.score) best = { rows, columns, score };
|
|
13622
|
+
}
|
|
13623
|
+
}
|
|
13624
|
+
if (!best) {
|
|
13625
|
+
throw new EncodeError(
|
|
13626
|
+
'Compact PDF417: payload does not fit the requested dimensions and error correction level'
|
|
13627
|
+
);
|
|
13628
|
+
}
|
|
13629
|
+
return best;
|
|
13630
|
+
}
|
|
13631
|
+
|
|
13632
|
+
/**
|
|
13633
|
+
* Encode Compact PDF417.
|
|
13634
|
+
*
|
|
13635
|
+
* High-level Text/Byte/Numeric compaction is intentionally delegated to the
|
|
13636
|
+
* existing PDF417 compaction implementation. `compact` here describes only
|
|
13637
|
+
* the physical truncated layout: right row indicators are omitted and the
|
|
13638
|
+
* stop pattern is reduced to one dark module.
|
|
13639
|
+
*
|
|
13640
|
+
* @param {string|Uint8Array|number[]} value
|
|
13641
|
+
* @param {object} [options]
|
|
13642
|
+
* @returns {import('../core/bit-matrix.js').BitMatrix}
|
|
13643
|
+
*/
|
|
13644
|
+
function encodeCompactPDF417(value, options = {}) {
|
|
13645
|
+
const rowHeight = validateCompactPdf417Options(options);
|
|
13646
|
+
const level = options.eccLevel ?? 2;
|
|
13647
|
+
if (!Number.isInteger(level) || level < 0 || level > 8) {
|
|
13648
|
+
throw new EncodeError('Compact PDF417: eccLevel must be an integer in 0..8');
|
|
13649
|
+
}
|
|
13650
|
+
|
|
13651
|
+
const payload = compactPdf417(value, { compaction: options.compaction, charset: options.charset });
|
|
13652
|
+
const { rows, columns } = dimensions(payload.length + 1, level, options, rowHeight);
|
|
13653
|
+
const eccLength = pdf417EccLength(level);
|
|
13654
|
+
const dataLength = rows * columns - eccLength;
|
|
13655
|
+
const data = [dataLength, ...payload];
|
|
13656
|
+
while (data.length < dataLength) data.push(900);
|
|
13657
|
+
const codewords = data.concat(pdf417ErrorCorrection(data, level));
|
|
13658
|
+
|
|
13659
|
+
const matrix = new BitMatrix(compactPdf417Width(columns), rows * rowHeight);
|
|
13660
|
+
for (let row = 0; row < rows; row++) {
|
|
13661
|
+
const y = row * rowHeight;
|
|
13662
|
+
const cluster = (row % 3) * 3;
|
|
13663
|
+
let x = appendWidths(matrix, y, 0, COMPACT_PDF417_START, rowHeight);
|
|
13664
|
+
const left = compactPdf417Indicators(row, rows, columns, level);
|
|
13665
|
+
x = appendWidths(matrix, y, x, patternSequence(pdf417PatternForCodeword(left, cluster)), rowHeight);
|
|
13666
|
+
for (let column = 0; column < columns; column++) {
|
|
13667
|
+
const codeword = codewords[row * columns + column];
|
|
13668
|
+
x = appendWidths(matrix, y, x,
|
|
13669
|
+
patternSequence(pdf417PatternForCodeword(codeword, cluster)), rowHeight);
|
|
13670
|
+
}
|
|
13671
|
+
// Compact PDF417 terminates every row with one dark module, not the normal
|
|
13672
|
+
// right indicator plus 18-module stop pattern.
|
|
13673
|
+
matrix.setRegion(x, y, 1, rowHeight);
|
|
13674
|
+
x++;
|
|
13675
|
+
if (x !== matrix.width) throw new EncodeError('Compact PDF417: internal row width mismatch');
|
|
13676
|
+
}
|
|
13677
|
+
|
|
13678
|
+
matrix.compactPdf417 = {
|
|
13679
|
+
rows,
|
|
13680
|
+
columns,
|
|
13681
|
+
eccLevel: level,
|
|
13682
|
+
rowHeight,
|
|
13683
|
+
codewords,
|
|
13684
|
+
layout: 'compact',
|
|
13685
|
+
};
|
|
13686
|
+
return matrix;
|
|
13687
|
+
}
|
|
13688
|
+
__exports.compactPdf417Dimensions = dimensions;
|
|
13689
|
+
|
|
13690
|
+
__exports.encodeCompactPDF417 = encodeCompactPDF417;
|
|
13691
|
+
};
|
|
13692
|
+
|
|
13693
|
+
__modules["compactpdf417/decoder.js"] = function (__require, __exports) {
|
|
13694
|
+
/** Compact PDF417 decoder. @module compactpdf417/decoder */
|
|
13695
|
+
const { FormatError } = __require("core/errors.js");
|
|
13696
|
+
const { decodePdf417CompactionDetailed } = __require("pdf417/compaction.js");
|
|
13697
|
+
const { pdf417CorrectErrors, pdf417EccLength } = __require("pdf417/error-correction.js");
|
|
13698
|
+
const { pdf417CodewordForPattern } = __require("pdf417/tables.js");
|
|
13699
|
+
const { COMPACT_PDF417_START_BITS, compactPdf417Geometry, compactPdf417Indicators, compactPdf417MatchingLevels } = __require("compactpdf417/tables.js");
|
|
13700
|
+
|
|
13701
|
+
function bits(matrix, y, x, width) {
|
|
13702
|
+
let value = 0;
|
|
13703
|
+
for (let index = 0; index < width; index++) value = (value << 1) | (matrix.get(x + index, y) ? 1 : 0);
|
|
13704
|
+
return value;
|
|
13705
|
+
}
|
|
13706
|
+
|
|
13707
|
+
function patternString(matrix, y, x, width) {
|
|
13708
|
+
return bits(matrix, y, x, width).toString(2).padStart(width, '0');
|
|
13709
|
+
}
|
|
13710
|
+
|
|
13711
|
+
/**
|
|
13712
|
+
* Decode a compact/truncated PDF417 module matrix.
|
|
13713
|
+
*
|
|
13714
|
+
* The high-level payload uses the normal PDF417 Text/Byte/Numeric compaction
|
|
13715
|
+
* rules. This function only changes the physical row parser: no right row
|
|
13716
|
+
* indicator is expected and the final module of each row must be dark.
|
|
13717
|
+
*
|
|
13718
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} matrix
|
|
13719
|
+
* @param {object} [options]
|
|
13720
|
+
* @param {number} [options.rowHeight]
|
|
13721
|
+
*/
|
|
13722
|
+
function decodeCompactPDF417(matrix, options = {}) {
|
|
13723
|
+
if (!matrix?.width || !matrix?.height) throw new FormatError('Compact PDF417: no matrix supplied');
|
|
13724
|
+
const rowHeight = options.rowHeight ?? matrix.compactPdf417?.rowHeight ?? 3;
|
|
13725
|
+
let geometry;
|
|
13726
|
+
try { geometry = compactPdf417Geometry(matrix.width, matrix.height, rowHeight); }
|
|
13727
|
+
catch (error) { throw new FormatError(error.message); }
|
|
13728
|
+
|
|
13729
|
+
const { rows, columns } = geometry;
|
|
13730
|
+
const all = [];
|
|
13731
|
+
const erasures = [];
|
|
13732
|
+
const leftIndicators = [];
|
|
13733
|
+
|
|
13734
|
+
for (let row = 0; row < rows; row++) {
|
|
13735
|
+
const y = row * rowHeight;
|
|
13736
|
+
if (patternString(matrix, y, 0, 17) !== COMPACT_PDF417_START_BITS) {
|
|
13737
|
+
throw new FormatError('Compact PDF417: missing start pattern');
|
|
13738
|
+
}
|
|
13739
|
+
const cluster = (row % 3) * 3;
|
|
13740
|
+
const leftDecoded = pdf417CodewordForPattern(bits(matrix, y, 17, 17));
|
|
13741
|
+
if (!leftDecoded || leftDecoded.cluster !== cluster) {
|
|
13742
|
+
throw new FormatError('Compact PDF417: invalid left row indicator');
|
|
13743
|
+
}
|
|
13744
|
+
leftIndicators.push(leftDecoded.codeword);
|
|
13745
|
+
|
|
13746
|
+
const dataStart = 34;
|
|
13747
|
+
const stopX = dataStart + columns * 17;
|
|
13748
|
+
if (!matrix.get(stopX, y)) throw new FormatError('Compact PDF417: missing reduced stop module');
|
|
13749
|
+
for (let column = 0; column < columns; column++) {
|
|
13750
|
+
const decoded = pdf417CodewordForPattern(bits(matrix, y, dataStart + column * 17, 17));
|
|
13751
|
+
if (!decoded || decoded.cluster !== cluster) {
|
|
13752
|
+
erasures.push(all.length);
|
|
13753
|
+
all.push(0);
|
|
13754
|
+
} else {
|
|
13755
|
+
all.push(decoded.codeword);
|
|
13756
|
+
}
|
|
13757
|
+
}
|
|
13758
|
+
}
|
|
13759
|
+
|
|
13760
|
+
const levels = compactPdf417MatchingLevels(leftIndicators, rows, columns);
|
|
13761
|
+
if (levels.length !== 1) throw new FormatError('Compact PDF417: row indicator mismatch');
|
|
13762
|
+
const level = levels[0];
|
|
13763
|
+
const eccLength = pdf417EccLength(level);
|
|
13764
|
+
if (all.length <= eccLength) throw new FormatError('Compact PDF417: insufficient codewords for ECC');
|
|
13765
|
+
|
|
13766
|
+
const corrected = all.slice();
|
|
13767
|
+
const corrections = pdf417CorrectErrors(corrected, level, erasures);
|
|
13768
|
+
const length = corrected[0];
|
|
13769
|
+
if (length < 1 || length > corrected.length - eccLength) {
|
|
13770
|
+
throw new FormatError('Compact PDF417: invalid symbol length descriptor');
|
|
13771
|
+
}
|
|
13772
|
+
const payload = corrected.slice(1, length);
|
|
13773
|
+
const decoded = decodePdf417CompactionDetailed(payload);
|
|
13774
|
+
return {
|
|
13775
|
+
...decoded,
|
|
13776
|
+
format: 'compact-pdf417',
|
|
13777
|
+
compact: true,
|
|
13778
|
+
codewords: corrected,
|
|
13779
|
+
rows,
|
|
13780
|
+
columns,
|
|
13781
|
+
eccLevel: level,
|
|
13782
|
+
rowHeight,
|
|
13783
|
+
corrections,
|
|
13784
|
+
};
|
|
13785
|
+
}
|
|
13786
|
+
|
|
13787
|
+
__exports.decodeCompactPDF417 = decodeCompactPDF417;
|
|
13788
|
+
};
|
|
13789
|
+
|
|
13790
|
+
__modules["compactpdf417/detector.js"] = function (__require, __exports) {
|
|
13791
|
+
/** Compact PDF417 detector for clean, integer-scaled rasters. @module compactpdf417/detector */
|
|
13792
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
13793
|
+
const { compactPdf417Width } = __require("compactpdf417/tables.js");
|
|
13794
|
+
const { decodeCompactPDF417 } = __require("compactpdf417/decoder.js");
|
|
13795
|
+
|
|
13796
|
+
function rotateClockwise(source) {
|
|
13797
|
+
const out = new BitMatrix(source.height, source.width);
|
|
13798
|
+
for (let y = 0; y < source.height; y++) {
|
|
13799
|
+
for (let x = 0; x < source.width; x++) {
|
|
13800
|
+
if (source.get(x, y)) out.set(source.height - 1 - y, x);
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
return out;
|
|
13804
|
+
}
|
|
13805
|
+
function cropAndDownsample(source, box, width, height, scale) {
|
|
13806
|
+
const out = new BitMatrix(width, height);
|
|
13807
|
+
for (let y = 0; y < height; y++) {
|
|
13808
|
+
for (let x = 0; x < width; x++) {
|
|
13809
|
+
let dark = 0;
|
|
13810
|
+
for (let dy = 0; dy < scale; dy++) {
|
|
13811
|
+
for (let dx = 0; dx < scale; dx++) {
|
|
13812
|
+
if (source.get(box.x + x * scale + dx, box.y + y * scale + dy)) dark++;
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
if (dark * 2 >= scale * scale) out.set(x, y);
|
|
13816
|
+
}
|
|
13817
|
+
}
|
|
13818
|
+
return out;
|
|
13819
|
+
}
|
|
13820
|
+
|
|
13821
|
+
function cornersFor(box) {
|
|
13822
|
+
return [
|
|
13823
|
+
{ x: box.x, y: box.y },
|
|
13824
|
+
{ x: box.x + box.width, y: box.y },
|
|
13825
|
+
{ x: box.x + box.width, y: box.y + box.height },
|
|
13826
|
+
{ x: box.x, y: box.y + box.height },
|
|
13827
|
+
];
|
|
13828
|
+
}
|
|
13829
|
+
|
|
13830
|
+
function mapCorners(corners, toOriginal) {
|
|
13831
|
+
return corners.map((point) => toOriginal(point));
|
|
13832
|
+
}
|
|
13833
|
+
|
|
13834
|
+
/**
|
|
13835
|
+
* Detect one Compact PDF417 symbol in a binarized raster.
|
|
13836
|
+
*
|
|
13837
|
+
* The clean-raster detector uses the dark bounding box and enumerates legal
|
|
13838
|
+
* compact widths, integer module scales, row counts and row heights. It is
|
|
13839
|
+
* deliberately conservative: arbitrary perspective, non-integer resampling,
|
|
13840
|
+
* grayscale thresholding and damaged stop bars belong to a future photo
|
|
13841
|
+
* detector and are not claimed here.
|
|
13842
|
+
*
|
|
13843
|
+
* @param {import('../core/bit-matrix.js').BitMatrix} binaryImage
|
|
13844
|
+
* @param {object} [options]
|
|
13845
|
+
* @param {number} [options.rowHeight] Restrict the row-height search.
|
|
13846
|
+
* @returns {object|null}
|
|
13847
|
+
*/
|
|
13848
|
+
function detectCompactPDF417(binaryImage, options = {}) {
|
|
13849
|
+
if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function') return null;
|
|
13850
|
+
|
|
13851
|
+
let oriented = binaryImage;
|
|
13852
|
+
let toOriginal = (point) => ({ x: point.x, y: point.y });
|
|
13853
|
+
for (let rotation = 0; rotation < 4; rotation++) {
|
|
13854
|
+
const bounds = oriented.getBounds();
|
|
13855
|
+
if (bounds) {
|
|
13856
|
+
for (let columns = 1; columns <= 30; columns++) {
|
|
13857
|
+
const width = compactPdf417Width(columns);
|
|
13858
|
+
const scale = bounds.width / width;
|
|
13859
|
+
if (!Number.isInteger(scale) || scale < 1) continue;
|
|
13860
|
+
const baseHeight = bounds.height / scale;
|
|
13861
|
+
const rowHeights = Number.isInteger(options.rowHeight)
|
|
13862
|
+
? [options.rowHeight]
|
|
13863
|
+
: Array.from({ length: 18 }, (_, index) => index + 3);
|
|
13864
|
+
for (const rowHeight of rowHeights) {
|
|
13865
|
+
if (!Number.isInteger(baseHeight / rowHeight)) continue;
|
|
13866
|
+
const rows = baseHeight / rowHeight;
|
|
13867
|
+
if (rows < 3 || rows > 90) continue;
|
|
13868
|
+
const matrix = cropAndDownsample(oriented, bounds, width, baseHeight, scale);
|
|
13869
|
+
try {
|
|
13870
|
+
const result = decodeCompactPDF417(matrix, { rowHeight });
|
|
13871
|
+
const corners = mapCorners(cornersFor(bounds), toOriginal);
|
|
13872
|
+
return {
|
|
13873
|
+
...result,
|
|
13874
|
+
matrix,
|
|
13875
|
+
corners,
|
|
13876
|
+
rotation: rotation * 90,
|
|
13877
|
+
moduleSize: scale,
|
|
13878
|
+
compact: true,
|
|
13879
|
+
};
|
|
13880
|
+
} catch {
|
|
13881
|
+
// Try another geometry; a standard PDF417 or random artwork should
|
|
13882
|
+
// never be accepted unless the compact decoder validates every row.
|
|
13883
|
+
}
|
|
13884
|
+
}
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13887
|
+
|
|
13888
|
+
const previous = oriented;
|
|
13889
|
+
const previousToOriginal = toOriginal;
|
|
13890
|
+
oriented = rotateClockwise(previous);
|
|
13891
|
+
toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
|
|
13892
|
+
}
|
|
13893
|
+
return null;
|
|
13894
|
+
}
|
|
13895
|
+
function detectAndDecodeCompactPDF417(binaryImage, options = {}) {
|
|
13896
|
+
return detectCompactPDF417(binaryImage, options);
|
|
13897
|
+
}
|
|
13898
|
+
|
|
13899
|
+
__exports.detectCompactPDF417 = detectCompactPDF417;
|
|
13900
|
+
__exports.detectAndDecodeCompactPDF417 = detectAndDecodeCompactPDF417;
|
|
13901
|
+
};
|
|
13902
|
+
|
|
13903
|
+
__modules["compactpdf417/index.js"] = function (__require, __exports) {
|
|
13904
|
+
/* Compact PDF417 public module exports. */
|
|
13905
|
+
const __reexport0 = __require("compactpdf417/tables.js"); __exports.COMPACT_PDF417_START = __reexport0.COMPACT_PDF417_START; __exports.COMPACT_PDF417_START_BITS = __reexport0.COMPACT_PDF417_START_BITS; __exports.COMPACT_PDF417_STOP_MODULES = __reexport0.COMPACT_PDF417_STOP_MODULES; __exports.COMPACT_PDF417_LIMITS = __reexport0.COMPACT_PDF417_LIMITS; __exports.compactPdf417Width = __reexport0.compactPdf417Width; __exports.compactPdf417Geometry = __reexport0.compactPdf417Geometry; __exports.compactPdf417Indicators = __reexport0.compactPdf417Indicators; __exports.compactPdf417MatchingLevels = __reexport0.compactPdf417MatchingLevels; __exports.validateCompactPdf417Options = __reexport0.validateCompactPdf417Options; __exports.validateCompactPdf417Tables = __reexport0.validateCompactPdf417Tables;
|
|
13906
|
+
const __reexport1 = __require("compactpdf417/encoder.js"); __exports.encodeCompactPDF417 = __reexport1.encodeCompactPDF417; __exports.compactPdf417Dimensions = __reexport1.compactPdf417Dimensions;
|
|
13907
|
+
const __reexport2 = __require("compactpdf417/decoder.js"); __exports.decodeCompactPDF417 = __reexport2.decodeCompactPDF417;
|
|
13908
|
+
const __reexport3 = __require("compactpdf417/detector.js"); __exports.detectCompactPDF417 = __reexport3.detectCompactPDF417; __exports.detectAndDecodeCompactPDF417 = __reexport3.detectAndDecodeCompactPDF417;
|
|
13909
|
+
|
|
13910
|
+
|
|
13911
|
+
};
|
|
13912
|
+
|
|
13913
|
+
__modules["databar/tables.js"] = function (__require, __exports) {
|
|
13914
|
+
/**
|
|
13915
|
+
* Static GS1 DataBar facts used by the GTIN compaction codec.
|
|
13916
|
+
*
|
|
13917
|
+
* The numbers in the DataBar-14 group tables are the public tables in
|
|
13918
|
+
* ISO/IEC 24724:2011, clauses 5.2.2 and 5.2.3. They describe values, not a
|
|
13919
|
+
* third-party implementation. Pattern construction deliberately belongs in
|
|
13920
|
+
* the renderer/encoder layer so this module stays a small, auditable contract.
|
|
13921
|
+
*
|
|
13922
|
+
* @module databar/tables
|
|
13923
|
+
*/
|
|
13924
|
+
|
|
13925
|
+
const group = (first, last, gsum, oddModules, evenModules, oddWidest, evenWidest, oddTotal, evenTotal) => Object.freeze({
|
|
13926
|
+
first, last, gsum, oddModules, evenModules, oddWidest, evenWidest, oddTotal, evenTotal,
|
|
13927
|
+
});
|
|
13928
|
+
|
|
13929
|
+
/** The four GTIN-only layouts sharing the DataBar-14 compaction rules. */
|
|
13930
|
+
const DATABAR14_VARIANTS = Object.freeze({
|
|
13931
|
+
omnidirectional: Object.freeze({ id: 'omnidirectional', rows: 1, modules: 96, checksumModulus: 79, pointOfSale: true }),
|
|
13932
|
+
truncated: Object.freeze({ id: 'truncated', rows: 1, modules: 96, checksumModulus: 79, pointOfSale: false }),
|
|
13933
|
+
stacked: Object.freeze({ id: 'stacked', rows: 2, modules: 50, checksumModulus: 79, pointOfSale: false }),
|
|
13934
|
+
'stacked-omnidirectional': Object.freeze({ id: 'stacked-omnidirectional', rows: 2, modules: 50, checksumModulus: 79, pointOfSale: true }),
|
|
13935
|
+
});
|
|
13936
|
+
|
|
13937
|
+
/** GS1 DataBar Limited is a distinct two-character symbology. */
|
|
13938
|
+
const DATABAR_LIMITED_VARIANT = Object.freeze({
|
|
13939
|
+
id: 'limited', rows: 1, checksumModulus: 89, pointOfSale: false,
|
|
13940
|
+
permittedIndicatorDigits: Object.freeze([0, 1]),
|
|
13941
|
+
});
|
|
13942
|
+
|
|
13943
|
+
/** (16,4) outside character groups, ISO/IEC 24724:2011 Table 1. */
|
|
13944
|
+
const DATABAR14_OUTSIDE_GROUPS = Object.freeze([
|
|
13945
|
+
group(0, 160, 0, 12, 4, 8, 1, 161, 1),
|
|
13946
|
+
group(161, 960, 161, 10, 6, 6, 3, 80, 10),
|
|
13947
|
+
group(961, 2014, 961, 8, 8, 4, 5, 31, 34),
|
|
13948
|
+
group(2015, 2714, 2015, 6, 10, 3, 6, 10, 70),
|
|
13949
|
+
group(2715, 2840, 2715, 4, 12, 1, 8, 1, 126),
|
|
13950
|
+
]);
|
|
13951
|
+
|
|
13952
|
+
/** (15,4) inside character groups, ISO/IEC 24724:2011 Table 2. */
|
|
13953
|
+
const DATABAR14_INSIDE_GROUPS = Object.freeze([
|
|
13954
|
+
group(0, 335, 0, 5, 10, 2, 7, 4, 84),
|
|
13955
|
+
group(336, 1035, 336, 7, 8, 4, 5, 20, 35),
|
|
13956
|
+
group(1036, 1515, 1036, 9, 6, 6, 3, 48, 10),
|
|
13957
|
+
group(1516, 1596, 1516, 11, 4, 8, 1, 81, 1),
|
|
13958
|
+
]);
|
|
13959
|
+
|
|
13960
|
+
/** Number of values carried by an inside (15,4) character. */
|
|
13961
|
+
const DATABAR14_INSIDE_RADIX = 1597;
|
|
13962
|
+
/** Number of values carried by one outside/inside character pair. */
|
|
13963
|
+
const DATABAR14_PAIR_RADIX = 4537077;
|
|
13964
|
+
/** Stand-alone and composite DataBar-14 payload domain, including linkage. */
|
|
13965
|
+
const DATABAR14_SYMBOL_LIMIT = 20000000000000n;
|
|
13966
|
+
|
|
13967
|
+
/** GS1 DataBar Limited divides its 13 data digits into two character values. */
|
|
13968
|
+
const DATABAR_LIMITED_PAIR_RADIX = 2013571;
|
|
13969
|
+
/** Offset that switches a DataBar Limited value from stand-alone to composite. */
|
|
13970
|
+
const DATABAR_LIMITED_LINKAGE_OFFSET = 2015133531096n;
|
|
13971
|
+
/** DataBar Limited carries GTIN values whose first digit is zero or one. */
|
|
13972
|
+
const DATABAR_LIMITED_DATA_LIMIT = 2000000000000n;
|
|
13973
|
+
|
|
13974
|
+
/** Locate the value group that an outside or inside character belongs to. */
|
|
13975
|
+
function dataBar14GroupFor(value, kind) {
|
|
13976
|
+
if (!Number.isInteger(value)) throw new TypeError('GS1 DataBar character value must be an integer');
|
|
13977
|
+
const groups = kind === 'outside' ? DATABAR14_OUTSIDE_GROUPS
|
|
13978
|
+
: kind === 'inside' ? DATABAR14_INSIDE_GROUPS : null;
|
|
13979
|
+
if (!groups) throw new RangeError(`Unknown GS1 DataBar character kind "${kind}"`);
|
|
13980
|
+
const found = groups.find((entry) => value >= entry.first && value <= entry.last);
|
|
13981
|
+
if (!found) throw new RangeError(`GS1 DataBar ${kind} character value ${value} is out of range`);
|
|
13982
|
+
return found;
|
|
13983
|
+
}
|
|
13984
|
+
/** Check the independently represented table identities. */
|
|
13985
|
+
function validateDataBarTables() {
|
|
13986
|
+
const problems = [];
|
|
13987
|
+
const validateGroups = (name, groups, expectedLast) => {
|
|
13988
|
+
let next = 0;
|
|
13989
|
+
for (const entry of groups) {
|
|
13990
|
+
if (entry.first !== next) problems.push(`${name}: gap before ${entry.first}`);
|
|
13991
|
+
if (entry.last < entry.first) problems.push(`${name}: inverted range ${entry.first}-${entry.last}`);
|
|
13992
|
+
if (entry.oddTotal * entry.evenTotal !== entry.last - entry.first + 1) {
|
|
13993
|
+
problems.push(`${name}: combination count mismatch at ${entry.first}`);
|
|
13994
|
+
}
|
|
13995
|
+
next = entry.last + 1;
|
|
13996
|
+
}
|
|
13997
|
+
if (next - 1 !== expectedLast) problems.push(`${name}: final value is ${next - 1}, expected ${expectedLast}`);
|
|
13998
|
+
};
|
|
13999
|
+
validateGroups('outside', DATABAR14_OUTSIDE_GROUPS, 2840);
|
|
14000
|
+
validateGroups('inside', DATABAR14_INSIDE_GROUPS, 1596);
|
|
14001
|
+
if (DATABAR14_PAIR_RADIX !== 2841 * DATABAR14_INSIDE_RADIX) problems.push('pair radix mismatch');
|
|
14002
|
+
if (DATABAR14_SYMBOL_LIMIT !== 2n * 10000000000000n) problems.push('symbol limit mismatch');
|
|
14003
|
+
if (DATABAR_LIMITED_LINKAGE_OFFSET !== BigInt(DATABAR_LIMITED_PAIR_RADIX) * 1000776n) {
|
|
14004
|
+
problems.push('limited linkage offset mismatch');
|
|
14005
|
+
}
|
|
14006
|
+
return problems;
|
|
14007
|
+
}
|
|
14008
|
+
|
|
14009
|
+
__exports.DATABAR14_VARIANTS = DATABAR14_VARIANTS;
|
|
14010
|
+
__exports.DATABAR_LIMITED_VARIANT = DATABAR_LIMITED_VARIANT;
|
|
14011
|
+
__exports.DATABAR14_OUTSIDE_GROUPS = DATABAR14_OUTSIDE_GROUPS;
|
|
14012
|
+
__exports.DATABAR14_INSIDE_GROUPS = DATABAR14_INSIDE_GROUPS;
|
|
14013
|
+
__exports.DATABAR14_INSIDE_RADIX = DATABAR14_INSIDE_RADIX;
|
|
14014
|
+
__exports.DATABAR14_PAIR_RADIX = DATABAR14_PAIR_RADIX;
|
|
14015
|
+
__exports.DATABAR14_SYMBOL_LIMIT = DATABAR14_SYMBOL_LIMIT;
|
|
14016
|
+
__exports.DATABAR_LIMITED_PAIR_RADIX = DATABAR_LIMITED_PAIR_RADIX;
|
|
14017
|
+
__exports.DATABAR_LIMITED_LINKAGE_OFFSET = DATABAR_LIMITED_LINKAGE_OFFSET;
|
|
14018
|
+
__exports.DATABAR_LIMITED_DATA_LIMIT = DATABAR_LIMITED_DATA_LIMIT;
|
|
14019
|
+
__exports.dataBar14GroupFor = dataBar14GroupFor;
|
|
14020
|
+
__exports.validateDataBarTables = validateDataBarTables;
|
|
14021
|
+
};
|
|
14022
|
+
|
|
14023
|
+
__modules["databar/codec.js"] = function (__require, __exports) {
|
|
14024
|
+
/**
|
|
14025
|
+
* GTIN validation and numeric compaction for GS1 DataBar GTIN-only symbols.
|
|
14026
|
+
*
|
|
14027
|
+
* This is intentionally not a bar-pattern encoder. It emits the exact symbol
|
|
14028
|
+
* character values required by ISO/IEC 24724:2011; a later encoder can turn
|
|
14029
|
+
* those values into widths without reinterpreting a GTIN or linkage flag.
|
|
14030
|
+
*
|
|
14031
|
+
* @module databar/codec
|
|
14032
|
+
*/
|
|
14033
|
+
const { DATABAR14_INSIDE_RADIX, DATABAR14_PAIR_RADIX, DATABAR14_SYMBOL_LIMIT, DATABAR_LIMITED_DATA_LIMIT, DATABAR_LIMITED_LINKAGE_OFFSET, DATABAR_LIMITED_PAIR_RADIX } = __require("databar/tables.js");
|
|
14034
|
+
|
|
14035
|
+
const GTIN_LENGTHS = new Set([8, 12, 13, 14]);
|
|
14036
|
+
|
|
14037
|
+
function digits(value, label) {
|
|
14038
|
+
const text = String(value);
|
|
14039
|
+
if (!/^\d+$/.test(text)) throw new TypeError(`${label} must contain only decimal digits`);
|
|
14040
|
+
return text;
|
|
14041
|
+
}
|
|
14042
|
+
|
|
14043
|
+
function asCharacterValue(value, label, maximum) {
|
|
14044
|
+
if (!Number.isInteger(value) || value < 0 || value > maximum) {
|
|
14045
|
+
throw new RangeError(`${label} must be an integer from 0 to ${maximum}`);
|
|
14046
|
+
}
|
|
14047
|
+
return value;
|
|
14048
|
+
}
|
|
14049
|
+
|
|
14050
|
+
/** Calculate the GS1 modulo-10 check digit for a GTIN body. */
|
|
14051
|
+
function gtinCheckDigit(body) {
|
|
14052
|
+
const text = digits(body, 'GTIN body');
|
|
14053
|
+
if (text.length < 1 || text.length > 13) throw new RangeError('GTIN body must contain 1 to 13 digits');
|
|
14054
|
+
let sum = 0;
|
|
14055
|
+
for (let i = text.length - 1, weight = 3; i >= 0; i--, weight = weight === 3 ? 1 : 3) {
|
|
14056
|
+
sum += Number(text[i]) * weight;
|
|
14057
|
+
}
|
|
14058
|
+
return String((10 - (sum % 10)) % 10);
|
|
14059
|
+
}
|
|
14060
|
+
|
|
14061
|
+
/** Return a checked 14-digit GTIN, preserving only its standardized digits. */
|
|
14062
|
+
function normalizeGTIN(value) {
|
|
14063
|
+
const input = digits(value, 'GTIN');
|
|
14064
|
+
if (!GTIN_LENGTHS.has(input.length)) {
|
|
14065
|
+
throw new RangeError('GTIN must contain 8, 12, 13, or 14 digits including its check digit');
|
|
14066
|
+
}
|
|
14067
|
+
if (gtinCheckDigit(input.slice(0, -1)) !== input.at(-1)) {
|
|
14068
|
+
throw new RangeError(`GTIN check digit is invalid for ${input}`);
|
|
14069
|
+
}
|
|
14070
|
+
return input.padStart(14, '0');
|
|
14071
|
+
}
|
|
14072
|
+
|
|
14073
|
+
/** Build a checked GTIN-14 from its thirteen-digit data body. */
|
|
14074
|
+
function makeGTIN14(body) {
|
|
14075
|
+
const input = digits(body, 'GTIN-14 body');
|
|
14076
|
+
if (input.length > 13) throw new RangeError('GTIN-14 body must contain at most 13 digits');
|
|
14077
|
+
const padded = input.padStart(13, '0');
|
|
14078
|
+
return padded + gtinCheckDigit(padded);
|
|
14079
|
+
}
|
|
14080
|
+
|
|
14081
|
+
function gtinFromSymbolValue(symbolValue) {
|
|
14082
|
+
const value = BigInt(symbolValue);
|
|
14083
|
+
if (value < 0n || value >= DATABAR14_SYMBOL_LIMIT) throw new RangeError('GS1 DataBar-14 symbol value is out of range');
|
|
14084
|
+
const linkage = value >= 10000000000000n;
|
|
14085
|
+
const body = (linkage ? value - 10000000000000n : value).toString().padStart(13, '0');
|
|
14086
|
+
return { linkage, gtin: body + gtinCheckDigit(body) };
|
|
14087
|
+
}
|
|
14088
|
+
|
|
14089
|
+
/**
|
|
14090
|
+
* Compact a GTIN into the four values shared by Omnidirectional, Truncated,
|
|
14091
|
+
* Stacked and Stacked Omnidirectional. The `physicalCharacters` sequence is
|
|
14092
|
+
* left-to-right on a linear DataBar-14 row: 1, 2, 4, 3.
|
|
14093
|
+
*/
|
|
14094
|
+
function encodeDataBar14GTIN(value, options = {}) {
|
|
14095
|
+
const gtin = normalizeGTIN(value);
|
|
14096
|
+
const linkage = options.linkage === true;
|
|
14097
|
+
if (options.linkage !== undefined && typeof options.linkage !== 'boolean') {
|
|
14098
|
+
throw new TypeError('GS1 DataBar linkage must be a boolean');
|
|
14099
|
+
}
|
|
14100
|
+
const dataBody = gtin.slice(0, -1);
|
|
14101
|
+
const symbolValue = BigInt(dataBody) + (linkage ? 10000000000000n : 0n);
|
|
14102
|
+
const leftPair = symbolValue / BigInt(DATABAR14_PAIR_RADIX);
|
|
14103
|
+
const rightPair = symbolValue % BigInt(DATABAR14_PAIR_RADIX);
|
|
14104
|
+
const outerLeft = Number(leftPair / BigInt(DATABAR14_INSIDE_RADIX));
|
|
14105
|
+
const innerLeft = Number(leftPair % BigInt(DATABAR14_INSIDE_RADIX));
|
|
14106
|
+
const outerRight = Number(rightPair / BigInt(DATABAR14_INSIDE_RADIX));
|
|
14107
|
+
const innerRight = Number(rightPair % BigInt(DATABAR14_INSIDE_RADIX));
|
|
14108
|
+
|
|
14109
|
+
return Object.freeze({
|
|
14110
|
+
gtin, linkage, symbolValue, leftPair, rightPair,
|
|
14111
|
+
logicalCharacters: Object.freeze({ outerLeft, innerLeft, outerRight, innerRight }),
|
|
14112
|
+
physicalCharacters: Object.freeze([outerLeft, innerLeft, innerRight, outerRight]),
|
|
14113
|
+
});
|
|
14114
|
+
}
|
|
14115
|
+
|
|
14116
|
+
/** Reconstruct and validate a GTIN from the four DataBar-14 character values. */
|
|
14117
|
+
function decodeDataBar14GTIN(values) {
|
|
14118
|
+
if (values === null || typeof values !== 'object') throw new TypeError('GS1 DataBar-14 characters must be an object');
|
|
14119
|
+
const outerLeft = asCharacterValue(values.outerLeft, 'outerLeft', 2840);
|
|
14120
|
+
const innerLeft = asCharacterValue(values.innerLeft, 'innerLeft', 1596);
|
|
14121
|
+
const outerRight = asCharacterValue(values.outerRight, 'outerRight', 2840);
|
|
14122
|
+
const innerRight = asCharacterValue(values.innerRight, 'innerRight', 1596);
|
|
14123
|
+
const leftPair = BigInt(outerLeft) * BigInt(DATABAR14_INSIDE_RADIX) + BigInt(innerLeft);
|
|
14124
|
+
const rightPair = BigInt(outerRight) * BigInt(DATABAR14_INSIDE_RADIX) + BigInt(innerRight);
|
|
14125
|
+
return Object.freeze({
|
|
14126
|
+
...gtinFromSymbolValue(leftPair * BigInt(DATABAR14_PAIR_RADIX) + rightPair),
|
|
14127
|
+
leftPair, rightPair,
|
|
14128
|
+
});
|
|
14129
|
+
}
|
|
14130
|
+
|
|
14131
|
+
/** Compact a DataBar Limited-eligible GTIN into its two data character values. */
|
|
14132
|
+
function encodeDataBarLimitedGTIN(value, options = {}) {
|
|
14133
|
+
const gtin = normalizeGTIN(value);
|
|
14134
|
+
const linkage = options.linkage === true;
|
|
14135
|
+
if (options.linkage !== undefined && typeof options.linkage !== 'boolean') {
|
|
14136
|
+
throw new TypeError('GS1 DataBar linkage must be a boolean');
|
|
14137
|
+
}
|
|
14138
|
+
const dataBody = BigInt(gtin.slice(0, -1));
|
|
14139
|
+
if (dataBody >= DATABAR_LIMITED_DATA_LIMIT) {
|
|
14140
|
+
throw new RangeError('GS1 DataBar Limited requires a GTIN whose indicator digit is 0 or 1');
|
|
14141
|
+
}
|
|
14142
|
+
const symbolValue = dataBody + (linkage ? DATABAR_LIMITED_LINKAGE_OFFSET : 0n);
|
|
14143
|
+
const left = Number(symbolValue / BigInt(DATABAR_LIMITED_PAIR_RADIX));
|
|
14144
|
+
const right = Number(symbolValue % BigInt(DATABAR_LIMITED_PAIR_RADIX));
|
|
14145
|
+
return Object.freeze({ gtin, linkage, symbolValue, left, right });
|
|
14146
|
+
}
|
|
14147
|
+
|
|
14148
|
+
/** Reconstruct and validate a GTIN from DataBar Limited data character values. */
|
|
14149
|
+
function decodeDataBarLimitedGTIN(values) {
|
|
14150
|
+
if (values === null || typeof values !== 'object') throw new TypeError('GS1 DataBar Limited characters must be an object');
|
|
14151
|
+
const left = asCharacterValue(values.left, 'left', 1994036);
|
|
14152
|
+
const right = asCharacterValue(values.right, 'right', DATABAR_LIMITED_PAIR_RADIX - 1);
|
|
14153
|
+
let symbolValue = BigInt(left) * BigInt(DATABAR_LIMITED_PAIR_RADIX) + BigInt(right);
|
|
14154
|
+
const linkage = symbolValue >= DATABAR_LIMITED_LINKAGE_OFFSET;
|
|
14155
|
+
if (linkage) symbolValue -= DATABAR_LIMITED_LINKAGE_OFFSET;
|
|
14156
|
+
if (symbolValue < 0n || symbolValue >= DATABAR_LIMITED_DATA_LIMIT) {
|
|
14157
|
+
throw new RangeError('GS1 DataBar Limited character values do not encode a permitted GTIN');
|
|
14158
|
+
}
|
|
14159
|
+
const body = symbolValue.toString().padStart(13, '0');
|
|
14160
|
+
return Object.freeze({ linkage, gtin: body + gtinCheckDigit(body), symbolValue: BigInt(left) * BigInt(DATABAR_LIMITED_PAIR_RADIX) + BigInt(right) });
|
|
14161
|
+
}
|
|
14162
|
+
|
|
14163
|
+
/** GS1 transmitted form for the GTIN-only DataBar variants. */
|
|
14164
|
+
function dataBarGtinTransmission(value) {
|
|
14165
|
+
return `]e001${normalizeGTIN(value)}`;
|
|
14166
|
+
}
|
|
14167
|
+
|
|
14168
|
+
__exports.gtinCheckDigit = gtinCheckDigit;
|
|
14169
|
+
__exports.normalizeGTIN = normalizeGTIN;
|
|
14170
|
+
__exports.makeGTIN14 = makeGTIN14;
|
|
14171
|
+
__exports.encodeDataBar14GTIN = encodeDataBar14GTIN;
|
|
14172
|
+
__exports.decodeDataBar14GTIN = decodeDataBar14GTIN;
|
|
14173
|
+
__exports.encodeDataBarLimitedGTIN = encodeDataBarLimitedGTIN;
|
|
14174
|
+
__exports.decodeDataBarLimitedGTIN = decodeDataBarLimitedGTIN;
|
|
14175
|
+
__exports.dataBarGtinTransmission = dataBarGtinTransmission;
|
|
14176
|
+
};
|
|
14177
|
+
|
|
14178
|
+
__modules["databar/gs1.js"] = function (__require, __exports) {
|
|
14179
|
+
/** GS1 element-string codec used by GS1 DataBar Expanded. @module databar/gs1 */
|
|
14180
|
+
const { EncodeError, FormatError } = __require("core/errors.js");
|
|
14181
|
+
const GS1_SEPARATOR = '\x1d';
|
|
14182
|
+
|
|
14183
|
+
const EXACT = new Map([
|
|
14184
|
+
['00', [18, true]], ['01', [14, true]], ['02', [14, true]],
|
|
14185
|
+
['10', [20, false]], ['11', [6, true]], ['12', [6, true]],
|
|
14186
|
+
['13', [6, true]], ['15', [6, true]], ['16', [6, true]],
|
|
14187
|
+
['17', [6, true]], ['20', [2, true]], ['21', [20, false]],
|
|
14188
|
+
['22', [29, false]], ['30', [8, false]], ['37', [8, false]],
|
|
14189
|
+
['240', [30, false]], ['241', [30, false]], ['242', [6, false]],
|
|
14190
|
+
['243', [20, false]], ['250', [30, false]], ['251', [30, false]],
|
|
14191
|
+
['253', [30, false]], ['254', [20, false]], ['255', [13, true]],
|
|
14192
|
+
['400', [30, false]], ['401', [30, false]], ['402', [17, true]],
|
|
14193
|
+
['403', [30, false]], ['410', [13, true]], ['411', [13, true]],
|
|
14194
|
+
['412', [13, true]], ['413', [13, true]], ['414', [13, true]],
|
|
14195
|
+
['415', [13, true]], ['416', [13, true]], ['417', [13, true]],
|
|
14196
|
+
['420', [20, false]], ['421', [15, false]], ['422', [3, true]],
|
|
14197
|
+
['423', [15, false]], ['424', [3, true]], ['425', [3, true]],
|
|
14198
|
+
['426', [3, true]], ['427', [3, false]],
|
|
14199
|
+
['7001', [13, true]], ['7002', [30, false]], ['7003', [10, true]],
|
|
14200
|
+
['7004', [4, false]], ['7005', [12, false]], ['7006', [6, true]],
|
|
14201
|
+
['7007', [12, false]], ['7008', [3, false]], ['7009', [10, false]],
|
|
14202
|
+
['7010', [2, false]], ['7020', [20, false]], ['7021', [20, false]],
|
|
14203
|
+
['7022', [20, false]], ['710', [20, false]], ['711', [20, false]],
|
|
14204
|
+
['712', [20, false]], ['713', [20, false]], ['714', [20, false]],
|
|
14205
|
+
['715', [20, false]],
|
|
14206
|
+
['8001', [14, true]], ['8002', [20, false]], ['8003', [30, false]],
|
|
14207
|
+
['8004', [30, false]], ['8005', [6, true]], ['8006', [18, true]],
|
|
14208
|
+
['8007', [34, false]], ['8008', [12, false]], ['8009', [50, false]],
|
|
14209
|
+
['8010', [30, false]], ['8011', [12, false]], ['8012', [20, false]],
|
|
14210
|
+
['8013', [30, false]], ['8017', [18, true]], ['8018', [18, true]],
|
|
14211
|
+
['8019', [10, false]], ['8020', [25, false]], ['8026', [18, true]],
|
|
14212
|
+
['8110', [70, false]], ['8111', [4, true]], ['8112', [70, false]],
|
|
14213
|
+
['8200', [70, false]],
|
|
14214
|
+
]);
|
|
14215
|
+
|
|
14216
|
+
function rangedAI(ai) {
|
|
14217
|
+
if (/^31[0-6]\d$/.test(ai) || /^32[0-7]\d$/.test(ai) ||
|
|
14218
|
+
/^33[0-7]\d$/.test(ai) || /^34[0-9]\d$/.test(ai) ||
|
|
14219
|
+
/^35[0-7]\d$/.test(ai) || /^36[0-9]\d$/.test(ai)) return [6, true];
|
|
14220
|
+
if (/^390\d$/.test(ai) || /^392\d$/.test(ai)) return [15, false];
|
|
14221
|
+
if (/^391\d$/.test(ai) || /^393\d$/.test(ai)) return [18, false];
|
|
14222
|
+
if (/^394\d$/.test(ai)) return [4, true];
|
|
14223
|
+
if (/^395\d$/.test(ai)) return [6, true];
|
|
14224
|
+
if (/^703\d$/.test(ai) || /^723\d$/.test(ai)) return [30, false];
|
|
14225
|
+
return undefined;
|
|
14226
|
+
}
|
|
14227
|
+
function gs1AIInfo(ai) {
|
|
14228
|
+
if (typeof ai !== 'string' || !/^\d{2,4}$/.test(ai)) return undefined;
|
|
14229
|
+
const spec = EXACT.get(ai) || rangedAI(ai);
|
|
14230
|
+
if (!spec) return undefined;
|
|
14231
|
+
return { ai, length: spec[0], fixed: spec[1] };
|
|
14232
|
+
}
|
|
14233
|
+
|
|
14234
|
+
function assertCharacters(value, label, ErrorType) {
|
|
14235
|
+
if (/[^\x20-\x7e]/.test(value)) {
|
|
14236
|
+
throw new ErrorType(`GS1: ${label} contains a character outside the GS1 element-string character set`);
|
|
14237
|
+
}
|
|
14238
|
+
}
|
|
14239
|
+
|
|
14240
|
+
function validateElement(ai, value, ErrorType) {
|
|
14241
|
+
const info = gs1AIInfo(ai);
|
|
14242
|
+
if (!info) throw new ErrorType(`GS1: unsupported or invalid Application Identifier ${ai}`);
|
|
14243
|
+
if (value.length === 0) throw new ErrorType(`GS1: AI ${ai} must not be empty`);
|
|
14244
|
+
if (info.fixed ? value.length !== info.length : value.length > info.length) {
|
|
14245
|
+
const expectation = info.fixed ? `exactly ${info.length}` : `at most ${info.length}`;
|
|
14246
|
+
throw new ErrorType(`GS1: AI ${ai} requires ${expectation} characters`);
|
|
14247
|
+
}
|
|
14248
|
+
assertCharacters(value, `AI ${ai}`, ErrorType);
|
|
14249
|
+
return { ai, value, fixed: info.fixed };
|
|
14250
|
+
}
|
|
14251
|
+
function parseGS1ElementString(input) {
|
|
14252
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
14253
|
+
throw new EncodeError('GS1: element string must be a non-empty string');
|
|
14254
|
+
}
|
|
14255
|
+
const elements = [];
|
|
14256
|
+
let offset = 0;
|
|
14257
|
+
while (offset < input.length) {
|
|
14258
|
+
const match = /^\((\d{2,4})\)/.exec(input.slice(offset));
|
|
14259
|
+
if (!match) throw new EncodeError(`GS1: expected an Application Identifier at offset ${offset}`);
|
|
14260
|
+
const ai = match[1];
|
|
14261
|
+
offset += match[0].length;
|
|
14262
|
+
const next = input.indexOf('(', offset);
|
|
14263
|
+
const end = next < 0 ? input.length : next;
|
|
14264
|
+
elements.push(validateElement(ai, input.slice(offset, end), EncodeError));
|
|
14265
|
+
offset = end;
|
|
14266
|
+
}
|
|
14267
|
+
return elements;
|
|
14268
|
+
}
|
|
14269
|
+
function encodeGS1ElementString(input) {
|
|
14270
|
+
const elements = Array.isArray(input)
|
|
14271
|
+
? input.map(({ ai, value }) => validateElement(String(ai), String(value), EncodeError))
|
|
14272
|
+
: parseGS1ElementString(input);
|
|
14273
|
+
return elements.map((element, index) =>
|
|
14274
|
+
`${element.ai}${element.value}${!element.fixed && index + 1 < elements.length ? GS1_SEPARATOR : ''}`
|
|
14275
|
+
).join('');
|
|
14276
|
+
}
|
|
14277
|
+
|
|
14278
|
+
function matchAIAt(data, offset) {
|
|
14279
|
+
for (const length of [4, 3, 2]) {
|
|
14280
|
+
const ai = data.slice(offset, offset + length);
|
|
14281
|
+
if (gs1AIInfo(ai)) return ai;
|
|
14282
|
+
}
|
|
14283
|
+
return undefined;
|
|
14284
|
+
}
|
|
14285
|
+
function decodeGS1ElementString(data) {
|
|
14286
|
+
if (typeof data !== 'string' || data.length === 0) {
|
|
14287
|
+
throw new FormatError('GS1: encoded data must be a non-empty string');
|
|
14288
|
+
}
|
|
14289
|
+
const elements = [];
|
|
14290
|
+
let offset = 0;
|
|
14291
|
+
while (offset < data.length) {
|
|
14292
|
+
if (data[offset] === GS1_SEPARATOR) {
|
|
14293
|
+
throw new FormatError(`GS1: unexpected separator at offset ${offset}`);
|
|
14294
|
+
}
|
|
14295
|
+
const ai = matchAIAt(data, offset);
|
|
14296
|
+
if (!ai) throw new FormatError(`GS1: unknown Application Identifier at offset ${offset}`);
|
|
14297
|
+
offset += ai.length;
|
|
14298
|
+
const info = gs1AIInfo(ai);
|
|
14299
|
+
let value;
|
|
14300
|
+
if (info.fixed) {
|
|
14301
|
+
value = data.slice(offset, offset + info.length);
|
|
14302
|
+
if (value.length !== info.length || value.includes(GS1_SEPARATOR)) {
|
|
14303
|
+
throw new FormatError(`GS1: truncated fixed-length AI ${ai}`);
|
|
14304
|
+
}
|
|
14305
|
+
offset += info.length;
|
|
14306
|
+
} else {
|
|
14307
|
+
const separator = data.indexOf(GS1_SEPARATOR, offset);
|
|
14308
|
+
const end = separator < 0 ? data.length : separator;
|
|
14309
|
+
value = data.slice(offset, end);
|
|
14310
|
+
offset = separator < 0 ? data.length : separator + 1;
|
|
14311
|
+
}
|
|
14312
|
+
elements.push(validateElement(ai, value, FormatError));
|
|
14313
|
+
}
|
|
14314
|
+
return elements;
|
|
14315
|
+
}
|
|
14316
|
+
function formatGS1Elements(elements) {
|
|
14317
|
+
return elements.map(({ ai, value }) => `(${ai})${value}`).join('');
|
|
14318
|
+
}
|
|
14319
|
+
|
|
14320
|
+
__exports.GS1_SEPARATOR = GS1_SEPARATOR;
|
|
14321
|
+
__exports.gs1AIInfo = gs1AIInfo;
|
|
14322
|
+
__exports.parseGS1ElementString = parseGS1ElementString;
|
|
14323
|
+
__exports.encodeGS1ElementString = encodeGS1ElementString;
|
|
14324
|
+
__exports.decodeGS1ElementString = decodeGS1ElementString;
|
|
14325
|
+
__exports.formatGS1Elements = formatGS1Elements;
|
|
14326
|
+
};
|
|
14327
|
+
|
|
14328
|
+
__modules["databar/patterns.js"] = function (__require, __exports) {
|
|
14329
|
+
/** Mathematical width construction for GS1 DataBar characters. @module databar/patterns */
|
|
14330
|
+
const { dataBar14GroupFor } = __require("databar/tables.js");
|
|
14331
|
+
|
|
14332
|
+
function combinations(n, r) {
|
|
14333
|
+
if (n < r || r < 0) return 0;
|
|
14334
|
+
if (r === 0 || n === r) return 1;
|
|
14335
|
+
let result = 1;
|
|
14336
|
+
const k = Math.min(r, n - r);
|
|
14337
|
+
for (let i = 1; i <= k; i++) result = result * (n - k + i) / i;
|
|
14338
|
+
return result;
|
|
14339
|
+
}
|
|
14340
|
+
|
|
14341
|
+
/**
|
|
14342
|
+
* Unrank one constrained positive composition.
|
|
14343
|
+
*
|
|
14344
|
+
* The rank is the GS1 DataBar symbol-character value inside its group. This
|
|
14345
|
+
* implementation follows the combinatorial definition directly: it counts
|
|
14346
|
+
* every remaining admissible suffix rather than storing third-party patterns.
|
|
14347
|
+
*/
|
|
14348
|
+
function dataBarWidths(rank, modules, elements, maximumWidth, noNarrow) {
|
|
14349
|
+
if (!Number.isInteger(rank) || rank < 0) throw new RangeError('GS1 DataBar width rank must be non-negative');
|
|
14350
|
+
const widths = new Array(elements).fill(0);
|
|
14351
|
+
let remaining = modules;
|
|
14352
|
+
let narrowMask = 0;
|
|
14353
|
+
|
|
14354
|
+
for (let bar = 0; bar < elements - 1; bar++) {
|
|
14355
|
+
let count = 0;
|
|
14356
|
+
for (let width = 1; ; width++) {
|
|
14357
|
+
narrowMask |= width === 1 ? 1 << bar : 0;
|
|
14358
|
+
count = combinations(remaining - width - 1, elements - bar - 2);
|
|
14359
|
+
|
|
14360
|
+
if (noNarrow && narrowMask === 0 && remaining - width - (elements - bar - 1) >= elements - bar - 1) {
|
|
14361
|
+
count -= combinations(remaining - width - (elements - bar), elements - bar - 2);
|
|
14362
|
+
}
|
|
14363
|
+
|
|
14364
|
+
if (elements - bar - 1 > 1) {
|
|
14365
|
+
let tooWide = 0;
|
|
14366
|
+
for (let last = remaining - width - (elements - bar - 2); last > maximumWidth; last--) {
|
|
14367
|
+
tooWide += combinations(remaining - width - last - 1, elements - bar - 3);
|
|
14368
|
+
}
|
|
14369
|
+
count -= tooWide * (elements - 1 - bar);
|
|
14370
|
+
} else if (remaining - width > maximumWidth) {
|
|
14371
|
+
count--;
|
|
14372
|
+
}
|
|
14373
|
+
|
|
14374
|
+
if (rank < count) {
|
|
14375
|
+
widths[bar] = width;
|
|
14376
|
+
remaining -= width;
|
|
14377
|
+
break;
|
|
14378
|
+
}
|
|
14379
|
+
rank -= count;
|
|
14380
|
+
narrowMask &= ~(1 << bar);
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14383
|
+
widths[elements - 1] = remaining;
|
|
14384
|
+
if (rank !== 0 || widths.some((width) => width < 1 || width > maximumWidth)) {
|
|
14385
|
+
throw new RangeError('GS1 DataBar width rank exceeds its character group');
|
|
14386
|
+
}
|
|
14387
|
+
return widths;
|
|
14388
|
+
}
|
|
14389
|
+
|
|
14390
|
+
/** Convert one DataBar-14 character value into its eight alternating widths. */
|
|
14391
|
+
function dataBar14CharacterWidths(value, kind) {
|
|
14392
|
+
const group = dataBar14GroupFor(value, kind);
|
|
14393
|
+
const offset = value - group.gsum;
|
|
14394
|
+
const outside = kind === 'outside';
|
|
14395
|
+
const oddRank = outside ? Math.floor(offset / group.evenTotal) : offset % group.oddTotal;
|
|
14396
|
+
const evenRank = outside ? offset % group.evenTotal : Math.floor(offset / group.oddTotal);
|
|
14397
|
+
const odd = dataBarWidths(oddRank, group.oddModules, 4, group.oddWidest, !outside);
|
|
14398
|
+
const even = dataBarWidths(evenRank, group.evenModules, 4, group.evenWidest, outside);
|
|
14399
|
+
const result = [];
|
|
14400
|
+
for (let i = 0; i < 4; i++) result.push(odd[i], even[i]);
|
|
14401
|
+
return result;
|
|
14402
|
+
}
|
|
14403
|
+
|
|
14404
|
+
const inverseCharacterMaps = new Map();
|
|
14405
|
+
|
|
14406
|
+
/** Recover a character value from its canonical eight-width representation. */
|
|
14407
|
+
function dataBar14ValueForWidths(widths, kind) {
|
|
14408
|
+
if (!Array.isArray(widths) || widths.length !== 8 || widths.some((width) => !Number.isInteger(width) || width < 1)) {
|
|
14409
|
+
throw new TypeError('GS1 DataBar character widths must contain eight positive integers');
|
|
14410
|
+
}
|
|
14411
|
+
if (kind !== 'outside' && kind !== 'inside') throw new RangeError(`Unknown GS1 DataBar character kind "${kind}"`);
|
|
14412
|
+
let inverse = inverseCharacterMaps.get(kind);
|
|
14413
|
+
if (!inverse) {
|
|
14414
|
+
inverse = new Map();
|
|
14415
|
+
const maximum = kind === 'outside' ? 2840 : 1596;
|
|
14416
|
+
for (let value = 0; value <= maximum; value++) {
|
|
14417
|
+
inverse.set(dataBar14CharacterWidths(value, kind).join(','), value);
|
|
14418
|
+
}
|
|
14419
|
+
inverseCharacterMaps.set(kind, inverse);
|
|
14420
|
+
}
|
|
14421
|
+
const value = inverse.get(widths.join(','));
|
|
14422
|
+
if (value === undefined) throw new RangeError(`Invalid GS1 DataBar ${kind} character widths`);
|
|
14423
|
+
return value;
|
|
14424
|
+
}
|
|
14425
|
+
|
|
14426
|
+
/** Nine finder patterns, expressed as normative five-element widths. */
|
|
14427
|
+
const DATABAR14_FINDERS = Object.freeze([
|
|
14428
|
+
Object.freeze([3, 8, 2, 1, 1]), Object.freeze([3, 5, 5, 1, 1]),
|
|
14429
|
+
Object.freeze([3, 3, 7, 1, 1]), Object.freeze([3, 1, 9, 1, 1]),
|
|
14430
|
+
Object.freeze([2, 7, 4, 1, 1]), Object.freeze([2, 5, 6, 1, 1]),
|
|
14431
|
+
Object.freeze([2, 3, 8, 1, 1]), Object.freeze([1, 5, 7, 1, 1]),
|
|
14432
|
+
Object.freeze([1, 3, 9, 1, 1]),
|
|
14433
|
+
]);
|
|
14434
|
+
|
|
14435
|
+
/** Weight sequence generated as powers of three modulo 79. */
|
|
14436
|
+
const DATABAR14_CHECKSUM_WEIGHTS = Object.freeze(Array.from({ length: 32 }, (_, index) => {
|
|
14437
|
+
let value = 1;
|
|
14438
|
+
for (let i = 0; i < index; i++) value = (value * 3) % 79;
|
|
14439
|
+
return value;
|
|
14440
|
+
}));
|
|
14441
|
+
|
|
14442
|
+
__exports.dataBarWidths = dataBarWidths;
|
|
14443
|
+
__exports.dataBar14CharacterWidths = dataBar14CharacterWidths;
|
|
14444
|
+
__exports.dataBar14ValueForWidths = dataBar14ValueForWidths;
|
|
14445
|
+
__exports.DATABAR14_FINDERS = DATABAR14_FINDERS;
|
|
14446
|
+
__exports.DATABAR14_CHECKSUM_WEIGHTS = DATABAR14_CHECKSUM_WEIGHTS;
|
|
14447
|
+
};
|
|
14448
|
+
|
|
14449
|
+
__modules["databar/encoder.js"] = function (__require, __exports) {
|
|
14450
|
+
/** GS1 DataBar Omnidirectional and Truncated encoder. @module databar/encoder */
|
|
14451
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
14452
|
+
const { EncodeError } = __require("core/errors.js");
|
|
14453
|
+
const { encodeDataBar14GTIN } = __require("databar/codec.js");
|
|
14454
|
+
const { DATABAR14_CHECKSUM_WEIGHTS, DATABAR14_FINDERS, dataBar14CharacterWidths } = __require("databar/patterns.js");
|
|
14455
|
+
|
|
14456
|
+
function finderIndexes(characters) {
|
|
14457
|
+
const widths = characters.map((value, index) =>
|
|
14458
|
+
dataBar14CharacterWidths(value, index === 0 || index === 3 ? 'outside' : 'inside')
|
|
14459
|
+
);
|
|
14460
|
+
let checksum = 0;
|
|
14461
|
+
const weightOffsets = [0, 8, 24, 16];
|
|
14462
|
+
for (let character = 0; character < 4; character++) {
|
|
14463
|
+
for (let element = 0; element < 8; element++) {
|
|
14464
|
+
checksum += widths[character][element] * DATABAR14_CHECKSUM_WEIGHTS[weightOffsets[character] + element];
|
|
14465
|
+
}
|
|
14466
|
+
}
|
|
14467
|
+
checksum %= 79;
|
|
14468
|
+
if (checksum >= 8) checksum++;
|
|
14469
|
+
if (checksum >= 72) checksum++;
|
|
14470
|
+
return { widths, checksum, left: Math.floor(checksum / 9), right: checksum % 9 };
|
|
14471
|
+
}
|
|
14472
|
+
|
|
14473
|
+
function widthsToMatrix(widths, height) {
|
|
14474
|
+
const width = widths.reduce((sum, value) => sum + value, 0);
|
|
14475
|
+
const matrix = new BitMatrix(width, height);
|
|
14476
|
+
let dark = false;
|
|
14477
|
+
let x = 0;
|
|
14478
|
+
for (const run of widths) {
|
|
14479
|
+
if (dark) matrix.setRegion(x, 0, run, height);
|
|
14480
|
+
x += run;
|
|
14481
|
+
dark = !dark;
|
|
14482
|
+
}
|
|
14483
|
+
return matrix;
|
|
14484
|
+
}
|
|
14485
|
+
|
|
14486
|
+
/** Encode a checked GTIN as DataBar Omnidirectional or Truncated. */
|
|
14487
|
+
function encodeDataBar14(value, options = {}) {
|
|
14488
|
+
const variant = options.variant ?? 'omnidirectional';
|
|
14489
|
+
if (variant !== 'omnidirectional' && variant !== 'truncated') {
|
|
14490
|
+
throw new EncodeError('GS1 DataBar-14 physical encoder currently supports omnidirectional and truncated');
|
|
14491
|
+
}
|
|
14492
|
+
const height = options.height ?? (variant === 'omnidirectional' ? 33 : 13);
|
|
14493
|
+
if (!Number.isInteger(height) || height < (variant === 'omnidirectional' ? 33 : 13)) {
|
|
14494
|
+
throw new EncodeError(`GS1 DataBar ${variant} height is below its normative minimum`);
|
|
14495
|
+
}
|
|
14496
|
+
const compacted = encodeDataBar14GTIN(value, { linkage: options.linkage });
|
|
14497
|
+
const characters = compacted.physicalCharacters;
|
|
14498
|
+
const check = finderIndexes(characters);
|
|
14499
|
+
const widths = [1, 1];
|
|
14500
|
+
widths.push(...check.widths[0]);
|
|
14501
|
+
widths.push(...DATABAR14_FINDERS[check.left]);
|
|
14502
|
+
widths.push(...check.widths[1].slice().reverse());
|
|
14503
|
+
widths.push(...check.widths[2]);
|
|
14504
|
+
widths.push(...DATABAR14_FINDERS[check.right].slice().reverse());
|
|
14505
|
+
widths.push(...check.widths[3].slice().reverse());
|
|
14506
|
+
widths.push(1, 1);
|
|
14507
|
+
const matrix = widthsToMatrix(widths, height);
|
|
14508
|
+
matrix.databar = Object.freeze({ variant, gtin: compacted.gtin, linkage: compacted.linkage, checksum: check.checksum });
|
|
14509
|
+
return matrix;
|
|
14510
|
+
}
|
|
14511
|
+
|
|
14512
|
+
__exports.encodeDataBar14 = encodeDataBar14;
|
|
14513
|
+
};
|
|
14514
|
+
|
|
14515
|
+
__modules["databar/decoder.js"] = function (__require, __exports) {
|
|
14516
|
+
/** Clean-matrix decoder for GS1 DataBar Omnidirectional and Truncated. @module databar/decoder */
|
|
14517
|
+
const { ChecksumError, FormatError } = __require("core/errors.js");
|
|
14518
|
+
const { decodeDataBar14GTIN } = __require("databar/codec.js");
|
|
14519
|
+
const { DATABAR14_CHECKSUM_WEIGHTS, DATABAR14_FINDERS, dataBar14ValueForWidths } = __require("databar/patterns.js");
|
|
14520
|
+
|
|
14521
|
+
function sampledRuns(matrix) {
|
|
14522
|
+
if (!matrix || !Number.isInteger(matrix.width) || !Number.isInteger(matrix.height)) {
|
|
14523
|
+
throw new TypeError('GS1 DataBar decoder expects a BitMatrix-like value');
|
|
14524
|
+
}
|
|
14525
|
+
if (matrix.width % 96 !== 0) throw new FormatError('GS1 DataBar-14 matrix width must be an integer multiple of 96 modules');
|
|
14526
|
+
const scale = matrix.width / 96;
|
|
14527
|
+
const y = Math.floor(matrix.height / 2);
|
|
14528
|
+
const bits = Array.from({ length: 96 }, (_, x) => matrix.get(x * scale + Math.floor(scale / 2), y));
|
|
14529
|
+
if (bits[0] || !bits[95]) throw new FormatError('GS1 DataBar-14 guard pattern is invalid');
|
|
14530
|
+
const runs = [];
|
|
14531
|
+
let current = bits[0];
|
|
14532
|
+
let length = 0;
|
|
14533
|
+
for (const bit of bits) {
|
|
14534
|
+
if (bit === current) length++;
|
|
14535
|
+
else {
|
|
14536
|
+
runs.push(length);
|
|
14537
|
+
current = bit;
|
|
14538
|
+
length = 1;
|
|
14539
|
+
}
|
|
14540
|
+
}
|
|
14541
|
+
runs.push(length);
|
|
14542
|
+
if (runs.length !== 46 || runs[0] !== 1 || runs[1] !== 1 || runs[44] !== 1 || runs[45] !== 1) {
|
|
14543
|
+
throw new FormatError('GS1 DataBar-14 element count or guard widths are invalid');
|
|
14544
|
+
}
|
|
14545
|
+
return runs;
|
|
14546
|
+
}
|
|
14547
|
+
|
|
14548
|
+
function finderIndex(widths) {
|
|
14549
|
+
const key = widths.join(',');
|
|
14550
|
+
return DATABAR14_FINDERS.findIndex((candidate) => candidate.join(',') === key);
|
|
14551
|
+
}
|
|
14552
|
+
|
|
14553
|
+
function expectedChecksum(widths) {
|
|
14554
|
+
const offsets = [0, 8, 24, 16];
|
|
14555
|
+
let checksum = 0;
|
|
14556
|
+
for (let character = 0; character < 4; character++) {
|
|
14557
|
+
for (let element = 0; element < 8; element++) {
|
|
14558
|
+
checksum += widths[character][element] * DATABAR14_CHECKSUM_WEIGHTS[offsets[character] + element];
|
|
14559
|
+
}
|
|
14560
|
+
}
|
|
14561
|
+
checksum %= 79;
|
|
14562
|
+
if (checksum >= 8) checksum++;
|
|
14563
|
+
if (checksum >= 72) checksum++;
|
|
14564
|
+
return checksum;
|
|
14565
|
+
}
|
|
14566
|
+
|
|
14567
|
+
/** Decode a clean or integer-scaled 96-module DataBar-14 matrix. */
|
|
14568
|
+
function decodeDataBar14(matrix) {
|
|
14569
|
+
const runs = sampledRuns(matrix);
|
|
14570
|
+
const widths = [
|
|
14571
|
+
runs.slice(2, 10),
|
|
14572
|
+
runs.slice(15, 23).slice().reverse(),
|
|
14573
|
+
runs.slice(23, 31),
|
|
14574
|
+
runs.slice(36, 44).slice().reverse(),
|
|
14575
|
+
];
|
|
14576
|
+
const leftFinder = finderIndex(runs.slice(10, 15));
|
|
14577
|
+
const rightFinder = finderIndex(runs.slice(31, 36).slice().reverse());
|
|
14578
|
+
if (leftFinder < 0 || rightFinder < 0) throw new FormatError('GS1 DataBar-14 finder pattern is invalid');
|
|
14579
|
+
const encodedChecksum = leftFinder * 9 + rightFinder;
|
|
14580
|
+
if (encodedChecksum !== expectedChecksum(widths)) throw new ChecksumError('GS1 DataBar-14 checksum mismatch');
|
|
14581
|
+
|
|
14582
|
+
const outerLeft = dataBar14ValueForWidths(widths[0], 'outside');
|
|
14583
|
+
const innerLeft = dataBar14ValueForWidths(widths[1], 'inside');
|
|
14584
|
+
const innerRight = dataBar14ValueForWidths(widths[2], 'inside');
|
|
14585
|
+
const outerRight = dataBar14ValueForWidths(widths[3], 'outside');
|
|
14586
|
+
const decoded = decodeDataBar14GTIN({ outerLeft, innerLeft, outerRight, innerRight });
|
|
14587
|
+
return Object.freeze({
|
|
14588
|
+
format: 'databar-omnidirectional',
|
|
14589
|
+
text: decoded.gtin,
|
|
14590
|
+
gtin: decoded.gtin,
|
|
14591
|
+
linkage: decoded.linkage,
|
|
14592
|
+
symbologyIdentifier: ']e0',
|
|
14593
|
+
});
|
|
14594
|
+
}
|
|
14595
|
+
|
|
14596
|
+
__exports.decodeDataBar14 = decodeDataBar14;
|
|
14597
|
+
};
|
|
14598
|
+
|
|
14599
|
+
__modules["databar/index.js"] = function (__require, __exports) {
|
|
14600
|
+
/** Verified GS1 DataBar data-layer primitives. @module databar */
|
|
14601
|
+
const __reexport0 = __require("databar/codec.js"); __exports.dataBarGtinTransmission = __reexport0.dataBarGtinTransmission; __exports.decodeDataBar14GTIN = __reexport0.decodeDataBar14GTIN; __exports.decodeDataBarLimitedGTIN = __reexport0.decodeDataBarLimitedGTIN; __exports.encodeDataBar14GTIN = __reexport0.encodeDataBar14GTIN; __exports.encodeDataBarLimitedGTIN = __reexport0.encodeDataBarLimitedGTIN; __exports.gtinCheckDigit = __reexport0.gtinCheckDigit; __exports.makeGTIN14 = __reexport0.makeGTIN14; __exports.normalizeGTIN = __reexport0.normalizeGTIN;
|
|
14602
|
+
const __reexport1 = __require("databar/gs1.js"); __exports.GS1_SEPARATOR = __reexport1.GS1_SEPARATOR; __exports.decodeGS1ElementString = __reexport1.decodeGS1ElementString; __exports.encodeGS1ElementString = __reexport1.encodeGS1ElementString; __exports.formatGS1Elements = __reexport1.formatGS1Elements; __exports.gs1AIInfo = __reexport1.gs1AIInfo; __exports.parseGS1ElementString = __reexport1.parseGS1ElementString;
|
|
14603
|
+
const __reexport2 = __require("databar/tables.js"); __exports.DATABAR14_VARIANTS = __reexport2.DATABAR14_VARIANTS; __exports.DATABAR_LIMITED_VARIANT = __reexport2.DATABAR_LIMITED_VARIANT; __exports.dataBar14GroupFor = __reexport2.dataBar14GroupFor; __exports.validateDataBarTables = __reexport2.validateDataBarTables;
|
|
14604
|
+
const __reexport3 = __require("databar/encoder.js"); __exports.encodeDataBar14 = __reexport3.encodeDataBar14;
|
|
14605
|
+
const __reexport4 = __require("databar/decoder.js"); __exports.decodeDataBar14 = __reexport4.decodeDataBar14;
|
|
14606
|
+
const __reexport5 = __require("databar/patterns.js"); __exports.DATABAR14_CHECKSUM_WEIGHTS = __reexport5.DATABAR14_CHECKSUM_WEIGHTS; __exports.DATABAR14_FINDERS = __reexport5.DATABAR14_FINDERS; __exports.dataBar14CharacterWidths = __reexport5.dataBar14CharacterWidths; __exports.dataBar14ValueForWidths = __reexport5.dataBar14ValueForWidths; __exports.dataBarWidths = __reexport5.dataBarWidths;
|
|
14607
|
+
|
|
14608
|
+
|
|
14609
|
+
};
|
|
14610
|
+
|
|
14611
|
+
__modules["render/options.js"] = function (__require, __exports) {
|
|
14612
|
+
/**
|
|
14613
|
+
* Shared render options, normalised once so every backend agrees.
|
|
14614
|
+
*
|
|
14615
|
+
* @module render/options
|
|
14616
|
+
*/
|
|
14617
|
+
const { BitMatrix } = __require("core/bit-matrix.js");
|
|
14618
|
+
|
|
14619
|
+
/**
|
|
14620
|
+
* @typedef {object} RenderOptions
|
|
14621
|
+
* @property {number} [scale] Pixels per module. Default 8.
|
|
14622
|
+
* @property {number} [margin] Quiet-zone modules on every side. Default 4.
|
|
14623
|
+
* @property {string} [dark] Colour of set modules. Default '#000000'.
|
|
14624
|
+
* @property {string} [light] Colour of clear modules, or 'none' for transparent.
|
|
14625
|
+
* @property {number} [barHeight] For 1D symbols: total bar height in pixels.
|
|
14626
|
+
*/
|
|
14627
|
+
|
|
14628
|
+
/**
|
|
14629
|
+
* Expand and pad the matrix, and resolve every dimension.
|
|
14630
|
+
*
|
|
14631
|
+
* Linear symbols arrive one module tall. They are stretched to `barHeight`
|
|
14632
|
+
* *before* the quiet zone is applied, so the margin ends up uniform on all
|
|
14633
|
+
* four sides — padding first would leave a quiet zone one module tall against
|
|
14634
|
+
* bars a hundred pixels tall, which no scanner would accept.
|
|
14635
|
+
*
|
|
14636
|
+
* @param {BitMatrix} matrix
|
|
14637
|
+
* @param {RenderOptions} options
|
|
14638
|
+
*/
|
|
14639
|
+
function normalizeOptions(matrix, options = {}) {
|
|
14640
|
+
const scale = Math.max(1, Math.floor(options.scale ?? 8));
|
|
14641
|
+
const margin = Math.max(0, Math.floor(options.margin ?? 4));
|
|
14642
|
+
const dark = options.dark ?? '#000000';
|
|
14643
|
+
const light = options.light ?? '#ffffff';
|
|
14644
|
+
const barHeight = options.barHeight ?? null;
|
|
14645
|
+
|
|
14646
|
+
let base = matrix;
|
|
14647
|
+
const is1D = matrix.height === 1;
|
|
14648
|
+
|
|
14649
|
+
if (is1D) {
|
|
14650
|
+
// Default to a bar height that stays scannable: tall enough that a laser
|
|
14651
|
+
// crossing at a slight angle still passes through the whole symbol.
|
|
14652
|
+
const targetPixels = barHeight ?? Math.max(40, Math.round(matrix.width * scale * 0.15));
|
|
14653
|
+
const rows = Math.max(1, Math.round(targetPixels / scale));
|
|
14654
|
+
base = new BitMatrix(matrix.width, rows);
|
|
14655
|
+
for (let x = 0; x < matrix.width; x++) {
|
|
14656
|
+
if (!matrix.get(x, 0)) continue;
|
|
14657
|
+
for (let y = 0; y < rows; y++) base.set(x, y);
|
|
14658
|
+
}
|
|
14659
|
+
}
|
|
14660
|
+
|
|
14661
|
+
const source = margin > 0 ? base.withMargin(margin) : base;
|
|
14662
|
+
|
|
14663
|
+
return {
|
|
14664
|
+
scale,
|
|
14665
|
+
margin,
|
|
14666
|
+
dark,
|
|
14667
|
+
light,
|
|
14668
|
+
is1D,
|
|
14669
|
+
source,
|
|
14670
|
+
rowHeight: scale,
|
|
14671
|
+
pixelWidth: source.width * scale,
|
|
14672
|
+
pixelHeight: source.height * scale,
|
|
14673
|
+
};
|
|
14674
|
+
}
|
|
14675
|
+
|
|
14676
|
+
/**
|
|
14677
|
+
* Parse a CSS colour into RGBA bytes.
|
|
14678
|
+
*
|
|
14679
|
+
* Supports the forms a barcode actually needs: #rgb, #rgba, #rrggbb,
|
|
14680
|
+
* #rrggbbaa, rgb(), rgba(), plus 'none' and 'transparent'.
|
|
14681
|
+
*
|
|
14682
|
+
* @param {string} colour
|
|
14683
|
+
* @returns {[number, number, number, number]}
|
|
14684
|
+
*/
|
|
14685
|
+
function parseColor(colour) {
|
|
14686
|
+
const value = String(colour).trim().toLowerCase();
|
|
14687
|
+
|
|
14688
|
+
if (value === 'none' || value === 'transparent') return [0, 0, 0, 0];
|
|
14689
|
+
if (value === 'white') return [255, 255, 255, 255];
|
|
14690
|
+
if (value === 'black') return [0, 0, 0, 255];
|
|
14691
|
+
|
|
14692
|
+
if (value[0] === '#') {
|
|
14693
|
+
const hex = value.slice(1);
|
|
14694
|
+
const expand = (c) => parseInt(c + c, 16);
|
|
10342
14695
|
if (hex.length === 3) {
|
|
10343
14696
|
return [expand(hex[0]), expand(hex[1]), expand(hex[2]), 255];
|
|
10344
14697
|
}
|
|
@@ -11513,6 +15866,12 @@ const qr = __require("qr/index.js");
|
|
|
11513
15866
|
const aztec = __require("aztec/index.js");
|
|
11514
15867
|
const pdf417 = __require("pdf417/index.js");
|
|
11515
15868
|
const micropdf417 = __require("micropdf417/index.js");
|
|
15869
|
+
const microqr = __require("microqr/index.js");
|
|
15870
|
+
const rmqr = __require("rmqr/index.js");
|
|
15871
|
+
const frameqr = __require("frameqr/index.js");
|
|
15872
|
+
const aztecRune = __require("aztecrune/index.js");
|
|
15873
|
+
const compactPdf417 = __require("compactpdf417/index.js");
|
|
15874
|
+
const databar = __require("databar/index.js");
|
|
11516
15875
|
__exports.BitMatrix = BitMatrix;
|
|
11517
15876
|
const __reexport0 = __require("core/errors.js"); __exports.BarcodeError = __reexport0.BarcodeError; __exports.EncodeError = __reexport0.EncodeError; __exports.NotFoundError = __reexport0.NotFoundError; __exports.FormatError = __reexport0.FormatError; __exports.ChecksumError = __reexport0.ChecksumError;
|
|
11518
15877
|
const __reexport1 = __require("image/luminance.js"); __exports.LuminanceSource = __reexport1.LuminanceSource;
|
|
@@ -11526,8 +15885,16 @@ const __reexport7 = __require("render/index.js"); __exports.renderToCanvasAutoAs
|
|
|
11526
15885
|
const __reexport8 = __require("qr/index.js"); __exports.encodeQR = __reexport8.encodeQR; __exports.decodeQR = __reexport8.decodeQR; __exports.detectQR = __reexport8.detectQR; __exports.detectAndDecodeQR = __reexport8.detectAndDecodeQR;
|
|
11527
15886
|
const __reexport9 = __require("datamatrix/index.js"); __exports.encodeDataMatrix = __reexport9.encodeDataMatrix; __exports.decodeDataMatrix = __reexport9.decodeDataMatrix; __exports.detectDataMatrix = __reexport9.detectDataMatrix; __exports.detectAndDecodeDataMatrix = __reexport9.detectAndDecodeDataMatrix;
|
|
11528
15887
|
const __reexport10 = __require("aztec/index.js"); __exports.encodeAztec = __reexport10.encodeAztec; __exports.decodeAztec = __reexport10.decodeAztec; __exports.detectAztec = __reexport10.detectAztec; __exports.detectAndDecodeAztec = __reexport10.detectAndDecodeAztec;
|
|
15888
|
+
Object.assign(__exports, __require("aztecrune/index.js"));
|
|
11529
15889
|
const __reexport11 = __require("pdf417/index.js"); __exports.encodePDF417 = __reexport11.encodePDF417; __exports.decodePDF417 = __reexport11.decodePDF417; __exports.detectPDF417 = __reexport11.detectPDF417; __exports.detectAndDecodePDF417 = __reexport11.detectAndDecodePDF417;
|
|
15890
|
+
Object.assign(__exports, __require("compactpdf417/index.js"));
|
|
15891
|
+
// DataBar currently exposes verified GS1 data-layer codecs only; physical
|
|
15892
|
+
// symbol rendering and image detection remain deliberately out of scope.
|
|
15893
|
+
Object.assign(__exports, __require("databar/index.js"));
|
|
11530
15894
|
const __reexport12 = __require("micropdf417/index.js"); __exports.encodeMicroPDF417 = __reexport12.encodeMicroPDF417; __exports.decodeMicroPDF417 = __reexport12.decodeMicroPDF417; __exports.detectMicroPDF417 = __reexport12.detectMicroPDF417; __exports.detectAndDecodeMicroPDF417 = __reexport12.detectAndDecodeMicroPDF417;
|
|
15895
|
+
const __reexport13 = __require("microqr/index.js"); __exports.encodeMicroQR = __reexport13.encodeMicroQR; __exports.decodeMicroQR = __reexport13.decodeMicroQR; __exports.detectMicroQR = __reexport13.detectMicroQR; __exports.detectAndDecodeMicroQR = __reexport13.detectAndDecodeMicroQR;
|
|
15896
|
+
const __reexport14 = __require("rmqr/index.js"); __exports.encodeRMQR = __reexport14.encodeRMQR; __exports.decodeRMQR = __reexport14.decodeRMQR; __exports.detectRMQR = __reexport14.detectRMQR; __exports.detectAndDecodeRMQR = __reexport14.detectAndDecodeRMQR;
|
|
15897
|
+
const __reexport15 = __require("frameqr/index.js"); __exports.encodeFrameQR = __reexport15.encodeFrameQR; __exports.decodeFrameQR = __reexport15.decodeFrameQR; __exports.detectFrameQR = __reexport15.detectFrameQR; __exports.detectAndDecodeFrameQR = __reexport15.detectAndDecodeFrameQR;
|
|
11531
15898
|
|
|
11532
15899
|
/**
|
|
11533
15900
|
* @typedef {object} FormatInfo
|
|
@@ -11563,6 +15930,17 @@ const pdf417CanEncode = typeof pdf417.encodePDF417 === 'function';
|
|
|
11563
15930
|
const pdf417CanDecode = typeof pdf417.detectAndDecodePDF417 === 'function';
|
|
11564
15931
|
const microPdf417CanEncode = typeof micropdf417.encodeMicroPDF417 === 'function';
|
|
11565
15932
|
const microPdf417CanDecode = typeof micropdf417.detectAndDecodeMicroPDF417 === 'function';
|
|
15933
|
+
const microQrCanEncode = typeof microqr.encodeMicroQR === 'function';
|
|
15934
|
+
const microQrCanDecode = typeof microqr.detectAndDecodeMicroQR === 'function';
|
|
15935
|
+
const rmqrCanEncode = typeof rmqr.encodeRMQR === 'function';
|
|
15936
|
+
const rmqrCanDecode = typeof rmqr.detectAndDecodeRMQR === 'function';
|
|
15937
|
+
const frameQrCanEncode = typeof frameqr.encodeFrameQR === 'function';
|
|
15938
|
+
const frameQrCanDecode = typeof frameqr.detectAndDecodeFrameQR === 'function';
|
|
15939
|
+
const aztecRuneCanEncode = typeof aztecRune.encodeAztecRune === 'function';
|
|
15940
|
+
const aztecRuneCanDecode = typeof aztecRune.detectAndDecodeAztecRune === 'function';
|
|
15941
|
+
const compactPdf417CanEncode = typeof compactPdf417.encodeCompactPDF417 === 'function';
|
|
15942
|
+
const compactPdf417CanDecode = typeof compactPdf417.detectAndDecodeCompactPDF417 === 'function';
|
|
15943
|
+
const dataBarCanEncode = typeof databar.encodeDataBar14 === 'function';
|
|
11566
15944
|
|
|
11567
15945
|
/**
|
|
11568
15946
|
* Every format this build supports.
|
|
@@ -11604,6 +15982,13 @@ function listFormats() {
|
|
|
11604
15982
|
canRead: aztecCanDecode,
|
|
11605
15983
|
kind: /** @type {'2D'} */ ('2D'),
|
|
11606
15984
|
});
|
|
15985
|
+
formats.push({
|
|
15986
|
+
id: 'aztecrune',
|
|
15987
|
+
label: 'Aztec Rune',
|
|
15988
|
+
canWrite: aztecRuneCanEncode,
|
|
15989
|
+
canRead: aztecRuneCanDecode,
|
|
15990
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
15991
|
+
});
|
|
11607
15992
|
formats.push({
|
|
11608
15993
|
id: 'pdf417',
|
|
11609
15994
|
label: 'PDF417',
|
|
@@ -11611,6 +15996,13 @@ function listFormats() {
|
|
|
11611
15996
|
canRead: pdf417CanDecode,
|
|
11612
15997
|
kind: /** @type {'2D'} */ ('2D'),
|
|
11613
15998
|
});
|
|
15999
|
+
formats.push({
|
|
16000
|
+
id: 'compactpdf417',
|
|
16001
|
+
label: 'Compact PDF417',
|
|
16002
|
+
canWrite: compactPdf417CanEncode,
|
|
16003
|
+
canRead: compactPdf417CanDecode,
|
|
16004
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
16005
|
+
});
|
|
11614
16006
|
formats.push({
|
|
11615
16007
|
id: 'micropdf417',
|
|
11616
16008
|
label: 'MicroPDF417',
|
|
@@ -11618,6 +16010,34 @@ function listFormats() {
|
|
|
11618
16010
|
canRead: microPdf417CanDecode,
|
|
11619
16011
|
kind: /** @type {'2D'} */ ('2D'),
|
|
11620
16012
|
});
|
|
16013
|
+
formats.push({
|
|
16014
|
+
id: 'microqr',
|
|
16015
|
+
label: 'Micro QR Code',
|
|
16016
|
+
canWrite: microQrCanEncode,
|
|
16017
|
+
canRead: microQrCanDecode,
|
|
16018
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
16019
|
+
});
|
|
16020
|
+
formats.push({
|
|
16021
|
+
id: 'rmqr',
|
|
16022
|
+
label: 'rMQR Code',
|
|
16023
|
+
canWrite: rmqrCanEncode,
|
|
16024
|
+
canRead: rmqrCanDecode,
|
|
16025
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
16026
|
+
});
|
|
16027
|
+
formats.push({
|
|
16028
|
+
id: 'frameqr',
|
|
16029
|
+
label: 'Sythos Canvas QR profile',
|
|
16030
|
+
canWrite: frameQrCanEncode,
|
|
16031
|
+
canRead: frameQrCanDecode,
|
|
16032
|
+
kind: /** @type {'2D'} */ ('2D'),
|
|
16033
|
+
});
|
|
16034
|
+
formats.push({
|
|
16035
|
+
id: 'gs1databar14',
|
|
16036
|
+
label: 'GS1 DataBar Omnidirectional / Truncated',
|
|
16037
|
+
canWrite: dataBarCanEncode,
|
|
16038
|
+
canRead: false,
|
|
16039
|
+
kind: /** @type {'1D'} */ ('1D'),
|
|
16040
|
+
});
|
|
11621
16041
|
|
|
11622
16042
|
return formats;
|
|
11623
16043
|
}
|
|
@@ -11648,6 +16068,14 @@ function listFormats() {
|
|
|
11648
16068
|
* @param {'auto'|'text'|'byte'|'numeric'} [options.compaction] PDF417 compaction mode.
|
|
11649
16069
|
* @param {number} [options.eci] MicroPDF417 byte-compaction ECI assignment (3 or 26).
|
|
11650
16070
|
* @param {number} [options.aspectRatio] Preferred MicroPDF417 symbol aspect ratio.
|
|
16071
|
+
* @param {object} [options.canvas] Sythos Canvas QR artwork reservation.
|
|
16072
|
+
* @param {'square'|'circle'|'diamond'} [options.canvas.shape] Canvas shape.
|
|
16073
|
+
* @param {number} [options.canvas.size] Odd canvas size in QR modules.
|
|
16074
|
+
* @param {number} [options.canvas.width] Canvas width in QR modules.
|
|
16075
|
+
* @param {number} [options.canvas.height] Canvas height in QR modules.
|
|
16076
|
+
* @param {number} [options.canvas.centerX] Canvas centre X in QR modules.
|
|
16077
|
+
* @param {number} [options.canvas.centerY] Canvas centre Y in QR modules.
|
|
16078
|
+
* @param {0|90|180|270} [options.canvas.angle] Canvas quarter-turn.
|
|
11651
16079
|
* @returns {BitMatrix}
|
|
11652
16080
|
*/
|
|
11653
16081
|
function encode(text, options = {}) {
|
|
@@ -11663,16 +16091,34 @@ function encode(text, options = {}) {
|
|
|
11663
16091
|
if (format === 'aztec' || format === 'aztec-code') {
|
|
11664
16092
|
return aztec.encodeAztec(value, options);
|
|
11665
16093
|
}
|
|
16094
|
+
if (format === 'aztecrune' || format === 'aztec-rune' || format === 'rune') {
|
|
16095
|
+
return aztecRune.encodeAztecRune(value, options);
|
|
16096
|
+
}
|
|
11666
16097
|
if (format === 'pdf417' || format === 'pdf-417') {
|
|
11667
16098
|
return pdf417.encodePDF417(value, options);
|
|
11668
16099
|
}
|
|
16100
|
+
if (format === 'compactpdf417' || format === 'compact-pdf417' || format === 'compact-pdf-417') {
|
|
16101
|
+
return compactPdf417.encodeCompactPDF417(value, options);
|
|
16102
|
+
}
|
|
11669
16103
|
if (format === 'micropdf417' || format === 'micro-pdf417' || format === 'micro-pdf-417') {
|
|
11670
16104
|
return micropdf417.encodeMicroPDF417(value, options);
|
|
11671
16105
|
}
|
|
16106
|
+
if (format === 'microqr' || format === 'micro-qr') {
|
|
16107
|
+
return microqr.encodeMicroQR(value, options);
|
|
16108
|
+
}
|
|
16109
|
+
if (format === 'rmqr' || format === 'r-mqr' || format === 'rectangular-micro-qr') {
|
|
16110
|
+
return rmqr.encodeRMQR(value, options);
|
|
16111
|
+
}
|
|
16112
|
+
if (format === 'frameqr' || format === 'frame-qr' || format === 'canvas-qr') {
|
|
16113
|
+
return frameqr.encodeFrameQR(value, options);
|
|
16114
|
+
}
|
|
16115
|
+
if (format === 'gs1databar14' || format === 'gs1-databar14' || format === 'databar') {
|
|
16116
|
+
return databar.encodeDataBar14(value, options);
|
|
16117
|
+
}
|
|
11672
16118
|
|
|
11673
16119
|
const entry = ONED_FORMATS[format];
|
|
11674
16120
|
if (!entry) {
|
|
11675
|
-
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'pdf417', 'micropdf417'].join(', ');
|
|
16121
|
+
const known = [...Object.keys(ONED_FORMATS), 'qr', 'datamatrix', 'aztec', 'aztecrune', 'pdf417', 'compactpdf417', 'micropdf417', 'microqr', 'rmqr', 'frameqr', 'gs1databar14'].join(', ');
|
|
11676
16122
|
throw new EncodeError(`Unknown format "${format}". Known formats: ${known}`);
|
|
11677
16123
|
}
|
|
11678
16124
|
return entry.encode(value, options);
|
|
@@ -11695,6 +16141,9 @@ function encode(text, options = {}) {
|
|
|
11695
16141
|
* @property {number} [rowHeight] PDF417 row height in modules.
|
|
11696
16142
|
* @property {number} [variant] MicroPDF417 predefined variant number.
|
|
11697
16143
|
* @property {number} [eccCodewords] MicroPDF417 fixed error-correction codewords.
|
|
16144
|
+
* @property {string} [profile] Sythos Canvas QR profile identifier.
|
|
16145
|
+
* @property {boolean} [certified] Whether the profile is certified by its originator.
|
|
16146
|
+
* @property {object} [canvas] Canvas reservation metadata for the Sythos profile.
|
|
11698
16147
|
*/
|
|
11699
16148
|
|
|
11700
16149
|
/**
|
|
@@ -11709,6 +16158,8 @@ function encode(text, options = {}) {
|
|
|
11709
16158
|
* @param {string[]} [options.formats] Restrict to these format ids.
|
|
11710
16159
|
* @param {boolean} [options.tryHarder] Retry inverted and rotated. Default true.
|
|
11711
16160
|
* @param {'global'|'hybrid'|'auto'} [options.binarizer]
|
|
16161
|
+
* @param {object} [options.frameqr] Sythos Canvas QR detector options when
|
|
16162
|
+
* the profile marker is not preserved through image rendering.
|
|
11712
16163
|
* @returns {DecodeResult[]}
|
|
11713
16164
|
*/
|
|
11714
16165
|
function decode(image, options = {}) {
|
|
@@ -11717,8 +16168,13 @@ function decode(image, options = {}) {
|
|
|
11717
16168
|
const wantQR = !want || want.has('qr') || want.has('qrcode');
|
|
11718
16169
|
const wantDataMatrix = !want || want.has('datamatrix') || want.has('data-matrix');
|
|
11719
16170
|
const wantAztec = !want || want.has('aztec') || want.has('aztec-code');
|
|
16171
|
+
const wantAztecRune = !want || want.has('aztecrune') || want.has('aztec-rune') || want.has('rune');
|
|
11720
16172
|
const wantPDF417 = !want || want.has('pdf417') || want.has('pdf-417');
|
|
16173
|
+
const wantCompactPDF417 = !want || want.has('compactpdf417') || want.has('compact-pdf417') || want.has('compact-pdf-417');
|
|
11721
16174
|
const wantMicroPDF417 = !want || want.has('micropdf417') || want.has('micro-pdf417') || want.has('micro-pdf-417');
|
|
16175
|
+
const wantMicroQR = !want || want.has('microqr') || want.has('micro-qr');
|
|
16176
|
+
const wantRMQR = !want || want.has('rmqr') || want.has('r-mqr') || want.has('rectangular-micro-qr');
|
|
16177
|
+
const wantFrameQR = !want || want.has('frameqr') || want.has('frame-qr') || want.has('canvas-qr');
|
|
11722
16178
|
const wantOneD = !want || [...want].some((f) => f in ONED_FORMATS);
|
|
11723
16179
|
|
|
11724
16180
|
const source = LuminanceSource.fromImageData(image);
|
|
@@ -11771,6 +16227,15 @@ function decode(image, options = {}) {
|
|
|
11771
16227
|
}
|
|
11772
16228
|
}
|
|
11773
16229
|
|
|
16230
|
+
if (wantAztecRune && aztecRuneCanDecode) {
|
|
16231
|
+
try {
|
|
16232
|
+
const found = aztecRune.detectAndDecodeAztecRune(bits);
|
|
16233
|
+
if (found) results.push({ ...found, format: 'aztecrune' });
|
|
16234
|
+
} catch {
|
|
16235
|
+
/* no Aztec Rune in this pass */
|
|
16236
|
+
}
|
|
16237
|
+
}
|
|
16238
|
+
|
|
11774
16239
|
if (wantPDF417 && pdf417CanDecode) {
|
|
11775
16240
|
try {
|
|
11776
16241
|
const found = pdf417.detectAndDecodePDF417(bits);
|
|
@@ -11780,6 +16245,15 @@ function decode(image, options = {}) {
|
|
|
11780
16245
|
}
|
|
11781
16246
|
}
|
|
11782
16247
|
|
|
16248
|
+
if (wantCompactPDF417 && compactPdf417CanDecode) {
|
|
16249
|
+
try {
|
|
16250
|
+
const found = compactPdf417.detectAndDecodeCompactPDF417(bits);
|
|
16251
|
+
if (found) results.push({ ...found, format: 'compactpdf417' });
|
|
16252
|
+
} catch {
|
|
16253
|
+
/* no Compact PDF417 in this pass */
|
|
16254
|
+
}
|
|
16255
|
+
}
|
|
16256
|
+
|
|
11783
16257
|
if (wantMicroPDF417 && microPdf417CanDecode) {
|
|
11784
16258
|
// MicroPDF417 detection measures runs across the whole raster. Hybrid
|
|
11785
16259
|
// thresholding can alter uniform modules near local-window boundaries,
|
|
@@ -11795,6 +16269,35 @@ function decode(image, options = {}) {
|
|
|
11795
16269
|
}
|
|
11796
16270
|
}
|
|
11797
16271
|
|
|
16272
|
+
if (wantMicroQR && microQrCanDecode) {
|
|
16273
|
+
try {
|
|
16274
|
+
for (const found of microqr.detectAndDecodeMicroQR(bits)) {
|
|
16275
|
+
results.push({ ...found, format: 'microqr' });
|
|
16276
|
+
}
|
|
16277
|
+
} catch {
|
|
16278
|
+
/* no Micro QR in this pass */
|
|
16279
|
+
}
|
|
16280
|
+
}
|
|
16281
|
+
|
|
16282
|
+
if (wantRMQR && rmqrCanDecode) {
|
|
16283
|
+
try {
|
|
16284
|
+
const found = rmqr.detectAndDecodeRMQR(bits);
|
|
16285
|
+
if (found) results.push({ ...found, format: 'rmqr' });
|
|
16286
|
+
} catch {
|
|
16287
|
+
/* no rMQR in this pass */
|
|
16288
|
+
}
|
|
16289
|
+
}
|
|
16290
|
+
|
|
16291
|
+
if (wantFrameQR && frameQrCanDecode) {
|
|
16292
|
+
try {
|
|
16293
|
+
for (const found of frameqr.detectAndDecodeFrameQR(bits, options.frameqr ?? {})) {
|
|
16294
|
+
results.push({ ...found, format: 'frameqr' });
|
|
16295
|
+
}
|
|
16296
|
+
} catch {
|
|
16297
|
+
/* no Sythos Canvas QR profile in this pass */
|
|
16298
|
+
}
|
|
16299
|
+
}
|
|
16300
|
+
|
|
11798
16301
|
if (wantOneD) {
|
|
11799
16302
|
const oneDFormats = want ? [...want].filter((f) => f in ONED_FORMATS) : null;
|
|
11800
16303
|
for (const found of decodeOneD(bits, { formats: oneDFormats, tryHarder })) {
|
|
@@ -11829,7 +16332,7 @@ function decodeStrict(image, options) {
|
|
|
11829
16332
|
}
|
|
11830
16333
|
|
|
11831
16334
|
/** Library version, matching package.json. */
|
|
11832
|
-
const VERSION = '1.
|
|
16335
|
+
const VERSION = '1.4.1';
|
|
11833
16336
|
|
|
11834
16337
|
__exports.listFormats = listFormats;
|
|
11835
16338
|
__exports.encode = encode;
|