Add Solidity QR Code generator implementation

Co-authored-by: okwme <964052+okwme@users.noreply.github.com>
pull/237/head^2
copilot-swe-agent[bot] 2 weeks ago
parent b8cdf308c0
commit c9a030e495

@ -0,0 +1 @@
qrcodegen-demo.o: qrcodegen-demo.c qrcodegen.h

@ -0,0 +1 @@
qrcodegen.o: qrcodegen.c qrcodegen.h

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,4 @@
node_modules/
artifacts/
cache/
typechain-types/

@ -0,0 +1,132 @@
QR Code generator library — Solidity
=====================================
Introduction
------------
This is the Solidity port of the QR Code generator library, modeled after the
C implementation in this repository. It provides on-chain QR Code generation
as a pure Solidity library — no external calls, no oracles, entirely self-contained.
Home page with live JavaScript demo, extensive descriptions, and competitor comparisons:
https://www.nayuki.io/page/qr-code-generator-library
Features
--------
Core features:
* Supports all 40 QR Code versions (sizes 21×21 to 177×177)
* All 4 error correction levels: Low, Medium, Quartile, High
* All 4 encoding modes: Numeric, Alphanumeric, Byte, Kanji/ECI
* Automatic mask selection (evaluates all 8 patterns, picks the lowest-penalty one)
* Manual mask pattern override (MASK_0 … MASK_7)
* ECC level boosting (upgrades ECC level when the version number doesn't increase)
* Low-level segment API for mixed-mode encoding
* On-chain SVG renderer (QRCodeDemo.toSvgString)
* Open source under the MIT License
Output format
-------------
Every successful encoding returns a `bytes` value:
```
qrcode[0] — side length in modules (21 for version 1, 177 for version 40)
qrcode[1..] — packed module bits, row-major order
Module (x, y) is at bit index y * size + x
stored in byte (y * size + x) / 8 + 1
at bit position (y * size + x) % 8 (LSB = bit 0)
A set bit (1) means a dark / black module.
```
A failed encoding (data too long for the requested version range) returns
`bytes` of length 1 where `qrcode[0] == 0`.
Examples
--------
```solidity
import "./QRCode.sol";
// Simple — encode "Hello, world!" with Low ECC, auto mask
bytes memory qr = QRCode.encodeText(
"Hello, world!",
QRCode.ECC_LOW,
QRCode.VERSION_MIN, // 1
QRCode.VERSION_MAX, // 40
QRCode.MASK_AUTO,
true // boost ECC level if possible
);
uint size = QRCode.getSize(qr); // 21 for version 1
// Read a module
bool dark = QRCode.getModule(qr, x, y);
// Manual — force version 5, mask pattern 2, no ECC boost
bytes memory qr2 = QRCode.encodeText(
"3141592653589793238462643383",
QRCode.ECC_HIGH,
5, 5,
QRCode.MASK_2,
false
);
// Low-level — mix numeric and alphanumeric segments
QRCode.Segment[] memory segs = new QRCode.Segment[](2);
segs[0] = QRCode.makeAlphanumeric(bytes("THE SQUARE ROOT OF 2 IS 1."));
segs[1] = QRCode.makeNumeric(bytes("41421356237309504880168872420969807856967187537694"));
bytes memory qr3 = QRCode.encodeSegments(segs, QRCode.ECC_LOW);
// On-chain SVG (4 module quiet zone)
string memory svg = QRCodeDemo(demoAddress).toSvgString(qr, 4);
```
Building and testing
--------------------
Prerequisites: Node.js ≥ 18 and npm.
```bash
cd solidity
npm install
npm run compile # compiles with the bundled solc (no internet required)
npm test # runs the Hardhat test suite against a local network
```
The `npm run compile` script uses the bundled `solc` npm package so no
network access is needed. `npm test` runs Hardhat which spins up an
in-process EVM for the tests.
Note: the contracts use `viaIR: true` (Yul IR pipeline) to avoid the EVM's
16-slot stack-depth limit, which the QR Code algorithm would otherwise exceed.
License
-------
Copyright © 2025 Project Nayuki. (MIT License)
https://www.nayuki.io/page/qr-code-generator-library
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
* The Software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement. In no event shall the
authors or copyright holders be liable for any claim, damages or other
liability, whether in an action of contract, tort or otherwise, arising from,
out of or in connection with the Software or the use or other dealings in the
Software.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,257 @@
/*
* QR Code generator demo (Solidity)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* - The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* - The Software is provided "as is", without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from,
* out of or in connection with the Software or the use or other dealings in the
* Software.
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./QRCode.sol";
/*
* A demo contract that exercises the QRCode library.
*
* All public functions are pure (no state) and return the raw QR Code bytes.
* Callers can inspect individual modules with QRCode.getModule() or render the
* symbol off-chain. See the Readme.markdown for the output format.
*
* toSvgString() shows how to render a QR Code to an inline SVG string entirely
* on-chain useful for NFT metadata or other on-chain display.
*/
contract QRCodeDemo {
// -----------------------------------------------------------------------
// Demo scenarios
// -----------------------------------------------------------------------
// Simple: encode "Hello, world!" with Low ECC, automatic mask.
function doBasicDemo() external pure returns (bytes memory) {
return QRCode.encodeText(
"Hello, world!",
QRCode.ECC_LOW,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_AUTO,
true
);
}
// Same as doBasicDemo() but forces mask 2 (the mask AUTO selects for this input).
// Useful for testing when on-chain gas limits prohibit the 8-pass auto-mask scan.
function doBasicDemoFixedMask() external pure returns (bytes memory) {
return QRCode.encodeText(
"Hello, world!",
QRCode.ECC_LOW,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_2,
true
);
}
// Numeric mode: digits are encoded with 3.33 bits per digit.
function doNumericDemo() external pure returns (bytes memory) {
return QRCode.encodeText(
"314159265358979323846264338327950288419716939937510",
QRCode.ECC_MEDIUM,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_AUTO,
true
);
}
// Same as doNumericDemo() but forces mask 3 (the mask AUTO selects for this input).
// Useful for testing when on-chain gas limits prohibit the 8-pass auto-mask scan.
function doNumericDemoFixedMask() external pure returns (bytes memory) {
return QRCode.encodeText(
"314159265358979323846264338327950288419716939937510",
QRCode.ECC_MEDIUM,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_3,
true
);
}
// Alphanumeric mode: uppercase + special chars at 5.5 bits per character.
function doAlphanumericDemo() external pure returns (bytes memory) {
return QRCode.encodeText(
"DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/",
QRCode.ECC_HIGH,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_AUTO,
true
);
}
// Same as doAlphanumericDemo() but forces mask 0.
// Useful for testing when on-chain gas limits prohibit the 8-pass auto-mask scan.
function doAlphanumericDemoFixedMask() external pure returns (bytes memory) {
return QRCode.encodeText(
"DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/",
QRCode.ECC_HIGH,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_0,
true
);
}
// Binary / byte mode: arbitrary byte sequences.
function doBinaryDemo() external pure returns (bytes memory) {
bytes memory data = hex"48656c6c6f2c20776f726c6421"; // "Hello, world!"
return QRCode.encodeBinary(
data,
QRCode.ECC_MEDIUM,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_AUTO,
true
);
}
// Same as doBinaryDemo() but forces mask 0.
function doBinaryDemoFixedMask() external pure returns (bytes memory) {
bytes memory data = hex"48656c6c6f2c20776f726c6421"; // "Hello, world!"
return QRCode.encodeBinary(
data,
QRCode.ECC_MEDIUM,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_0,
true
);
}
// Fixed mask: forces mask pattern 3 instead of auto-selecting.
function doFixedMaskDemo() external pure returns (bytes memory) {
return QRCode.encodeText(
"https://www.nayuki.io/",
QRCode.ECC_HIGH,
QRCode.VERSION_MIN,
QRCode.VERSION_MAX,
QRCode.MASK_3,
true
);
}
// Segment API: mix alphanumeric + numeric segments for compact encoding.
function doSegmentDemo() external pure returns (bytes memory) {
QRCode.Segment[] memory segs = new QRCode.Segment[](2);
segs[0] = QRCode.makeAlphanumeric(bytes("THE SQUARE ROOT OF 2 IS 1."));
segs[1] = QRCode.makeNumeric(bytes("41421356237309504880168872420969807856967187537694"));
return QRCode.encodeSegments(segs, QRCode.ECC_LOW);
}
// Same as doSegmentDemo() but forces mask 0 to stay within the gas cap.
function doSegmentDemoFixedMask() external pure returns (bytes memory) {
QRCode.Segment[] memory segs = new QRCode.Segment[](2);
segs[0] = QRCode.makeAlphanumeric(bytes("THE SQUARE ROOT OF 2 IS 1."));
segs[1] = QRCode.makeNumeric(bytes("41421356237309504880168872420969807856967187537694"));
return QRCode.encodeSegmentsAdvanced(segs, QRCode.ECC_LOW,
QRCode.VERSION_MIN, QRCode.VERSION_MAX, QRCode.MASK_0, true);
}
// ECI segment: marks the payload as UTF-8 (ECI assignment value 26).
function doEciDemo() external pure returns (bytes memory) {
QRCode.Segment[] memory segs = new QRCode.Segment[](2);
segs[0] = QRCode.makeEci(26);
segs[1] = QRCode.makeBytes(hex"e4b8ade69687"); // Chinese "zhongwen" in UTF-8
return QRCode.encodeSegments(segs, QRCode.ECC_MEDIUM);
}
// Same as doEciDemo() but forces mask 0 to stay within the gas cap.
function doEciDemoFixedMask() external pure returns (bytes memory) {
QRCode.Segment[] memory segs = new QRCode.Segment[](2);
segs[0] = QRCode.makeEci(26);
segs[1] = QRCode.makeBytes(hex"e4b8ade69687"); // Chinese "zhongwen" in UTF-8
return QRCode.encodeSegmentsAdvanced(segs, QRCode.ECC_MEDIUM,
QRCode.VERSION_MIN, QRCode.VERSION_MAX, QRCode.MASK_0, true);
}
// Version-constrained: forces exactly version 5 (37×37 modules), mask 2, no ECC boost.
function doVersionConstraintDemo() external pure returns (bytes memory) {
return QRCode.encodeText(
"3141592653589793238462643383",
QRCode.ECC_HIGH,
5, 5,
QRCode.MASK_2,
false
);
}
// -----------------------------------------------------------------------
// SVG renderer (on-chain)
// -----------------------------------------------------------------------
/*
* Converts a QR Code to a minimal SVG string suitable for embedding in HTML
* or returning as NFT metadata.
*
* `border` is the number of quiet-zone modules to add on each side (the QR
* Code specification recommends at least 4).
*/
function toSvgString(bytes memory qrcode, uint border)
external pure returns (string memory)
{
uint sz = QRCode.getSize(qrcode);
uint total = sz + 2 * border;
string memory svg = string(abi.encodePacked(
'<?xml version="1.0" encoding="UTF-8"?>',
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1"',
' viewBox="0 0 ', _uint2str(total), ' ', _uint2str(total), '"',
' stroke="none">',
'<rect width="100%" height="100%" fill="#FFFFFF"/>',
'<path fill="#000000" d="'
));
for (uint y = 0; y < sz; y++) {
for (uint x = 0; x < sz; x++) {
if (QRCode.getModule(qrcode, x, y)) {
svg = string(abi.encodePacked(
svg,
'M', _uint2str(x + border), ',', _uint2str(y + border), 'h1v1h-1z'
));
}
}
}
return string(abi.encodePacked(svg, '"/></svg>'));
}
// Internal helper: convert uint to its decimal string representation.
function _uint2str(uint n) internal pure returns (string memory) {
if (n == 0) return "0";
uint temp = n;
uint digits = 0;
while (temp != 0) { digits++; temp /= 10; }
bytes memory buf = new bytes(digits);
while (n != 0) {
digits--;
buf[digits] = bytes1(uint8(48 + n % 10));
n /= 10;
}
return string(buf);
}
}

@ -0,0 +1,25 @@
import "@nomicfoundation/hardhat-toolbox";
/** @type import('hardhat/config').HardhatUserConfig */
const config = {
solidity: {
version: "0.8.34",
settings: {
viaIR: true,
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
// QR Code generation requires significant gas.
// blockGasLimit increases the cap for calls; gas is the default per-tx limit.
blockGasLimit: 30_000_000,
allowUnlimitedContractSize: true,
},
},
};
export default config;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,23 @@
{
"name": "qrcodegen-solidity",
"version": "1.0.0",
"description": "QR Code generator library — Solidity port",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "hardhat test",
"compile": "node scripts/compile.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"ethers": "^6.16.0",
"hardhat": "^2.28.6",
"solc": "^0.8.34"
}
}

