qr-core 1.0.0

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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -0
  3. package/dist/bench/bench.d.ts +1 -0
  4. package/dist/bench/bench.js +38 -0
  5. package/dist/src/api/index.d.ts +9 -0
  6. package/dist/src/api/index.js +224 -0
  7. package/dist/src/core/bit-buffer.d.ts +11 -0
  8. package/dist/src/core/bit-buffer.js +53 -0
  9. package/dist/src/core/error.d.ts +8 -0
  10. package/dist/src/core/error.js +16 -0
  11. package/dist/src/core/gf.d.ts +4 -0
  12. package/dist/src/core/gf.js +52 -0
  13. package/dist/src/core/rs.d.ts +1 -0
  14. package/dist/src/core/rs.js +60 -0
  15. package/dist/src/encoding/bitstream.d.ts +7 -0
  16. package/dist/src/encoding/bitstream.js +68 -0
  17. package/dist/src/encoding/encoders.d.ts +13 -0
  18. package/dist/src/encoding/encoders.js +54 -0
  19. package/dist/src/encoding/interleave.d.ts +5 -0
  20. package/dist/src/encoding/interleave.js +49 -0
  21. package/dist/src/encoding/segmentation.d.ts +12 -0
  22. package/dist/src/encoding/segmentation.js +43 -0
  23. package/dist/src/index.d.ts +3 -0
  24. package/dist/src/index.js +3 -0
  25. package/dist/src/layout/patterns.d.ts +12 -0
  26. package/dist/src/layout/patterns.js +120 -0
  27. package/dist/src/mapping/zigzag.d.ts +5 -0
  28. package/dist/src/mapping/zigzag.js +28 -0
  29. package/dist/src/mask/patterns.d.ts +6 -0
  30. package/dist/src/mask/patterns.js +13 -0
  31. package/dist/src/mask/penalty.d.ts +5 -0
  32. package/dist/src/mask/penalty.js +173 -0
  33. package/dist/src/mask/selection.d.ts +12 -0
  34. package/dist/src/mask/selection.js +178 -0
  35. package/dist/src/matrix/bit-matrix.d.ts +46 -0
  36. package/dist/src/matrix/bit-matrix.js +139 -0
  37. package/dist/src/spec/bch.d.ts +11 -0
  38. package/dist/src/spec/bch.js +30 -0
  39. package/dist/src/spec/constants.d.ts +40 -0
  40. package/dist/src/spec/constants.js +37 -0
  41. package/dist/src/spec/tables.d.ts +20 -0
  42. package/dist/src/spec/tables.js +1099 -0
  43. package/dist/src/strategies/index.d.ts +3 -0
  44. package/dist/src/strategies/index.js +3 -0
  45. package/dist/src/strategies/mask.d.ts +16 -0
  46. package/dist/src/strategies/mask.js +9 -0
  47. package/dist/src/strategies/segmentation.d.ts +12 -0
  48. package/dist/src/strategies/segmentation.js +41 -0
  49. package/dist/src/strategies/version.d.ts +12 -0
  50. package/dist/src/strategies/version.js +22 -0
  51. package/dist/src/types/index.d.ts +51 -0
  52. package/dist/src/types/index.js +1 -0
  53. package/dist/tests/golden/golden.test.d.ts +1 -0
  54. package/dist/tests/golden/golden.test.js +26 -0
  55. package/dist/tests/unit/api.test.d.ts +1 -0
  56. package/dist/tests/unit/api.test.js +35 -0
  57. package/dist/tests/unit/bit-matrix.test.d.ts +1 -0
  58. package/dist/tests/unit/bit-matrix.test.js +46 -0
  59. package/dist/tests/unit/core.test.d.ts +1 -0
  60. package/dist/tests/unit/core.test.js +88 -0
  61. package/dist/tests/unit/encoding.test.d.ts +1 -0
  62. package/dist/tests/unit/encoding.test.js +63 -0
  63. package/dist/tests/unit/layout.test.d.ts +1 -0
  64. package/dist/tests/unit/layout.test.js +62 -0
  65. package/dist/tests/unit/masking.test.d.ts +1 -0
  66. package/dist/tests/unit/masking.test.js +31 -0
  67. package/package.json +45 -0
