//! The in-house sets (`graphics-vector.md` §4, Annexes A and B): the default stroke set's //! digest equals Annex B; Annex A states the expansion's table; the expansion is //! deterministic and re-derives both committed sets from their committed sources; the //! sets' notes carry their licence; and the test repertoire's joining table picks the //! forms of the test words. //! //! Every leg runs these: natively, and under WASI with the `wai` directory preopened. use std::path::PathBuf; use wai_vector::builtin; use wai_vector::json::hex; use wai_vector::sha256::sha256; use wai_vector::vectors::{repertoire, stroke, text}; use wai_vector::wsf1; fn wai() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(rel: &str) -> Vec { std::fs::read(wai().join(rel)).unwrap_or_else(|e| panic!("{rel}: {e}")) } /// The text of the extension from `heading` up to the next heading of the same level. fn section(heading: &str) -> String { let doc = String::from_utf8(read("extensions/graphics-vector.md")).unwrap(); let start = doc.find(heading).unwrap_or_else(|| panic!("no {heading}")); let level = heading.split(' ').next().unwrap(); let rest = &doc[start + heading.len()..]; let end = rest.find(&format!("\n{level} ")).unwrap_or(rest.len()); rest[..end].to_string() } /// The first run of 64 lowercase hexadecimal digits on the line holding `label`. fn digest_on(text: &str, label: &str) -> String { let line = text.lines().find(|l| l.contains(label)).unwrap_or_else(|| panic!("no line with {label}")); line.split(|c: char| !c.is_ascii_hexdigit()).find(|w| w.len() == 64).unwrap_or_else(|| panic!("no digest on {line}")).to_string() } /// The number before " bytes" on the line holding `label`, commas removed. fn length_on(text: &str, label: &str) -> usize { let line = text.lines().find(|l| l.contains(label)).unwrap(); let before = &line[..line.find(" bytes").unwrap()]; before.rsplit("length ").next().unwrap().replace(',', "").parse().unwrap() } #[test] fn the_stroke_sets_digest_equals_annex_b() { let b = section("## Annex B."); let (set_digest, src_digest) = (digest_on(&b, "the set (`WOS1`)"), digest_on(&b, "its source (`WSF1`)")); // The bundled bytes, their constant and the annex agree. assert_eq!(hex(&sha256(builtin::STROKE_ASCII)), set_digest, "the bundled set's SHA-256 is Annex B's"); assert_eq!(hex(&builtin::STROKE_ASCII_SHA256), set_digest, "the digest constant is Annex B's"); assert_eq!(builtin::STROKE_ASCII.len(), length_on(&b, "the set (`WOS1`)")); assert_eq!(builtin::STROKE_ASCII_LEN as usize, builtin::STROKE_ASCII.len()); // The committed source is Annex B's, and expanding it by Annex A gives the set. let src = read("vector-conformance/sources/stroke-ascii.wsf1"); assert_eq!(hex(&sha256(&src)), src_digest, "the committed source's SHA-256 is Annex B's"); assert_eq!(src.len(), length_on(&b, "its source (`WSF1`)")); let set = wsf1::expand(&src).unwrap().write(); assert_eq!(hex(&sha256(&set)), set_digest, "Annex A's expansion of the source gives Annex B's set"); assert!(set == builtin::STROKE_ASCII); // Coverage: printable ASCII, each glyph ID its code point. let w = wai_vector::wos1::parse(builtin::STROKE_ASCII).unwrap(); assert_eq!(w.glyphs.iter().map(|g| g.gid).collect::>(), (0x20..=0x7E).collect::>()); } #[test] fn annex_a_states_the_table() { let a = section("### A.2 "); let block = &a[a.find("```text").unwrap() + 7..]; let block = &block[..block.find("```").unwrap()]; let hexs: String = block.split_whitespace().collect(); assert_eq!(hexs, hex(&wsf1::unit_table_bytes()), "Annex A's bytes are the table the expansion reads"); assert_eq!(digest_on(&a, "Their SHA-256"), hex(&sha256(&wsf1::unit_table_bytes()))); } #[test] fn the_expansion_is_deterministic() { // Offsets worked by hand. (300, 400) has an exact length; (100, 200) does not: // isqrt(50000) = 223, and −18000/223 = −80.72 rounds to −81, 9000/223 = 40.36 to 40. assert_eq!(wsf1::normal((300, 400), 80), (-64, 48)); assert_eq!(wsf1::normal((100, 200), 90), (-81, 40)); assert_eq!(wsf1::normal((-7, 3), 80), (-34, -80), "isqrt(58) = 7: (R_rat(−240, 7), R_rat(−560, 7))"); // Both committed sets re-derive from their committed sources, twice, byte for byte. for name in ["stroke-ascii", "test-repertoire"] { let src = read(&format!("vector-conformance/sources/{name}.wsf1")); let set = read(&format!("vector-conformance/sets/{name}.wos1")); let a = wsf1::expand(&src).unwrap().write(); let b = wsf1::expand(&src).unwrap().write(); assert!(a == b, "{name}: two expansions agree"); assert!(a == set, "{name}: the expansion reproduces the committed set"); // The source written by the generator is the committed one. let generated = if name == "stroke-ascii" { stroke::ascii_source_bytes() } else { repertoire::repertoire_source_bytes() }; assert!(generated == src, "{name}: the generator writes the committed source"); // The set's notes name its source by SHA-256, and the expansion. let w = wai_vector::wos1::parse(&set).unwrap(); let notes = String::from_utf8(w.notes.clone()).unwrap(); assert!(notes.starts_with(&format!("parent_sha256={}\nconverter=wsf1-expansion/1\n", hex(&sha256(&src)))), "{name}"); // A polygon-only set: every point on-curve, overlaps not removed. assert!(w.points.iter().all(|p| p.tag == 0) && w.flags == 0, "{name}"); } } #[test] fn each_set_carries_its_licence() { for name in ["stroke-ascii", "test-repertoire"] { let set = read(&format!("vector-conformance/sets/{name}.wos1")); let w = wai_vector::wos1::parse(&set).unwrap(); let notes = String::from_utf8(w.notes).unwrap(); let (keys, licence) = notes.split_once("\n\n").expect("key lines, a blank line, the licence text"); let key = |k: &str| keys.lines().find_map(|l| l.strip_prefix(&format!("{k}="))).unwrap_or_else(|| panic!("{name}: no {k}")); assert_eq!(key("licence"), "Apache-2.0", "{name}"); assert_eq!(key("licence_sha256"), hex(&sha256(licence.as_bytes())), "{name}: the verifier's licence check"); assert_eq!(key("rfn"), "none", "{name}"); assert!(key("source").contains("no outline is traced from any font"), "{name}"); assert!(licence.contains("Apache License") && licence.contains("Version 2.0"), "{name}"); } } #[test] fn the_joining_table_picks_the_forms_of_the_test_words() { for (word, want) in text::test_words() { let got: Vec = repertoire::shape_arabic(word).0.iter().map(|g| g.gid).collect(); assert_eq!(got, want, "{word}"); } // Every form the table names is drawn in the test repertoire. let src = repertoire::repertoire_source(); for (_, _, forms) in repertoire::JOINING { for f in forms { assert!(src.glyph(f).is_some(), "form {f:04X} is drawn"); } } } /// The winding number of the closed polygon contours of a glyph about `(px, py)`. fn winding(contours: &[Vec<(i64, i64)>], px: i64, py: i64) -> i64 { let mut w = 0; for c in contours { for i in 0..c.len() { let (a, b) = (c[i], c[(i + 1) % c.len()]); let side = (b.0 - a.0) * (py - a.1) - (b.1 - a.1) * (px - a.0); if a.1 <= py && py < b.1 && side > 0 { w += 1; } else if b.1 <= py && py < a.1 && side < 0 { w -= 1; } } } w } /// Whether `(px, py)` lies within `r` of a centre-line polyline, exactly in integers. fn within(stroke: &[(i16, i16)], px: i64, py: i64, r: i64) -> bool { let p: Vec<(i64, i64)> = stroke.iter().map(|&(x, y)| (i64::from(x), i64::from(y))).collect(); let d2 = |a: (i64, i64)| (px - a.0).pow(2) + (py - a.1).pow(2) <= r * r; if p.len() == 1 { return d2(p[0]); } p.windows(2).any(|s| { let (a, b) = (s[0], s[1]); let (dx, dy) = (b.0 - a.0, b.1 - a.1); let l = dx * dx + dy * dy; let t = (px - a.0) * dx + (py - a.1) * dy; if t <= 0 { d2(a) } else if t >= l { d2(b) } else { let c = i128::from((px - a.0) * dy - (py - a.1) * dx); c * c <= i128::from(r * r) * i128::from(l) } }) } #[test] fn the_expansion_covers_the_pens_path() { // Sampled every 20 outline units: a point within ρ − 4 of a centre line is inside // the glyph's fill (non-zero winding), and a point farther than ρ + 8 from every // centre line is outside it. The bands absorb the 64-gon's chords and the offsets' // rounding; the inner loops of joins must not open a hole, and no outer join may // cut its corner. for src in [stroke::ascii_source(), repertoire::repertoire_source()] { let rho = i64::from(src.radius); let set = wsf1::expand(&src.write()).unwrap(); for g in &src.glyphs { let rec = set.glyph(g.gid).unwrap(); let pts = set.glyph_points(rec); let mut contours = Vec::new(); let mut start = 0usize; for &e in set.glyph_ends(rec) { contours.push(pts[start..=e as usize].iter().map(|p| (i64::from(p.x), i64::from(p.y))).collect::>()); start = e as usize + 1; } let (x0, y0, x1, y1) = (i64::from(rec.xmin) - 20, i64::from(rec.ymin) - 20, i64::from(rec.xmax) + 20, i64::from(rec.ymax) + 20); let (mut inside, mut outside) = (0, 0); let mut y = y0 + 3; while y <= y1 { let mut x = x0 + 7; while x <= x1 { let near = g.strokes.iter().any(|s| within(s, x, y, rho - 4)); let far = !g.strokes.iter().any(|s| within(s, x, y, rho + 8)); let w = winding(&contours, x, y); if near { inside += 1; assert!(w != 0, "glyph {:04X}: ({x}, {y}) is within the pen's path but not filled", g.gid); } if far { outside += 1; assert!(w == 0, "glyph {:04X}: ({x}, {y}) is outside the pen's path but winds {w}", g.gid); } x += 20; } y += 20; } assert!(g.strokes.is_empty() || inside > 0, "glyph {:04X}: the samples reach its strokes", g.gid); assert!(outside > 0); } } }