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.
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/bench/bench.d.ts +1 -0
- package/dist/bench/bench.js +38 -0
- package/dist/src/api/index.d.ts +9 -0
- package/dist/src/api/index.js +224 -0
- package/dist/src/core/bit-buffer.d.ts +11 -0
- package/dist/src/core/bit-buffer.js +53 -0
- package/dist/src/core/error.d.ts +8 -0
- package/dist/src/core/error.js +16 -0
- package/dist/src/core/gf.d.ts +4 -0
- package/dist/src/core/gf.js +52 -0
- package/dist/src/core/rs.d.ts +1 -0
- package/dist/src/core/rs.js +60 -0
- package/dist/src/encoding/bitstream.d.ts +7 -0
- package/dist/src/encoding/bitstream.js +68 -0
- package/dist/src/encoding/encoders.d.ts +13 -0
- package/dist/src/encoding/encoders.js +54 -0
- package/dist/src/encoding/interleave.d.ts +5 -0
- package/dist/src/encoding/interleave.js +49 -0
- package/dist/src/encoding/segmentation.d.ts +12 -0
- package/dist/src/encoding/segmentation.js +43 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/layout/patterns.d.ts +12 -0
- package/dist/src/layout/patterns.js +120 -0
- package/dist/src/mapping/zigzag.d.ts +5 -0
- package/dist/src/mapping/zigzag.js +28 -0
- package/dist/src/mask/patterns.d.ts +6 -0
- package/dist/src/mask/patterns.js +13 -0
- package/dist/src/mask/penalty.d.ts +5 -0
- package/dist/src/mask/penalty.js +173 -0
- package/dist/src/mask/selection.d.ts +12 -0
- package/dist/src/mask/selection.js +178 -0
- package/dist/src/matrix/bit-matrix.d.ts +46 -0
- package/dist/src/matrix/bit-matrix.js +139 -0
- package/dist/src/spec/bch.d.ts +11 -0
- package/dist/src/spec/bch.js +30 -0
- package/dist/src/spec/constants.d.ts +40 -0
- package/dist/src/spec/constants.js +37 -0
- package/dist/src/spec/tables.d.ts +20 -0
- package/dist/src/spec/tables.js +1099 -0
- package/dist/src/strategies/index.d.ts +3 -0
- package/dist/src/strategies/index.js +3 -0
- package/dist/src/strategies/mask.d.ts +16 -0
- package/dist/src/strategies/mask.js +9 -0
- package/dist/src/strategies/segmentation.d.ts +12 -0
- package/dist/src/strategies/segmentation.js +41 -0
- package/dist/src/strategies/version.d.ts +12 -0
- package/dist/src/strategies/version.js +22 -0
- package/dist/src/types/index.d.ts +51 -0
- package/dist/src/types/index.js +1 -0
- package/dist/tests/golden/golden.test.d.ts +1 -0
- package/dist/tests/golden/golden.test.js +26 -0
- package/dist/tests/unit/api.test.d.ts +1 -0
- package/dist/tests/unit/api.test.js +35 -0
- package/dist/tests/unit/bit-matrix.test.d.ts +1 -0
- package/dist/tests/unit/bit-matrix.test.js +46 -0
- package/dist/tests/unit/core.test.d.ts +1 -0
- package/dist/tests/unit/core.test.js +88 -0
- package/dist/tests/unit/encoding.test.d.ts +1 -0
- package/dist/tests/unit/encoding.test.js +63 -0
- package/dist/tests/unit/layout.test.d.ts +1 -0
- package/dist/tests/unit/layout.test.js +62 -0
- package/dist/tests/unit/masking.test.d.ts +1 -0
- package/dist/tests/unit/masking.test.js +31 -0
- 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,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,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,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,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,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;
|