@ -0,0 +1,62 @@
#!/usr/bin/env node
/**
* Compile script uses the bundled solc npm package (no internet required).
* Outputs compiled JSON artifacts to the artifacts/ directory.
*
* Usage: node scripts/compile.js
* or: npm run compile
*/
import { readFileSync, writeFileSync, mkdirSync } from "fs";
import { createRequire } from "module";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
const require = createRequire(import.meta.url);
const __dirname = dirname(fileURLToPath(import.meta.url));
const solc = require("solc");
const contractsDir = resolve(__dirname, "../contracts");
const artifactsDir = resolve(__dirname, "../artifacts");
mkdirSync(artifactsDir, { recursive: true });
const input = {
language: "Solidity",
sources: {
"QRCode.sol": { content: readFileSync(`${contractsDir}/QRCode.sol`, "utf8") },
"QRCodeDemo.sol": { content: readFileSync(`${contractsDir}/QRCodeDemo.sol`, "utf8") },
},
settings: {
viaIR: true,
optimizer: { enabled: true, runs: 200 },
outputSelection: {
"*": { "*": ["abi", "evm.bytecode.object", "evm.deployedBytecode.object"] },
},
},
};
console.log(`Compiling with solc ${solc.version()}`);
const output = JSON.parse(solc.compile(JSON.stringify(input)));
let hasError = false;
for (const e of output.errors || []) {
const msg = `[${e.severity.toUpperCase()}] ${e.formattedMessage}`;
if (e.severity === "error") { console.error(msg); hasError = true; }
else { console.warn(msg); }
}
if (hasError) process.exit(1);
for (const [, contracts] of Object.entries(output.contracts || {})) {
for (const [name, data] of Object.entries(contracts)) {
const artifact = {
contractName: name,
abi: data.abi,
bytecode: "0x" + data.evm.bytecode.object,
deployedBytecode: "0x" + data.evm.deployedBytecode.object,
};
const outPath = `${artifactsDir}/${name}.json`;
writeFileSync(outPath, JSON.stringify(artifact, null, 2));
console.log(`${name} → artifacts/${name}.json`);
}
}
console.log("Compilation successful.");

