open-standards/wai/wai-vector/wasm/verify.mjs
Transaction Science 4c61c5b67b wai-vector: the default stroke set and the test repertoire, as WSF1 sources expanded by Annex A; the text vectors (D7-5)
- WSF1 (feature author): the stroke source format, its checks, the pinned Q15 table of
  64 unit vectors and the integer expansion into a polygon-only WOS1.
- The ASCII stroke set, U+0020-U+007E, drawn here as integer centre lines; bundled as
  builtin::STROKE_ASCII with its SHA-256, which Annex B states.
- The test repertoire: Arabic, Hebrew and Devanagari glyphs and a U+1F600 face, drawn
  here, with the joining table and the small shaper the corpus generator uses.
- graphics-vector.md section 4, Annex A (WSF1, the table as bytes, the expansion) and
  Annex B (the digest).
- vector-conformance: sources/, the two sets, the text group, and stroke-glyph members of
  runs and motion; the README's provenance line names the in-house sets.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 17:41:08 -04:00

88 lines
3.5 KiB
JavaScript

// Recompute every expected file of vector-conformance under this engine's
// WebAssembly, and require each to equal the committed file byte for byte; and the
// authoring form beside each payload. The default stroke set's SHA-256 is required to
// be the one Annex B of the extension states.
//
// node verify.mjs <wai_vector_wasm.wasm> <vector-conformance dir>
import { createHash } from 'node:crypto';
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
const [wasmPath, root] = process.argv.slice(2);
if (!wasmPath || !root) {
console.error('usage: node verify.mjs <wai_vector_wasm.wasm> <vector-conformance dir>');
process.exit(2);
}
const { instance } = await WebAssembly.instantiate(readFileSync(wasmPath), {});
const x = instance.exports;
const enc = new TextEncoder();
function put(bytes) {
const p = x.wv_alloc(bytes.length);
new Uint8Array(x.memory.buffer, p, bytes.length).set(bytes);
return p;
}
function out() {
return new Uint8Array(x.memory.buffer, x.wv_out_ptr(), x.wv_out_len()).slice();
}
for (const f of readdirSync(join(root, 'sets')).filter((n) => n.endsWith('.wos1')).sort()) {
const b = readFileSync(join(root, 'sets', f));
const p = put(b);
x.wv_add_set(p, b.length);
x.wv_free(p, b.length);
}
let vectors = 0, forms = 0, bad = 0;
// Annex B: the default stroke set's digest.
{
const doc = readFileSync(join(root, '..', 'extensions', 'graphics-vector.md'), 'utf8');
const annex = doc.slice(doc.indexOf('## Annex B.'));
const line = annex.split('\n').find((l) => l.includes('the set (`WOS1`)')) || '';
const stated = (line.match(/[0-9a-f]{64}/) || [''])[0];
const have = createHash('sha256').update(readFileSync(join(root, 'sets', 'stroke-ascii.wos1'))).digest('hex');
if (stated.length !== 64 || have !== stated) {
bad += 1;
console.error(`ANNEX B: sets/stroke-ascii.wos1 hashes to ${have}, Annex B states ${stated || 'nothing'}`);
} else {
console.log(`the default stroke set is Annex B's: ${have}`);
}
}
for (const group of readdirSync(join(root, 'vectors')).sort()) {
for (const f of readdirSync(join(root, 'vectors', group)).sort()) {
const dot = f.lastIndexOf('.');
const name = f.slice(0, dot), ext = f.slice(dot + 1);
if (name === 'index' || (ext === 'json' && !['arith', 'flatten'].includes(group))) continue;
const data = readFileSync(join(root, 'vectors', group, f));
const [g, e] = [enc.encode(group), enc.encode(ext)];
const [pg, pe, pd] = [put(g), put(e), put(data)];
const status = x.wv_recompute(pg, g.length, pe, e.length, pd, data.length);
const got = out();
x.wv_free(pg, g.length); x.wv_free(pe, e.length);
vectors += 1;
const want = readFileSync(join(root, 'expected', group, name + '.json'));
if (status !== 0) {
bad += 1;
console.error(`FAIL ${group}/${f}: ${new TextDecoder().decode(got)}`);
} else if (Buffer.compare(Buffer.from(got), want) !== 0) {
bad += 1;
console.error(`DIFF ${group}/${f}: the recomputed file differs`);
}
if (ext === 'wvg1') {
x.wv_form(pd, data.length);
forms += 1;
if (Buffer.compare(Buffer.from(out()), readFileSync(join(root, 'vectors', group, name + '.json'))) !== 0) {
bad += 1;
console.error(`FORM ${group}/${f}: the authoring form differs`);
}
}
x.wv_free(pd, data.length);
}
}
console.log(`recomputed ${vectors} vectors and ${forms} authoring forms, under ${process.version}`);
if (bad > 0) {
console.error(`${bad} files failed`);
process.exit(1);
}
console.log('vector-conformance: every vector verifies');