const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); const Module = require('node:module'); const root = path.resolve(__dirname, '../halo'); const ts = require(path.join(root, 'node_modules/typescript')); const sharp = require('./qr-test-tools/node_modules/sharp'); const jsQR = require('./qr-test-tools/node_modules/jsqr'); function loadSource(relative) { const filename = path.join(root, relative); const content = fs.readFileSync(filename, 'utf8'); const compiled = ts.transpileModule(content, { compilerOptions: { module: ts.ModuleKind.CommonJS, esModuleInterop: true } }).outputText; const loaded = new Module(filename, module); loaded.filename = filename; loaded.paths = Module._nodeModulePaths(path.dirname(filename)); loaded._compile(compiled, filename); return loaded.exports; } const { renderQrSvg, buildMatrix } = loadSource('lib/qr-render.ts'); const options = { value: 'https://halo.wezzik.app/guide', fgColor: '#111111', bgColor: '#ffffff', gradientKind: 'none', gradientFrom: '#111111', gradientTo: '#111111', pattern: 'squares', errorCorrection: 'M', logoDataUrl: null, frameEnabled: false, frameText: '', frameColor: '#111111', transparentBackground: false, size: 640 }; const cases = [ { id: 'baseline-white', label: '深色模块与白色背景', transparent: false, background: '#ffffff', size: 640 }, { id: 'transparent-white', label: '透明文件叠放在白色底板', transparent: true, background: '#ffffff', size: 640 }, { id: 'transparent-dark', label: '同一透明文件叠放在与模块同色的背景', transparent: true, background: '#111111', size: 640 }, { id: 'small-raster', label: '基础版本缩小至三十二像素后再放大', transparent: false, background: '#ffffff', size: 32 }, ]; (async () => { const directory = path.join(root, 'public/evidence/2026-09-14'); fs.mkdirSync(directory, { recursive: true }); const results = []; for (const test of cases) { const svg = renderQrSvg({ ...options, transparentBackground: test.transparent }); fs.writeFileSync(path.join(directory, `${test.id}.svg`), svg); const png = await sharp(Buffer.from(svg)).resize(test.size, test.size).flatten({ background: test.background }).png().toBuffer(); fs.writeFileSync(path.join(directory, `${test.id}.tested.png`), png); const { data, info } = await sharp(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true }); const decoded = jsQR(new Uint8ClampedArray(data), info.width, info.height, { inversionAttempts: 'attemptBoth' }); const display = await sharp(png).resize(640, 640, { kernel: 'nearest' }).png().toBuffer(); fs.writeFileSync(path.join(directory, `${test.id}.png`), display); results.push({ id: test.id, label: test.label, decoded: decoded?.data || null, matches: decoded?.data === options.value, inputSize: test.size, image: `/evidence/2026-09-14/${test.id}.png`, testedPng: `/evidence/2026-09-14/${test.id}.tested.png`, svg: `/evidence/2026-09-14/${test.id}.svg`, testedPngSha256: crypto.createHash('sha256').update(png).digest('hex') }); } const report = { testedAt: new Date().toISOString(), environment: { node: process.version, platform: `${process.platform}/${process.arch}`, sharp: '0.34.5', decoder: 'jsQR 1.4.0', qrcode: require(path.join(root, 'node_modules/qrcode/package.json')).version }, payload: options.value, modules: buildMatrix(options.value, options.errorCorrection).size, quietZoneModules: 4, options, rendererSha256: crypto.createHash('sha256').update(fs.readFileSync(path.join(root, 'lib/qr-render.ts'))).digest('hex'), scope: '实际应用的矢量渲染器,经软件栅格化后自动解码;没有测试浏览器画布下载、手机或纸张。展示图片在测试后放大,原测试尺寸另行记录。', results }; fs.writeFileSync(path.join(directory, 'report.json'), JSON.stringify(report, null, 2)); fs.copyFileSync(path.join(root, 'lib/qr-render.ts'), path.join(directory, 'qr-render.ts.txt')); fs.copyFileSync(__filename, path.join(directory, 'verify-qr-evidence.cjs.txt')); console.log(JSON.stringify(report, null, 2)); if (!results[0].matches || !results[1].matches) throw new Error('正常对照版本未通过,必须调查而非写成成功'); })().catch(error => { console.error(error); process.exitCode = 1; });