@ -0,0 +1,227 @@
/*
* QR Code generator test suite (Solidity)
*
* Deploys QRCodeDemo to a local Hardhat network and validates the output of
* every demo function against known-good values produced by the C reference
* implementation in this repository.
*
* Run with: npx hardhat test
* or: npm test
*
* Gas note: On-chain QR Code generation for small inputs costs 39 million gas.
* Auto-mask selection evaluates all 8 mask patterns and can exceed Hardhat's
* default per-transaction gas cap (16 777 216 gas, `FUSAKA_TRANSACTION_GAS_LIMIT`
* in hardhat/src/internal/constants.ts). The fixed-mask demo functions
* (doBasicDemoFixedMask, doNumericDemoFixedMask, etc.) are provided specifically
* to allow byte-exact comparison against the C reference output without triggering
* the 8-pass auto-mask penalty scoring.
*
* The expected byte strings were obtained by compiling and running the C reference
* implementation (c/qrcodegen.c) with identical parameters.
*/
import { expect } from "chai";
import hre from "hardhat";
// ---------------------------------------------------------------------------
// Utility: read a single module from raw QR Code bytes (JS-side helper)
// ---------------------------------------------------------------------------
function getModule(buf, x, y) {
const size = buf[0];
if (x < 0 || x >= size || y < 0 || y >= size) return false;
const index = y * size + x;
const byteIdx = (index >> 3) + 1; // +1 because buf[0] is the size
const bitIdx = index & 7;
return ((buf[byteIdx] >> bitIdx) & 1) !== 0;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function hexToBytes(hex) {
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
return Buffer.from(h, "hex");
}
// ---------------------------------------------------------------------------
// Test suite
// ---------------------------------------------------------------------------
describe("QRCode library", function () {
let demo;
before(async function () {
const Demo = await hre.ethers.getContractFactory("QRCodeDemo");
demo = await Demo.deploy();
await demo.waitForDeployment();
});
// -----------------------------------------------------------------------
// 1. Byte-for-byte comparison against C reference output
// (all use explicit fixed masks to stay within the ~16 M gas cap)
// -----------------------------------------------------------------------
it("'Hello, world!' ECC_LOW MASK_2 matches C reference", async function () {
// C: qrcodegen_encodeText("Hello, world!", …, Ecc_LOW, 1, 40, Mask_2, true)
// MASK_AUTO also selects mask 2 for this input (verified with C reference).
const expected = "157fd03f48097675ddaea0db3575839ee05ff50728007dce0733e622dd39535eb3170d01c2c79f3d0baa755d7bb9ab70747d93209af3b79400";
const qr = await demo.doBasicDemoFixedMask();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("50-digit π string ECC_MEDIUM MASK_3 matches C reference", async function () {
// MASK_AUTO also selects mask 3 for this input (verified with C reference).
const expected = "197f41fc83ea0a7611d7eddaa9db254f3748a2e05f557f805c00edcaa4617a999dd9898804a9aaad4452e225bb57d5dcc4bb4fcc2fbf006a23fd7d540aba8ad6a5fbbb6ba668d7eead20c1617fd14e01";
const qr = await demo.doNumericDemoFixedMask();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("version-5 HIGH MASK_2 no-boost matches C reference", async function () {
const expected = "257f6508cf3f68a7ef0b76d55b29dd2e744aa6db45deac748376fdbee05f5555f507a8bfda005ce991fd9c56ef6a79bb0b047f44571c56f6bd2fdcd60729a7e6d607fb4ef64f6d0de054fc70fa2cadfe0049104b434d6803d3564d9e3aa5a6151aa557908cc828520456b6cbd5f0e92f5e4ab1c06ae47eee8f7667cc18ba8e62973892567445bf00e64f32f61fc408d60de234ab085d4516f1b52bb7cf93765dfcded820496061fe874add2601";
const qr = await demo.doVersionConstraintDemo();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("'https://www.nayuki.io/' ECC_HIGH MASK_3 matches C reference", async function () {
const expected = "1d7f90d53fc8bc0b76c951dd2ee7afdb55aa7483a0b0e05f55f507987000cc016961224f2cbe770339c40188488dcbeb3b9591ad9c95ee96c5f9dbcdfbb45bc469d014f35c0b1057f52b9e4cdf015630f65ffc560b229c085d60f7b76b01e175c556c220cab8f5a7c48b00";
const qr = await demo.doFixedMaskDemo();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("binary 'Hello, world!' ECC_MEDIUM MASK_0 matches C reference", async function () {
const expected = "157fd63f680a7669dd2eacdb557583ace05ff507e000550869272153c1fe024274661100b2db1f4c0f62045dbda88bb77565d42086f4d78801";
const qr = await demo.doBinaryDemoFixedMask();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("mixed alphanumeric+numeric segment ECC_LOW MASK_0 matches C reference", async function () {
const expected = "1d7fc4cd3f68280b76e104dd2ea0acdb75d87583809de05f55f507b0a00055e00849c1cc499d5da07ccf6115830593916751f86e70bc0e548c7038efad85dd322d311707051055dd499ec7645f012e2cca1fc9d50fc29c285d15f4ad8bb19d75955ede202034fe37173401";
const qr = await demo.doSegmentDemoFixedMask();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
it("ECI(26)+UTF-8 bytes ECC_MEDIUM MASK_0 matches C reference", async function () {
const expected = "157fc93f88097609ddaea3db657583ace05ff507c000742df291a763e1e7504154374201c6df1fe30eaaf15d53a48bfb748dc720ccfa07ea01";
const qr = await demo.doEciDemoFixedMask();
expect(hexToBytes(qr).toString("hex")).to.equal(expected);
});
// -----------------------------------------------------------------------
// 2. Size / structural invariants for fixed-mask functions
// -----------------------------------------------------------------------
it("doBasicDemoFixedMask: size is 21 modules (version 1)", async function () {
const qr = await demo.doBasicDemoFixedMask();
const size = hexToBytes(qr)[0];
expect(size).to.equal(21);
});
it("doNumericDemoFixedMask: size is 25 modules (version 2)", async function () {
const qr = await demo.doNumericDemoFixedMask();
const size = hexToBytes(qr)[0];
expect(size).to.equal(25);
});
it("doVersionConstraintDemo: size is 37 modules (version 5)", async function () {
const qr = await demo.doVersionConstraintDemo();
const size = hexToBytes(qr)[0];
expect(size).to.equal(37);
});
it("doFixedMaskDemo: size is 29 modules (version 3)", async function () {
const qr = await demo.doFixedMaskDemo();
const size = hexToBytes(qr)[0];
expect(size).to.equal(29);
});
it("doSegmentDemoFixedMask: size is 29 modules (version 3)", async function () {
const qr = await demo.doSegmentDemoFixedMask();
const size = hexToBytes(qr)[0];
expect(size).to.equal(29);
});
it("doEciDemoFixedMask: size is 21 modules (version 1)", async function () {
const qr = await demo.doEciDemoFixedMask();
const size = hexToBytes(qr)[0];
expect(size).to.equal(21);
});
// -----------------------------------------------------------------------
// 3. Buffer-length invariant: length == 1 + ceil(size² / 8)
// -----------------------------------------------------------------------
const lengthCheckCases = [
["doBasicDemoFixedMask", (d) => d.doBasicDemoFixedMask()],
["doNumericDemoFixedMask", (d) => d.doNumericDemoFixedMask()],
["doVersionConstraintDemo", (d) => d.doVersionConstraintDemo()],
["doFixedMaskDemo", (d) => d.doFixedMaskDemo()],
["doSegmentDemoFixedMask", (d) => d.doSegmentDemoFixedMask()],
["doEciDemoFixedMask", (d) => d.doEciDemoFixedMask()],
];
for (const [name, fn] of lengthCheckCases) {
it(`${name}: buffer length equals 1 + ceil(size² / 8)`, async function () {
const qr = await fn(demo);
const buf = hexToBytes(qr);
const size = buf[0];
const expected = 1 + Math.ceil(size * size / 8);
expect(buf.length).to.equal(expected);
});
}
// -----------------------------------------------------------------------
// 4. Finder-pattern module checks (version 1, top-left corner)
// -----------------------------------------------------------------------
it("finder pattern: outer corners dark, separator ring light, centre dark", async function () {
const qr = await demo.doBasicDemoFixedMask();
const buf = hexToBytes(qr);
// Top-left finder outer corners must be dark
expect(getModule(buf, 0, 0)).to.be.true;
expect(getModule(buf, 6, 0)).to.be.true;
expect(getModule(buf, 0, 6)).to.be.true;
expect(getModule(buf, 6, 6)).to.be.true;
// Inner dark 3×3 centre
expect(getModule(buf, 3, 3)).to.be.true;
// Separator ring (Chebyshev distance 2 from centre (3,3)) must be light
expect(getModule(buf, 1, 1)).to.be.false;
expect(getModule(buf, 5, 1)).to.be.false;
expect(getModule(buf, 1, 5)).to.be.false;
expect(getModule(buf, 5, 5)).to.be.false;
});
// -----------------------------------------------------------------------
// 5. Determinism: calling the same function twice gives the same output
// -----------------------------------------------------------------------
it("encodeText is deterministic", async function () {
const qr1 = await demo.doBasicDemoFixedMask();
const qr2 = await demo.doBasicDemoFixedMask();
expect(qr1).to.equal(qr2);
});
// -----------------------------------------------------------------------
// 6. SVG output
// -----------------------------------------------------------------------
it("toSvgString returns well-formed SVG", async function () {
const qr = await demo.doBasicDemoFixedMask();
const svg = await demo.toSvgString(qr, 4);
expect(svg).to.include('<svg');
expect(svg).to.include('</svg>');
expect(svg).to.include('fill="#000000"');
expect(svg).to.include('fill="#FFFFFF"');
// viewBox = size + 2*border = 21 + 8 = 29
expect(svg).to.include('viewBox="0 0 29 29"');
});
});
Loading…
Cancel
Save