qr-core 1.0.0 → 1.0.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/README.md +3 -3
- package/dist/{src/api → api}/index.js +3 -3
- package/dist/{src/mapping → mapping}/zigzag.js +3 -2
- package/dist/{src/mask → mask}/selection.js +3 -1
- package/dist/{src/matrix → matrix}/bit-matrix.js +1 -3
- package/package.json +9 -6
- package/dist/bench/bench.d.ts +0 -1
- package/dist/bench/bench.js +0 -38
- package/dist/tests/golden/golden.test.d.ts +0 -1
- package/dist/tests/golden/golden.test.js +0 -26
- package/dist/tests/unit/api.test.d.ts +0 -1
- package/dist/tests/unit/api.test.js +0 -35
- package/dist/tests/unit/bit-matrix.test.d.ts +0 -1
- package/dist/tests/unit/bit-matrix.test.js +0 -46
- package/dist/tests/unit/core.test.d.ts +0 -1
- package/dist/tests/unit/core.test.js +0 -88
- package/dist/tests/unit/encoding.test.d.ts +0 -1
- package/dist/tests/unit/encoding.test.js +0 -63
- package/dist/tests/unit/layout.test.d.ts +0 -1
- package/dist/tests/unit/layout.test.js +0 -62
- package/dist/tests/unit/masking.test.d.ts +0 -1
- package/dist/tests/unit/masking.test.js +0 -31
- /package/dist/{src/api → api}/index.d.ts +0 -0
- /package/dist/{src/core → core}/bit-buffer.d.ts +0 -0
- /package/dist/{src/core → core}/bit-buffer.js +0 -0
- /package/dist/{src/core → core}/error.d.ts +0 -0
- /package/dist/{src/core → core}/error.js +0 -0
- /package/dist/{src/core → core}/gf.d.ts +0 -0
- /package/dist/{src/core → core}/gf.js +0 -0
- /package/dist/{src/core → core}/rs.d.ts +0 -0
- /package/dist/{src/core → core}/rs.js +0 -0
- /package/dist/{src/encoding → encoding}/bitstream.d.ts +0 -0
- /package/dist/{src/encoding → encoding}/bitstream.js +0 -0
- /package/dist/{src/encoding → encoding}/encoders.d.ts +0 -0
- /package/dist/{src/encoding → encoding}/encoders.js +0 -0
- /package/dist/{src/encoding → encoding}/interleave.d.ts +0 -0
- /package/dist/{src/encoding → encoding}/interleave.js +0 -0
- /package/dist/{src/encoding → encoding}/segmentation.d.ts +0 -0
- /package/dist/{src/encoding → encoding}/segmentation.js +0 -0
- /package/dist/{src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{src/index.js → index.js} +0 -0
- /package/dist/{src/layout → layout}/patterns.d.ts +0 -0
- /package/dist/{src/layout → layout}/patterns.js +0 -0
- /package/dist/{src/mapping → mapping}/zigzag.d.ts +0 -0
- /package/dist/{src/mask → mask}/patterns.d.ts +0 -0
- /package/dist/{src/mask → mask}/patterns.js +0 -0
- /package/dist/{src/mask → mask}/penalty.d.ts +0 -0
- /package/dist/{src/mask → mask}/penalty.js +0 -0
- /package/dist/{src/mask → mask}/selection.d.ts +0 -0
- /package/dist/{src/matrix → matrix}/bit-matrix.d.ts +0 -0
- /package/dist/{src/spec → spec}/bch.d.ts +0 -0
- /package/dist/{src/spec → spec}/bch.js +0 -0
- /package/dist/{src/spec → spec}/constants.d.ts +0 -0
- /package/dist/{src/spec → spec}/constants.js +0 -0
- /package/dist/{src/spec → spec}/tables.d.ts +0 -0
- /package/dist/{src/spec → spec}/tables.js +0 -0
- /package/dist/{src/strategies → strategies}/index.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/index.js +0 -0
- /package/dist/{src/strategies → strategies}/mask.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/mask.js +0 -0
- /package/dist/{src/strategies → strategies}/segmentation.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/segmentation.js +0 -0
- /package/dist/{src/strategies → strategies}/version.d.ts +0 -0
- /package/dist/{src/strategies → strategies}/version.js +0 -0
- /package/dist/{src/types → types}/index.d.ts +0 -0
- /package/dist/{src/types → types}/index.js +0 -0
package/README.md
CHANGED
|
@@ -110,9 +110,9 @@ Run `npm run bench` to reproduce.
|
|
|
110
110
|
|
|
111
111
|
| Scenario | Description | p50 (ms) | p95 (ms) |
|
|
112
112
|
| :--- | :--- | :---: | :---: |
|
|
113
|
-
| **Small** | Auto version/mask, ecc M ("HELLO WORLD") | **0.
|
|
114
|
-
| **Medium** | Auto version/mask, ecc M (~500 bytes URL‑ish payload) | **1.
|
|
115
|
-
| **Large** | Auto version/mask, ecc L (3000 bytes) | **
|
|
113
|
+
| **Small** | Auto version/mask, ecc M ("HELLO WORLD") | **0.074** | **0.138** |
|
|
114
|
+
| **Medium** | Auto version/mask, ecc M (~500 bytes URL‑ish payload) | **1.275** | **1.527** |
|
|
115
|
+
| **Large** | Auto version/mask, ecc L (3000 bytes) | **4.082** | **4.536** |
|
|
116
116
|
|
|
117
117
|
> Benchmarks captured on 2026‑01‑31 with Node v24.7.0 (darwin arm64). Goal from RFC: version‑40 near‑capacity with auto‑mask < 5ms — **met**.
|
|
118
118
|
|
|
@@ -153,9 +153,7 @@ function validateOptions(options, ecc, charset, quietZone) {
|
|
|
153
153
|
throw new QrException("INVALID_OPTIONS", `Invalid charset: ${charset}`);
|
|
154
154
|
}
|
|
155
155
|
if (options.mode !== undefined && options.mode !== "auto") {
|
|
156
|
-
if (options.mode !== "numeric" &&
|
|
157
|
-
options.mode !== "alphanumeric" &&
|
|
158
|
-
options.mode !== "byte") {
|
|
156
|
+
if (options.mode !== "numeric" && options.mode !== "alphanumeric" && options.mode !== "byte") {
|
|
159
157
|
throw new QrException("INVALID_OPTIONS", `Invalid mode: ${options.mode}`);
|
|
160
158
|
}
|
|
161
159
|
}
|
|
@@ -196,6 +194,8 @@ function prepareSegments(segments, charset, strict) {
|
|
|
196
194
|
const bytes = encodeByteString(segment.data, charset, strict);
|
|
197
195
|
return { ...segment, bytes, count: bytes.length };
|
|
198
196
|
}
|
|
197
|
+
default:
|
|
198
|
+
throw new QrException("INTERNAL_INVARIANT_BROKEN", `Unknown segment mode: ${segment.mode}`);
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
201
|
}
|
|
@@ -5,10 +5,11 @@ export function zigzag(matrix, data) {
|
|
|
5
5
|
const size = matrix.size;
|
|
6
6
|
let bitIndex = 0;
|
|
7
7
|
const totalBits = data.length * 8;
|
|
8
|
-
|
|
8
|
+
const last = size - 1;
|
|
9
|
+
for (let right = last, pairIndex = 0; right > 0; right -= 2, pairIndex++) {
|
|
9
10
|
if (right === 6)
|
|
10
11
|
right--;
|
|
11
|
-
const upward =
|
|
12
|
+
const upward = pairIndex % 2 === 0;
|
|
12
13
|
for (let i = 0; i < size; i++) {
|
|
13
14
|
const y = upward ? size - 1 - i : i;
|
|
14
15
|
for (let j = 0; j < 2; j++) {
|
|
@@ -141,7 +141,7 @@ export function writeFormatInformation(matrix, ecc, maskId) {
|
|
|
141
141
|
const formatBits = getFormatInfo(ecc, maskId);
|
|
142
142
|
const size = matrix.size;
|
|
143
143
|
for (let i = 0; i < 15; i++) {
|
|
144
|
-
const bit = (formatBits >>> i) & 1;
|
|
144
|
+
const bit = (formatBits >>> (14 - i)) & 1;
|
|
145
145
|
// Top-left
|
|
146
146
|
let x;
|
|
147
147
|
let y;
|
|
@@ -168,6 +168,8 @@ export function writeFormatInformation(matrix, ecc, maskId) {
|
|
|
168
168
|
if (i < 8) {
|
|
169
169
|
x2 = 8;
|
|
170
170
|
y2 = size - 1 - i;
|
|
171
|
+
if (y2 === size - 8)
|
|
172
|
+
continue; // skip dark module
|
|
171
173
|
}
|
|
172
174
|
else {
|
|
173
175
|
x2 = size - 15 + i;
|
|
@@ -88,9 +88,7 @@ export class Matrix {
|
|
|
88
88
|
toRows() {
|
|
89
89
|
const rows = [];
|
|
90
90
|
for (let y = 0; y < this.size; y++) {
|
|
91
|
-
rows.push(this.data
|
|
92
|
-
.slice(y * this.size, (y + 1) * this.size)
|
|
93
|
-
.map((v) => (v & 1)));
|
|
91
|
+
rows.push(this.data.slice(y * this.size, (y + 1) * this.size).map((v) => (v & 1)));
|
|
94
92
|
}
|
|
95
93
|
return rows;
|
|
96
94
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qr-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Deterministic, zero-dependency QR Code Model 2 encoder (matrix-only)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
6
|
+
"main": "./dist/src/index.js",
|
|
7
|
+
"types": "./dist/src/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"
|
|
10
|
+
"types": "./dist/src/index.d.ts",
|
|
11
|
+
"import": "./dist/src/index.js",
|
|
12
|
+
"default": "./dist/src/index.js"
|
|
12
13
|
}
|
|
13
14
|
},
|
|
14
15
|
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
16
17
|
"prepublishOnly": "npm run build",
|
|
17
18
|
"test": "vitest",
|
|
18
19
|
"lint": "biome check src tests bench",
|
|
@@ -39,6 +40,8 @@
|
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@biomejs/biome": "^2.3.13",
|
|
41
42
|
"@types/node": "^25.1.0",
|
|
43
|
+
"jsqr": "^1.4.0",
|
|
44
|
+
"pngjs": "^7.0.0",
|
|
42
45
|
"typescript": "^5.9.3",
|
|
43
46
|
"vitest": "^3.2.4"
|
|
44
47
|
}
|
package/dist/bench/bench.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/bench/bench.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { encode } from "../src/index.js";
|
|
2
|
-
function bench(name, fn, iterations = 100) {
|
|
3
|
-
// Warmup
|
|
4
|
-
for (let i = 0; i < 10; i++)
|
|
5
|
-
fn();
|
|
6
|
-
const times = [];
|
|
7
|
-
for (let i = 0; i < iterations; i++) {
|
|
8
|
-
const start = performance.now();
|
|
9
|
-
fn();
|
|
10
|
-
const end = performance.now();
|
|
11
|
-
times.push(end - start);
|
|
12
|
-
}
|
|
13
|
-
times.sort((a, b) => a - b);
|
|
14
|
-
const p50 = times[Math.floor(times.length * 0.5)] || 0;
|
|
15
|
-
const p95 = times[Math.floor(times.length * 0.95)] || 0;
|
|
16
|
-
const avg = times.reduce((a, b) => a + b, 0) / times.length;
|
|
17
|
-
console.log(`${name}:`);
|
|
18
|
-
console.log(` p50: ${p50.toFixed(3)}ms`);
|
|
19
|
-
console.log(` p95: ${p95.toFixed(3)}ms`);
|
|
20
|
-
console.log(` avg: ${avg.toFixed(3)}ms`);
|
|
21
|
-
}
|
|
22
|
-
async function runBenchmarks() {
|
|
23
|
-
console.log("Running benchmarks...\n");
|
|
24
|
-
const smallPayload = "HELLO WORLD";
|
|
25
|
-
bench("Small Payload (auto version/mask, ecc M)", () => {
|
|
26
|
-
encode(smallPayload, { ecc: "M" });
|
|
27
|
-
});
|
|
28
|
-
const mediumPayload = "https://example.com/search?q=" + "A".repeat(480);
|
|
29
|
-
bench("Medium Payload (~500 bytes, auto version/mask, ecc M)", () => {
|
|
30
|
-
encode(mediumPayload, { ecc: "M" });
|
|
31
|
-
});
|
|
32
|
-
const largePayload = "A".repeat(3000);
|
|
33
|
-
// Version 40-L holds 3181 data codewords; 3000 bytes fits.
|
|
34
|
-
bench("Large Payload (3000 bytes, auto version/mask, ecc L)", () => {
|
|
35
|
-
encode(largePayload, { ecc: "L" });
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
runBenchmarks();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { encode } from "../../src/api/index";
|
|
5
|
-
const vectorsDir = path.join(__dirname, "../vectors");
|
|
6
|
-
const vectorFiles = fs
|
|
7
|
-
.readdirSync(vectorsDir)
|
|
8
|
-
.filter((f) => f.endsWith(".json"));
|
|
9
|
-
let vectors = [];
|
|
10
|
-
for (const file of vectorFiles) {
|
|
11
|
-
const content = JSON.parse(fs.readFileSync(path.join(vectorsDir, file), "utf-8"));
|
|
12
|
-
vectors = vectors.concat(content);
|
|
13
|
-
}
|
|
14
|
-
describe("Golden Vectors", () => {
|
|
15
|
-
for (const vec of vectors) {
|
|
16
|
-
it(`should match vector: ${vec.name}`, () => {
|
|
17
|
-
// Normalize inputs
|
|
18
|
-
// biome-ignore lint/suspicious/noExplicitAny: generic casting for options
|
|
19
|
-
const opts = { ...vec.options };
|
|
20
|
-
const result = encode(vec.input.value, opts);
|
|
21
|
-
expect(result.version).toBe(vec.expected.version);
|
|
22
|
-
expect(result.size).toBe(vec.expected.size);
|
|
23
|
-
// In a real golden test, we would compare the entire matrix hash/bits
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { encode, encodeSafe } from "../../src/api/index";
|
|
3
|
-
describe("Public API", () => {
|
|
4
|
-
it("should encode simple string", () => {
|
|
5
|
-
const result = encode("Hello World");
|
|
6
|
-
expect(result.version).toBeGreaterThan(0);
|
|
7
|
-
expect(result.size).toBeGreaterThan(0);
|
|
8
|
-
expect(result.matrix).toBeDefined();
|
|
9
|
-
});
|
|
10
|
-
it("should allow forcing version", () => {
|
|
11
|
-
const result = encode("A", { version: 5 });
|
|
12
|
-
expect(result.version).toBe(5);
|
|
13
|
-
expect(result.size).toBe(21 + 4 * (5 - 1));
|
|
14
|
-
});
|
|
15
|
-
it("should throw if data too large for fixed version", () => {
|
|
16
|
-
// Version 1 can only hold ~17 bytes (Ecc L)
|
|
17
|
-
// "A" * 100 needs version ~4
|
|
18
|
-
const largeData = "A".repeat(100);
|
|
19
|
-
// Expect message to contain "Data requires version"
|
|
20
|
-
expect(() => encode(largeData, { version: 1 })).toThrow(/Data requires version/);
|
|
21
|
-
});
|
|
22
|
-
it("should safe encode without throwing", () => {
|
|
23
|
-
const largeData = "A".repeat(100);
|
|
24
|
-
const result = encodeSafe(largeData, { version: 1 });
|
|
25
|
-
expect(result.ok).toBe(false);
|
|
26
|
-
if (!result.ok) {
|
|
27
|
-
expect(result.error.code).toBe("DATA_TOO_LARGE");
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
it("should support Uint8Array input", () => {
|
|
31
|
-
const input = new Uint8Array([65, 66, 67]);
|
|
32
|
-
const result = encode(input, { mode: "byte" });
|
|
33
|
-
expect(result.version).toBe(1);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { Matrix } from "../../src/matrix/bit-matrix";
|
|
3
|
-
describe("BitMatrix", () => {
|
|
4
|
-
it("should initialize with correct size and default values", () => {
|
|
5
|
-
const size = 21;
|
|
6
|
-
const matrix = new Matrix(size);
|
|
7
|
-
expect(matrix.size).toBe(size);
|
|
8
|
-
expect(matrix.get(0, 0)).toBe(0);
|
|
9
|
-
expect(matrix.get(size - 1, size - 1)).toBe(0);
|
|
10
|
-
});
|
|
11
|
-
it("should set and get values", () => {
|
|
12
|
-
const matrix = new Matrix(21);
|
|
13
|
-
matrix.set(5, 5, true);
|
|
14
|
-
expect(matrix.get(5, 5)).toBe(1);
|
|
15
|
-
matrix.set(5, 5, false);
|
|
16
|
-
expect(matrix.get(5, 5)).toBe(0);
|
|
17
|
-
});
|
|
18
|
-
it("should handle reserved modules", () => {
|
|
19
|
-
const matrix = new Matrix(21);
|
|
20
|
-
matrix.setReserved(10, 10, true);
|
|
21
|
-
expect(matrix.isReserved(10, 10)).toBe(true);
|
|
22
|
-
expect(matrix.get(10, 10)).toBe(1);
|
|
23
|
-
// Normal set should not overwrite reserved
|
|
24
|
-
matrix.set(10, 10, false);
|
|
25
|
-
expect(matrix.get(10, 10)).toBe(1);
|
|
26
|
-
});
|
|
27
|
-
it("should throw on out of bounds access", () => {
|
|
28
|
-
const matrix = new Matrix(21);
|
|
29
|
-
expect(() => matrix.get(-1, 0)).toThrow();
|
|
30
|
-
expect(() => matrix.get(0, 21)).toThrow();
|
|
31
|
-
expect(() => matrix.set(21, 0, true)).toThrow();
|
|
32
|
-
expect(() => matrix.setReserved(0, -1, true)).toThrow();
|
|
33
|
-
});
|
|
34
|
-
it("should serialize to packed bytes correct MSB-first order", () => {
|
|
35
|
-
// Size 9 -> 2 bytes per row
|
|
36
|
-
// Row 0: 10000000 1.......
|
|
37
|
-
const matrix = new Matrix(9);
|
|
38
|
-
matrix.set(0, 0, true); // First bit
|
|
39
|
-
matrix.set(8, 0, true); // 9th bit (first bit of second byte)
|
|
40
|
-
const packed = matrix.toPacked();
|
|
41
|
-
expect(packed.length).toBe(9 * 2);
|
|
42
|
-
// Row 0
|
|
43
|
-
expect(packed[0]).toBe(0x80); // 10000000
|
|
44
|
-
expect(packed[1]).toBe(0x80); // 10000000 (padded)
|
|
45
|
-
});
|
|
46
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { BitBuffer } from "../../src/core/bit-buffer";
|
|
3
|
-
import { EXP, inv, LOG, mul } from "../../src/core/gf";
|
|
4
|
-
import { computeEcc } from "../../src/core/rs";
|
|
5
|
-
describe("GF(256)", () => {
|
|
6
|
-
it("should have correct table sizes", () => {
|
|
7
|
-
expect(EXP.length).toBe(512);
|
|
8
|
-
expect(LOG.length).toBe(256);
|
|
9
|
-
});
|
|
10
|
-
it("EXP[0] should be 1", () => {
|
|
11
|
-
expect(EXP[0]).toBe(1);
|
|
12
|
-
expect(EXP[255]).toBe(1);
|
|
13
|
-
});
|
|
14
|
-
it("LOG[1] should be 0", () => {
|
|
15
|
-
expect(LOG[1]).toBe(0);
|
|
16
|
-
});
|
|
17
|
-
it("multiplication should be correct", () => {
|
|
18
|
-
expect(mul(0, 5)).toBe(0);
|
|
19
|
-
expect(mul(5, 0)).toBe(0);
|
|
20
|
-
expect(mul(1, 10)).toBe(10);
|
|
21
|
-
// 2 * 2 = 4
|
|
22
|
-
expect(mul(2, 2)).toBe(4);
|
|
23
|
-
// Inverse check
|
|
24
|
-
const a = 123;
|
|
25
|
-
const invA = inv(a);
|
|
26
|
-
expect(mul(a, invA)).toBe(1);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
describe("BitBuffer", () => {
|
|
30
|
-
it("should append bits correctly", () => {
|
|
31
|
-
const bb = new BitBuffer();
|
|
32
|
-
bb.put(0b1, 1);
|
|
33
|
-
bb.put(0b0, 1);
|
|
34
|
-
bb.put(0b1, 1);
|
|
35
|
-
// current: 101 (5)
|
|
36
|
-
// length: 3
|
|
37
|
-
expect(bb.length).toBe(3);
|
|
38
|
-
const bytes = bb.toBytes();
|
|
39
|
-
// 10100000 = 0xA0
|
|
40
|
-
expect(bytes[0]).toBe(0xa0);
|
|
41
|
-
});
|
|
42
|
-
it("should handle multi-bit put", () => {
|
|
43
|
-
const bb = new BitBuffer();
|
|
44
|
-
// append 0xABC (101010111100) 12 bits
|
|
45
|
-
bb.put(0xabc, 12);
|
|
46
|
-
expect(bb.length).toBe(12);
|
|
47
|
-
const bytes = bb.toBytes();
|
|
48
|
-
// 10101011 1100xxxx
|
|
49
|
-
// 0xAB 0xC0
|
|
50
|
-
expect(bytes[0]).toBe(0xab);
|
|
51
|
-
expect((bytes[1] ?? 0) & 0xf0).toBe(0xc0);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("Reed-Solomon", () => {
|
|
55
|
-
it("should return all zeros for zero data", () => {
|
|
56
|
-
const data = new Uint8Array([0, 0, 0]);
|
|
57
|
-
const ecc = computeEcc(data, 5);
|
|
58
|
-
expect(ecc).toEqual(new Uint8Array(5));
|
|
59
|
-
});
|
|
60
|
-
// Simple known vector check could be added here if we had one.
|
|
61
|
-
// Using a known property check:
|
|
62
|
-
// If we encode M(x), then M(x)x^n + R(x) is a codeword.
|
|
63
|
-
// This means it evaluates to 0 at the roots of G(x)?
|
|
64
|
-
// Roots of G(x) are 2^0, 2^1 ... 2^{n-1}.
|
|
65
|
-
// Let's check with eccLen=2. Roots 2^0=1, 2^1=2.
|
|
66
|
-
// Codeword C(x) should satisfy C(1)=0 and C(2)=0.
|
|
67
|
-
it("should produce valid codewords (eval to 0 at roots)", () => {
|
|
68
|
-
const message = new Uint8Array([10, 20, 30]);
|
|
69
|
-
const eccLen = 2; // Roots: 1, 2
|
|
70
|
-
const ecc = computeEcc(message, eccLen);
|
|
71
|
-
// Construct full codeword: message * x^n + ecc
|
|
72
|
-
// Coefficients: [10, 20, 30, ecc[0], ecc[1]]
|
|
73
|
-
const codeword = new Uint8Array([...message, ...ecc]);
|
|
74
|
-
// Evaluate at root 1 (2^0) = 1
|
|
75
|
-
// Eval(x) = c0*x^4 + c1*x^3 + ...
|
|
76
|
-
function evalPoly(poly, x) {
|
|
77
|
-
let res = 0;
|
|
78
|
-
for (const coeff of poly) {
|
|
79
|
-
res = mul(res, x) ^ coeff;
|
|
80
|
-
}
|
|
81
|
-
return res;
|
|
82
|
-
}
|
|
83
|
-
const root0 = EXP[0] ?? 0; // 1
|
|
84
|
-
const root1 = EXP[1] ?? 0; // 2
|
|
85
|
-
expect(evalPoly(codeword, root0)).toBe(0);
|
|
86
|
-
expect(evalPoly(codeword, root1)).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { BitBuffer } from "../../src/core/bit-buffer";
|
|
3
|
-
import { encodeAlphanumeric, encodeByte, encodeNumeric, } from "../../src/encoding/encoders";
|
|
4
|
-
import { interleave } from "../../src/encoding/interleave";
|
|
5
|
-
import { segmentize } from "../../src/encoding/segmentation";
|
|
6
|
-
describe("Encoders", () => {
|
|
7
|
-
it("should encode numeric data", () => {
|
|
8
|
-
const buffer = new BitBuffer();
|
|
9
|
-
// "01234567" -> "012" (10 bits) + "345" (10 bits) + "67" (7 bits)
|
|
10
|
-
encodeNumeric("01234567", buffer);
|
|
11
|
-
// 012 = 0000001100
|
|
12
|
-
// 345 = 0101011001
|
|
13
|
-
// 67 = 1000011
|
|
14
|
-
expect(buffer.length).toBe(27);
|
|
15
|
-
});
|
|
16
|
-
it("should encode alphanumeric data", () => {
|
|
17
|
-
const buffer = new BitBuffer();
|
|
18
|
-
// "AC-42" -> "AC" (11) + "-4" (11) + "2" (6)
|
|
19
|
-
encodeAlphanumeric("AC-42", buffer);
|
|
20
|
-
expect(buffer.length).toBe(11 + 11 + 6);
|
|
21
|
-
});
|
|
22
|
-
it("should encode byte data", () => {
|
|
23
|
-
const buffer = new BitBuffer();
|
|
24
|
-
encodeByte("Hello", buffer);
|
|
25
|
-
expect(buffer.length).toBe(5 * 8);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
describe("Segmentation", () => {
|
|
29
|
-
it("should segment simple numeric", () => {
|
|
30
|
-
const segments = segmentize("0123456789");
|
|
31
|
-
expect(segments).toHaveLength(1);
|
|
32
|
-
expect(segments[0]?.mode).toBe("numeric");
|
|
33
|
-
});
|
|
34
|
-
it("should segment simple alphanumeric", () => {
|
|
35
|
-
const segments = segmentize("HELLO WORLD");
|
|
36
|
-
expect(segments).toHaveLength(1);
|
|
37
|
-
expect(segments[0]?.mode).toBe("alphanumeric");
|
|
38
|
-
});
|
|
39
|
-
it("should segment mixed content", () => {
|
|
40
|
-
// "123ABC123" -> numeric, alphanumeric, numeric
|
|
41
|
-
// Our greedy segmenter might merge if it's suboptimal, but let's see current behavior.
|
|
42
|
-
// "123" (num) "ABC" (alnum) "123" (num)
|
|
43
|
-
const segments = segmentize("123ABC123");
|
|
44
|
-
// Actually, greedy might switch eagerly.
|
|
45
|
-
// "123" -> numeric
|
|
46
|
-
// "A" -> switch to Alnum
|
|
47
|
-
// "123" -> switch to Numeric
|
|
48
|
-
expect(segments.map((s) => s.mode)).toEqual([
|
|
49
|
-
"numeric",
|
|
50
|
-
"alphanumeric",
|
|
51
|
-
"numeric",
|
|
52
|
-
]);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe("Interleave", () => {
|
|
56
|
-
it("should interleave data and ecc correctly", () => {
|
|
57
|
-
// Mock data that fits version 1-L (19 data bytes)
|
|
58
|
-
const data = new Uint8Array(19).fill(0x11);
|
|
59
|
-
// Version 1-L: 19 data bytes, 7 ECC bytes. Total 26.
|
|
60
|
-
const result = interleave(data, 1, "L");
|
|
61
|
-
expect(result.length).toBe(26);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { setupFunctionPatterns } from "../../src/layout/patterns";
|
|
3
|
-
import { Matrix } from "../../src/matrix/bit-matrix";
|
|
4
|
-
describe("Layout Patterns", () => {
|
|
5
|
-
it("should set up finder patterns", () => {
|
|
6
|
-
const matrix = new Matrix(21);
|
|
7
|
-
setupFunctionPatterns(matrix, 1);
|
|
8
|
-
// Top Left Finder
|
|
9
|
-
expect(matrix.isReserved(3, 3)).toBe(true); // Center of 3x3
|
|
10
|
-
// Finder is 7x7. (0,0) to (6,6). Center (3,3).
|
|
11
|
-
// (3,3) is dark.
|
|
12
|
-
expect(matrix.get(3, 3)).toBe(1);
|
|
13
|
-
// (0,0) is dark (outer ring)
|
|
14
|
-
expect(matrix.get(0, 0)).toBe(1);
|
|
15
|
-
// (1,1) is light (gap between outer and inner) - wait no
|
|
16
|
-
// 7x7 finder:
|
|
17
|
-
// xxxxxxx
|
|
18
|
-
// x.....x
|
|
19
|
-
// x.xxx.x
|
|
20
|
-
// x.xxx.x
|
|
21
|
-
// x.xxx.x
|
|
22
|
-
// x.....x
|
|
23
|
-
// xxxxxxx
|
|
24
|
-
// (0,0) dark
|
|
25
|
-
// (1,1) light?
|
|
26
|
-
// Logic: max(abs(x-3), abs(y-3))
|
|
27
|
-
// (1,1): abs(-2)=2. max(2,2)=2.
|
|
28
|
-
// isDark = (max === 3) || (max <= 1).
|
|
29
|
-
// 2 is not 3, nor <= 1. So light. Correct.
|
|
30
|
-
expect(matrix.get(1, 1)).toBe(0);
|
|
31
|
-
});
|
|
32
|
-
it("should set up timing patterns", () => {
|
|
33
|
-
const matrix = new Matrix(21);
|
|
34
|
-
setupFunctionPatterns(matrix, 1);
|
|
35
|
-
// Row 6, Cols 8..Size-8 (8..13 for V1)
|
|
36
|
-
// (8, 6) -> i=8. even -> dark (true)
|
|
37
|
-
// (9, 6) -> light
|
|
38
|
-
expect(matrix.isReserved(8, 6)).toBe(true);
|
|
39
|
-
expect(matrix.get(8, 6)).toBe(1);
|
|
40
|
-
expect(matrix.get(9, 6)).toBe(0);
|
|
41
|
-
});
|
|
42
|
-
it("should set up alignment patterns for V2", () => {
|
|
43
|
-
const matrix = new Matrix(25); // V2 size
|
|
44
|
-
setupFunctionPatterns(matrix, 2);
|
|
45
|
-
// V2 alignment at 6, 18
|
|
46
|
-
// (6,6) is finder.
|
|
47
|
-
// (18,18) should be alignment center.
|
|
48
|
-
// Alignment is 5x5 center (18,18).
|
|
49
|
-
expect(matrix.isReserved(18, 18)).toBe(true);
|
|
50
|
-
// Center is dark
|
|
51
|
-
expect(matrix.get(18, 18)).toBe(1);
|
|
52
|
-
});
|
|
53
|
-
it("should set up version info for V7", () => {
|
|
54
|
-
const matrix = new Matrix(45); // V7 size
|
|
55
|
-
setupFunctionPatterns(matrix, 7);
|
|
56
|
-
// Version info is 3x6 block above bottom-left finder and left of top-right finder.
|
|
57
|
-
// Bottom-left finder is at (0, Size-7).
|
|
58
|
-
// Version info above it: Row Size-11..Size-9. Col 0..5
|
|
59
|
-
// Let's check reservation.
|
|
60
|
-
expect(matrix.isReserved(0, 45 - 11)).toBe(true);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { applyMask, selectAndApplyBestMask } from "../../src/mask/selection";
|
|
3
|
-
import { Matrix } from "../../src/matrix/bit-matrix";
|
|
4
|
-
describe("Masking", () => {
|
|
5
|
-
it("should allow applying a specific mask", () => {
|
|
6
|
-
const matrix = new Matrix(21);
|
|
7
|
-
// Mask 0: (x+y)%2 == 0
|
|
8
|
-
applyMask(matrix, 0);
|
|
9
|
-
// (0,0) -> even -> flip inv (0->1)
|
|
10
|
-
expect(matrix.get(0, 0)).toBe(1);
|
|
11
|
-
// (0,1) -> odd -> 0
|
|
12
|
-
expect(matrix.get(0, 1)).toBe(0);
|
|
13
|
-
});
|
|
14
|
-
it("should select the best mask", () => {
|
|
15
|
-
const matrix = new Matrix(21);
|
|
16
|
-
// Empty matrix -> penalty calculation will favor a mask that breaks up large white areas
|
|
17
|
-
// or one that doesn't create false patterns.
|
|
18
|
-
const result = selectAndApplyBestMask(matrix, "M");
|
|
19
|
-
expect(result.mask).toBeGreaterThanOrEqual(0);
|
|
20
|
-
expect(result.mask).toBeLessThanOrEqual(7);
|
|
21
|
-
expect(result.penalties).toHaveLength(8);
|
|
22
|
-
// Ensure format info was written
|
|
23
|
-
// (8,0) to (8,8) area
|
|
24
|
-
let formatInfoWritten = false;
|
|
25
|
-
for (let i = 0; i < 9; i++) {
|
|
26
|
-
if (matrix.isReserved(i, 8))
|
|
27
|
-
formatInfoWritten = true;
|
|
28
|
-
}
|
|
29
|
-
expect(formatInfoWritten).toBe(true);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|