@@ -0,0 +1,68 @@
1
+ import { BitBuffer } from "../core/bit-buffer.js";
2
+ import { CHAR_COUNT_INDICATOR_BITS, getVersionGroupIndex, MODE_INDICATOR, } from "../spec/constants.js";
3
+ import { ECC_TABLE } from "../spec/tables.js";
4
+ import { encodeAlphanumeric, encodeByte, encodeNumeric } from "./encoders.js";
5
+ /**
6
+ * Combines encoded segments and applies padding to create the final data bitstream.
7
+ */
8
+ export function buildBitstream(segments, version, ecc) {
9
+ const buffer = new BitBuffer();
10
+ const groupIdx = getVersionGroupIndex(version);
11
+ // 1. Append segments
12
+ for (const segment of segments) {
13
+ // Mode Indicator (4 bits)
14
+ buffer.put(MODE_INDICATOR[segment.mode], 4);
15
+ // Character Count Indicator (length depends on mode and version group)
16
+ const charCountBits = CHAR_COUNT_INDICATOR_BITS[segment.mode][groupIdx];
17
+ const charCount = segment.count;
18
+ const maxCount = (1 << charCountBits) - 1;
19
+ if (charCount > maxCount) {
20
+ throw new Error(`Segment too long for mode ${segment.mode} at version ${version}`);
21
+ }
22
+ buffer.put(charCount, charCountBits);
23
+ // Data bits
24
+ switch (segment.mode) {
25
+ case "numeric":
26
+ encodeNumeric(segment.data, buffer);
27
+ break;
28
+ case "alphanumeric":
29
+ encodeAlphanumeric(segment.data, buffer);
30
+ break;
31
+ case "byte":
32
+ if (!segment.bytes) {
33
+ throw new Error("Encoding error: Byte segment missing bytes");
34
+ }
35
+ encodeByte(segment.bytes, buffer);
36
+ break;
37
+ }
38
+ }
39
+ // Calculate target capacity in bits
40
+ const versionInfo = ECC_TABLE[version]?.[ecc];
41
+ if (!versionInfo) {
42
+ throw new Error(`Invalid version/ECC combination: ${version}/${ecc}`);
43
+ }
44
+ const dataCapacityBits = (versionInfo.group1.numBlocks * versionInfo.group1.dataCodewords +
45
+ versionInfo.group2.numBlocks * versionInfo.group2.dataCodewords) *
46
+ 8;
47
+ if (buffer.length > dataCapacityBits) {
48
+ throw new Error(`Data too large for version ${version}-${ecc}`);
49
+ }
50
+ // 2. Terminator (up to 4 zero bits)
51
+ const terminatorLen = Math.min(4, dataCapacityBits - buffer.length);
52
+ if (terminatorLen > 0) {
53
+ buffer.put(0, terminatorLen);
54
+ }
55
+ // 3. Pad to byte boundary
56
+ buffer.bitByteAlign();
57
+ // 4. Pad with alternating bytes (0xEC, 0x11) until capacity is reached
58
+ const padCodewords = [0xec, 0x11];
59
+ let padIdx = 0;
60
+ while (buffer.length < dataCapacityBits) {
61
+ // Safe access because padIdx is modulo 2, so it's always 0 or 1.
62
+ // padCodewords has 2 elements.
63
+ const padVal = padCodewords[padIdx];
64
+ buffer.put(padVal ?? 0xec, 8); // Fallback purely for type safety, never reached
65
+ padIdx = (padIdx + 1) % 2;
66
+ }
67
+ return buffer;
68
+ }
@@ -0,0 +1,13 @@
1
+ import type { BitBuffer } from "../core/bit-buffer.js";
2
+ /**
3
+ * Numeric Mode: 10 bit / 3 characters
4
+ */
5
+ export declare function encodeNumeric(data: string, buffer: BitBuffer): void;
6
+ /**
7
+ * Alphanumeric Mode: 11 bit / 2 characters
8
+ */
9
+ export declare function encodeAlphanumeric(data: string, buffer: BitBuffer): void;
10
+ /**
11
+ * Byte Mode: UTF-8 (Default)
12
+ */
13
+ export declare function encodeByte(data: string | Uint8Array, buffer: BitBuffer): void;
@@ -0,0 +1,54 @@
1
+ import { ALPHANUMERIC_CHARSET } from "../spec/constants.js";
2
+ /**
3
+ * Numeric Mode: 10 bit / 3 characters
4
+ */
5
+ export function encodeNumeric(data, buffer) {
6
+ let i = 0;
7
+ while (i + 3 <= data.length) {
8
+ const num = parseInt(data.substring(i, i + 3), 10);
9
+ buffer.put(num, 10);
10
+ i += 3;
11
+ }
12
+ if (data.length - i === 2) {
13
+ buffer.put(parseInt(data.substring(i), 10), 7);
14
+ }
15
+ else if (data.length - i === 1) {
16
+ buffer.put(parseInt(data.substring(i), 10), 4);
17
+ }
18
+ }
19
+ /**
20
+ * Alphanumeric Mode: 11 bit / 2 characters
21
+ */
22
+ export function encodeAlphanumeric(data, buffer) {
23
+ let i = 0;
24
+ while (i + 2 <= data.length) {
25
+ const char1Str = data[i];
26
+ const char2Str = data[i + 1];
27
+ if (char1Str === undefined || char2Str === undefined)
28
+ throw new Error("Encoding error: Index out of bounds");
29
+ const char1 = ALPHANUMERIC_CHARSET.indexOf(char1Str);
30
+ const char2 = ALPHANUMERIC_CHARSET.indexOf(char2Str);
31
+ if (char1 === -1 || char2 === -1)
32
+ throw new Error("Encoding error: Invalid character");
33
+ buffer.put(char1 * 45 + char2, 11);
34
+ i += 2;
35
+ }
36
+ if (data.length - i === 1) {
37
+ const char1Str = data[i];
38
+ if (char1Str === undefined)
39
+ throw new Error("Encoding error: Index out of bounds");
40
+ const char1 = ALPHANUMERIC_CHARSET.indexOf(char1Str);
41
+ if (char1 === -1)
42
+ throw new Error("Encoding error: Invalid character");
43
+ buffer.put(char1, 6);
44
+ }
45
+ }
46
+ /**
47
+ * Byte Mode: UTF-8 (Default)
48
+ */
49
+ export function encodeByte(data, buffer) {
50
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
51
+ for (const byte of bytes) {
52
+ buffer.put(byte, 8);
53
+ }
54
+ }
@@ -0,0 +1,5 @@
1
+ import type { EccLevel } from "../types/index.js";
2
+ /**
3
+ * Splits data into blocks and interleaves them with ECC codewords.
4
+ */
5
+ export declare function interleave(data: Uint8Array, version: number, ecc: EccLevel): Uint8Array;
@@ -0,0 +1,49 @@
1
+ import { computeEcc } from "../core/rs.js";
2
+ import { ECC_TABLE } from "../spec/tables.js";
3
+ /**
4
+ * Splits data into blocks and interleaves them with ECC codewords.
5
+ */
6
+ export function interleave(data, version, ecc) {
7
+ const spec = ECC_TABLE[version]?.[ecc];
8
+ if (!spec)
9
+ throw new Error(`Invalid version/ECC: ${version}/${ecc}`);
10
+ const dataBlocks = [];
11
+ const eccBlocks = [];
12
+ let offset = 0;
13
+ const groups = [spec.group1, spec.group2];
14
+ for (const group of groups) {
15
+ for (let i = 0; i < group.numBlocks; i++) {
16
+ const block = data.slice(offset, offset + group.dataCodewords);
17
+ dataBlocks.push(block);
18
+ eccBlocks.push(computeEcc(block, spec.eccPerBlock));
19
+ offset += group.dataCodewords;
20
+ }
21
+ }
22
+ const totalDataLen = dataBlocks.reduce((acc, b) => acc + b.length, 0);
23
+ const totalEccLen = eccBlocks.length * spec.eccPerBlock;
24
+ const result = new Uint8Array(totalDataLen + totalEccLen);
25
+ let resOffset = 0;
26
+ // Interleave data codewords
27
+ const maxDataLen = Math.max(...dataBlocks.map((b) => b.length));
28
+ for (let i = 0; i < maxDataLen; i++) {
29
+ for (const block of dataBlocks) {
30
+ if (i < block.length) {
31
+ const val = block[i];
32
+ // block[i] is safe because i < block.length
33
+ if (val !== undefined) {
34
+ result[resOffset++] = val;
35
+ }
36
+ }
37
+ }
38
+ }
39
+ // Interleave ECC codewords
40
+ for (let i = 0; i < spec.eccPerBlock; i++) {
41
+ for (const block of eccBlocks) {
42
+ const val = block[i];
43
+ if (val === undefined)
44
+ throw new Error("Internal error: ECC block shorter than expected");
45
+ result[resOffset++] = val;
46
+ }
47
+ }
48
+ return result;
49
+ }
@@ -0,0 +1,12 @@
1
+ import type { QrMode } from "../types/index.js";
2
+ export interface Segment {
3
+ mode: Exclude<QrMode, "auto">;
4
+ data: string;
5
+ count: number;
6
+ bytes?: Uint8Array;
7
+ }
8
+ /**
9
+ * Analyzes the input string and splits it into the most appropriate QR modes.
10
+ * v1: Simple but effective greedy approach.
11
+ */
12
+ export declare function segmentize(input: string): Segment[];
@@ -0,0 +1,43 @@
1
+ import { ALPHANUMERIC_CHARSET } from "../spec/constants.js";
2
+ /**
3
+ * Analyzes the input string and splits it into the most appropriate QR modes.
4
+ * v1: Simple but effective greedy approach.
5
+ */
6
+ export function segmentize(input) {
7
+ if (input.length === 0)
8
+ return [];
9
+ const segments = [];
10
+ const firstChar = input[0];
11
+ if (firstChar === undefined)
12
+ return []; // Should be caught by input.length === 0 check
13
+ let currentMode = getBestMode(firstChar);
14
+ let startIndex = 0;
15
+ for (let i = 1; i < input.length; i++) {
16
+ const char = input[i];
17
+ if (char === undefined)
18
+ throw new Error("Segmentation error: index out of bounds");
19
+ const charMode = getBestMode(char);
20
+ if (charMode !== currentMode) {
21
+ segments.push({
22
+ mode: currentMode,
23
+ data: input.substring(startIndex, i),
24
+ count: i - startIndex,
25
+ });
26
+ currentMode = charMode;
27
+ startIndex = i;
28
+ }
29
+ }
30
+ segments.push({
31
+ mode: currentMode,
32
+ data: input.substring(startIndex),
33
+ count: input.length - startIndex,
34
+ });
35
+ return segments;
36
+ }
37
+ function getBestMode(char) {
38
+ if (/[0-9]/.test(char))
39
+ return "numeric";
40
+ if (ALPHANUMERIC_CHARSET.includes(char))
41
+ return "alphanumeric";
42
+ return "byte";
43
+ }
@@ -0,0 +1,3 @@
1
+ export { encode, encodeSafe } from "./api/index.js";
2
+ export { QrException } from "./core/error.js";
3
+ export * from "./types/index.js";
@@ -0,0 +1,3 @@
1
+ export { encode, encodeSafe } from "./api/index.js";
2
+ export { QrException } from "./core/error.js";
3
+ export * from "./types/index.js";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * QR Fonksiyonel Desenlerin Yerleştirilmesi (Finder, Timing, Alignment)
3
+ */
4
+ import type { Matrix } from "../matrix/bit-matrix.js";
5
+ /**
6
+ * Writes the 18-bit version information for versions >= 7.
7
+ */
8
+ export declare function writeVersionInfo(matrix: Matrix, version: number): void;
9
+ /**
10
+ * Tüm fonksiyonel desenleri matrise çizer
11
+ */
12
+ export declare function setupFunctionPatterns(matrix: Matrix, version: number): void;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * QR Fonksiyonel Desenlerin Yerleştirilmesi (Finder, Timing, Alignment)
3
+ */
4
+ import { getVersionInfo } from "../spec/bch.js";
5
+ import { ALIGNMENT_PATTERN_POSITIONS } from "../spec/tables.js";
6
+ /**
7
+ * Writes the 18-bit version information for versions >= 7.
8
+ */
9
+ export function writeVersionInfo(matrix, version) {
10
+ const info = getVersionInfo(version);
11
+ const size = matrix.size;
12
+ for (let i = 0; i < 18; i++) {
13
+ const bit = ((info >>> i) & 1) === 1;
14
+ const row = Math.floor(i / 3);
15
+ const col = i % 3;
16
+ // Bottom-left area (above the bottom-left finder pattern)
17
+ matrix.setReserved(row, size - 11 + col, bit);
18
+ // Top-right area (left of the top-right finder pattern)
19
+ matrix.setReserved(size - 11 + col, row, bit);
20
+ }
21
+ }
22
+ /**
23
+ * Tüm fonksiyonel desenleri matrise çizer
24
+ */
25
+ export function setupFunctionPatterns(matrix, version) {
26
+ const size = matrix.size;
27
+ // 1. Finder Patterns (Sol Üst, Sağ Üst, Sol Alt)
28
+ drawFinder(matrix, 0, 0);
29
+ drawFinder(matrix, size - 7, 0);
30
+ drawFinder(matrix, 0, size - 7);
31
+ // 2. Separators (Finder pattern'lerin etrafındaki beyaz boşluklar)
32
+ drawSeparators(matrix);
33
+ // 3. Timing Patterns (6. satır ve 6. sütun boyunca bir dolu bir boş çizgiler)
34
+ for (let i = 8; i < size - 8; i++) {
35
+ const bit = i % 2 === 0;
36
+ matrix.setReserved(6, i, bit); // Dikey
37
+ matrix.setReserved(i, 6, bit); // Yatay
38
+ }
39
+ // 4. Alignment Patterns (Versiyon >= 2, 5x5 desenler)
40
+ const coords = ALIGNMENT_PATTERN_POSITIONS[version] || [];
41
+ for (let i = 0; i < coords.length; i++) {
42
+ for (let j = 0; j < coords.length; j++) {
43
+ const x = coords[i];
44
+ const y = coords[j];
45
+ if (x === undefined || y === undefined)
46
+ throw new Error("Internal error: Alignment pattern coords undefined");
47
+ // Finder pattern alanlarıyla çakışmıyorsa çiz
48
+ if (!matrix.isReserved(x, y)) {
49
+ drawAlignment(matrix, x - 2, y - 2);
50
+ }
51
+ }
52
+ }
53
+ // 5. Dark Module (Sabit koyu modül)
54
+ matrix.setReserved(8, size - 8, true);
55
+ // 6. Format & Version Alanlarını Rezerve Et (Daha sonra BCH ile doldurulacak)
56
+ reserveFormatAreas(matrix);
57
+ if (version >= 7) {
58
+ reserveVersionAreas(matrix);
59
+ }
60
+ }
61
+ function drawFinder(matrix, ox, oy) {
62
+ for (let y = 0; y < 7; y++) {
63
+ for (let x = 0; x < 7; x++) {
64
+ const isDark = Math.max(Math.abs(x - 3), Math.abs(y - 3)) === 3 || // Dış 7x7 çerçeve
65
+ Math.max(Math.abs(x - 3), Math.abs(y - 3)) <= 1; // İç 3x3 kare
66
+ matrix.setReserved(ox + x, oy + y, isDark);
67
+ }
68
+ }
69
+ }
70
+ function drawAlignment(matrix, ox, oy) {
71
+ for (let y = 0; y < 5; y++) {
72
+ for (let x = 0; x < 5; x++) {
73
+ const isDark = Math.max(Math.abs(x - 2), Math.abs(y - 2)) === 2 || // 5x5 çerçeve
74
+ (x === 2 && y === 2); // Merkez nokta
75
+ matrix.setReserved(ox + x, oy + y, isDark);
76
+ }
77
+ }
78
+ }
79
+ function drawSeparators(matrix) {
80
+ const size = matrix.size;
81
+ // Sol Üst
82
+ for (let i = 0; i < 8; i++) {
83
+ matrix.setReserved(7, i, false);
84
+ matrix.setReserved(i, 7, false);
85
+ }
86
+ // Sağ Üst
87
+ for (let i = 0; i < 8; i++) {
88
+ matrix.setReserved(size - 8, i, false);
89
+ matrix.setReserved(size - 8 + i, 7, false);
90
+ }
91
+ // Sol Alt
92
+ for (let i = 0; i < 8; i++) {
93
+ matrix.setReserved(7, size - 8 + i, false);
94
+ matrix.setReserved(i, size - 8, false);
95
+ }
96
+ }
97
+ function reserveFormatAreas(matrix) {
98
+ const size = matrix.size;
99
+ // Format bitleri alanlarını rezerve et (Timing çizgisi hariç)
100
+ for (let i = 0; i <= 8; i++) {
101
+ if (i !== 6)
102
+ matrix.setReserved(i, 8, false);
103
+ if (i !== 6)
104
+ matrix.setReserved(8, i, false);
105
+ }
106
+ for (let i = 0; i < 8; i++)
107
+ matrix.setReserved(size - 1 - i, 8, false);
108
+ for (let i = 0; i < 7; i++)
109
+ matrix.setReserved(8, size - 1 - i, false);
110
+ }
111
+ function reserveVersionAreas(matrix) {
112
+ const size = matrix.size;
113
+ // Versiyon >= 7 için 3x6 alanlar
114
+ for (let i = 0; i < 6; i++) {
115
+ for (let j = 0; j < 3; j++) {
116
+ matrix.setReserved(size - 11 + j, i, false);
117
+ matrix.setReserved(i, size - 11 + j, false);
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,5 @@
1
+ import type { Matrix } from "../matrix/bit-matrix.js";
2
+ /**
3
+ * Places bits into the matrix in a zigzag pattern, skipping reserved modules.
4
+ */
5
+ export declare function zigzag(matrix: Matrix, data: Uint8Array): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Places bits into the matrix in a zigzag pattern, skipping reserved modules.
3
+ */
4
+ export function zigzag(matrix, data) {
5
+ const size = matrix.size;
6
+ let bitIndex = 0;
7
+ const totalBits = data.length * 8;
8
+ for (let right = size - 1; right > 0; right -= 2) {
9
+ if (right === 6)
10
+ right--;
11
+ const upward = ((right + 1) / 2) % 2 !== 0;
12
+ for (let i = 0; i < size; i++) {
13
+ const y = upward ? size - 1 - i : i;
14
+ for (let j = 0; j < 2; j++) {
15
+ const x = right - j;
16
+ if (!matrix.isReserved(x, y) && bitIndex < totalBits) {
17
+ const byteIndex = Math.floor(bitIndex / 8);
18
+ const byte = data[byteIndex];
19
+ if (byte === undefined)
20
+ throw new Error(`Internal error: Data missing at index ${byteIndex}`);
21
+ const bit = (byte >>> (7 - (bitIndex % 8))) & 1;
22
+ matrix.set(x, y, bit === 1);
23
+ bitIndex++;
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ import type { MaskId } from "../types/index.js";
2
+ export type MaskFn = (x: number, y: number) => boolean;
3
+ /**
4
+ * Standard QR mask patterns (0-7)
5
+ */
6
+ export declare const MASK_PATTERNS: Record<MaskId, MaskFn>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Standard QR mask patterns (0-7)
3
+ */
4
+ export const MASK_PATTERNS = {
5
+ 0: (x, y) => (x + y) % 2 === 0,
6
+ 1: (_x, y) => y % 2 === 0,
7
+ 2: (x, _y) => x % 3 === 0,
8
+ 3: (x, y) => (x + y) % 3 === 0,
9
+ 4: (x, y) => (Math.floor(y / 2) + Math.floor(x / 3)) % 2 === 0,
10
+ 5: (x, y) => ((x * y) % 2) + ((x * y) % 3) === 0,
11
+ 6: (x, y) => (((x * y) % 2) + ((x * y) % 3)) % 2 === 0,
12
+ 7: (x, y) => (((x + y) % 2) + ((x * y) % 3)) % 2 === 0,
13
+ };
@@ -0,0 +1,5 @@
1
+ import type { Matrix } from "../matrix/bit-matrix.js";
2
+ /**
3
+ * Calculates the penalty score for a matrix based on N1-N4 rules.
4
+ */
5
+ export declare function calculatePenalty(matrix: Matrix): number;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Calculates the penalty score for a matrix based on N1-N4 rules.
3
+ */
4
+ export function calculatePenalty(matrix) {
5
+ const size = matrix.size;
6
+ const data = matrix._data; // Direct access optimization
7
+ let penalty = 0;
8
+ let darkCount = 0;
9
+ // Process Rows: N1, N3, N2, N4 (partial)
10
+ for (let y = 0; y < size; y++) {
11
+ let rowRun = 0;
12
+ let lastBit = -1;
13
+ // History scalars
14
+ let h0 = 0, h1 = 0, h2 = 0, h3 = 0, h4 = 0;
15
+ for (let x = 0; x < size; x++) {
16
+ const idx = y * size + x;
17
+ const val = data[idx] ?? 0;
18
+ const bit = val & 1;
19
+ if (bit === 1)
20
+ darkCount++;
21
+ // N2 (2x2)
22
+ if (x < size - 1 && y < size - 1) {
23
+ const right = (data[idx + 1] ?? 0) & 1;
24
+ const down = (data[idx + size] ?? 0) & 1;
25
+ const diag = (data[idx + size + 1] ?? 0) & 1;
26
+ if (bit === right && bit === down && bit === diag) {
27
+ penalty += 3;
28
+ }
29
+ }
30
+ // N1 & N3
31
+ if (bit === lastBit) {
32
+ rowRun++;
33
+ }
34
+ else {
35
+ if (rowRun >= 5)
36
+ penalty += 3 + (rowRun - 5);
37
+ // Shift history
38
+ if (lastBit !== -1) {
39
+ // Skip initial run
40
+ h0 = h1;
41
+ h1 = h2;
42
+ h2 = h3;
43
+ h3 = h4;
44
+ h4 = rowRun;
45
+ // Check N3
46
+ if (lastBit === 1 && checkRatioScalar(h0, h1, h2, h3, h4)) {
47
+ if (checkWhiteSpace(data, size, x, y, 1, 0, h0, h1, h2, h3, h4)) {
48
+ penalty += 40;
49
+ }
50
+ }
51
+ }
52
+ rowRun = 1;
53
+ lastBit = bit;
54
+ }
55
+ }
56
+ // End of row
57
+ if (rowRun >= 5)
58
+ penalty += 3 + (rowRun - 5);
59
+ if (lastBit !== -1) {
60
+ h0 = h1;
61
+ h1 = h2;
62
+ h2 = h3;
63
+ h3 = h4;
64
+ h4 = rowRun;
65
+ if (lastBit === 1 && checkRatioScalar(h0, h1, h2, h3, h4)) {
66
+ if (checkWhiteSpace(data, size, size, y, 1, 0, h0, h1, h2, h3, h4)) {
67
+ penalty += 40;
68
+ }
69
+ }
70
+ }
71
+ }
72
+ // Process Columns: N1
73
+ for (let x = 0; x < size; x++) {
74
+ let colRun = 0;
75
+ let lastBit = -1;
76
+ let h0 = 0, h1 = 0, h2 = 0, h3 = 0, h4 = 0;
77
+ for (let y = 0; y < size; y++) {
78
+ const idx = y * size + x;
79
+ const bit = (data[idx] ?? 0) & 1;
80
+ if (bit === lastBit) {
81
+ colRun++;
82
+ }
83
+ else {
84
+ if (colRun >= 5)
85
+ penalty += 3 + (colRun - 5);
86
+ if (lastBit !== -1) {
87
+ h0 = h1;
88
+ h1 = h2;
89
+ h2 = h3;
90
+ h3 = h4;
91
+ h4 = colRun;
92
+ if (lastBit === 1 && checkRatioScalar(h0, h1, h2, h3, h4)) {
93
+ if (checkWhiteSpace(data, size, x, y, 0, 1, h0, h1, h2, h3, h4)) {
94
+ penalty += 40;
95
+ }
96
+ }
97
+ }
98
+ colRun = 1;
99
+ lastBit = bit;
100
+ }
101
+ }
102
+ if (colRun >= 5)
103
+ penalty += 3 + (colRun - 5);
104
+ if (lastBit !== -1) {
105
+ h0 = h1;
106
+ h1 = h2;
107
+ h2 = h3;
108
+ h3 = h4;
109
+ h4 = colRun;
110
+ if (lastBit === 1 && checkRatioScalar(h0, h1, h2, h3, h4)) {
111
+ if (checkWhiteSpace(data, size, x, size, 0, 1, h0, h1, h2, h3, h4)) {
112
+ penalty += 40;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ const total = size * size;
118
+ const ratio = (darkCount * 20) / total;
119
+ const _diff = Math.abs(ratio - 10);
120
+ const percentage = (darkCount / total) * 100;
121
+ const deviation = Math.abs(percentage - 50);
122
+ penalty += Math.floor(deviation / 5) * 10;
123
+ return penalty;
124
+ }
125
+ function checkRatioScalar(h0, h1, h2, h3, h4) {
126
+ if (h0 === 0 || h1 === 0 || h2 === 0 || h3 === 0 || h4 === 0)
127
+ return false;
128
+ const total = h0 + h1 + h2 + h3 + h4;
129
+ if (total < 7)
130
+ return false;
131
+ const moduleSize = total / 7;
132
+ const maxVariance = moduleSize / 2;
133
+ return (Math.abs(moduleSize - h0) < maxVariance &&
134
+ Math.abs(moduleSize - h1) < maxVariance &&
135
+ Math.abs(3 * moduleSize - h2) < 3 * maxVariance &&
136
+ Math.abs(moduleSize - h3) < maxVariance &&
137
+ Math.abs(moduleSize - h4) < maxVariance);
138
+ }
139
+ function checkWhiteSpace(data, size, x, y, dx, dy, h0, h1, h2, h3, h4) {
140
+ const totalLen = h0 + h1 + h2 + h3 + h4;
141
+ let hasRightWhite = true;
142
+ for (let i = 0; i < 4; i++) {
143
+ const px = x + dx * i;
144
+ const py = y + dy * i;
145
+ if (px >= size || py >= size)
146
+ break;
147
+ const idx = py * size + px;
148
+ if (((data[idx] ?? 0) & 1) === 1) {
149
+ hasRightWhite = false;
150
+ break;
151
+ }
152
+ }
153
+ if (hasRightWhite)
154
+ return true; // Optimization: If one side is white, we are good?
155
+ // Wait. "Preceded OR followed by". Yes. If right is white, we satisfy condition.
156
+ // We don't need to check left.
157
+ // RETURN TRUE EARLY!
158
+ let hasLeftWhite = true;
159
+ const startX = x - dx * totalLen;
160
+ const startY = y - dy * totalLen;
161
+ for (let i = 1; i <= 4; i++) {
162
+ const px = startX - dx * i;
163
+ const py = startY - dy * i;
164
+ if (px < 0 || py < 0)
165
+ break;
166
+ const idx = py * size + px;
167
+ if (((data[idx] ?? 0) & 1) === 1) {
168
+ hasLeftWhite = false;
169
+ break;
170
+ }
171
+ }
172
+ return hasLeftWhite;
173
+ }
@@ -0,0 +1,12 @@
1
+ import { Matrix } from "../matrix/bit-matrix.js";
2
+ import type { EccLevel, MaskId } from "../types/index.js";
3
+ /**
4
+ * Evaluates all 8 masks and applies the one with the lowest penalty.
5
+ * Implements the deterministic tie-break (lowest ID).
6
+ */
7
+ export declare function selectAndApplyBestMask(matrix: Matrix, ecc: EccLevel): {
8
+ mask: MaskId;
9
+ penalties: number[];
10
+ };
11
+ export declare function applyMask(matrix: Matrix, maskId: MaskId): void;
12
+ export declare function writeFormatInformation(matrix: Matrix, ecc: EccLevel, maskId: MaskId): void;