- 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]>
88 lines
3.5 KiB
JavaScript
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');
|