Some checks failed
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m13s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m58s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 50s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 34s
standards workspaces build + test / map-rs (pull_request) Successful in 31s
standards workspaces build + test / mesh-rs (pull_request) Successful in 31s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no comparison (pull_request) Has been cancelled
no-vanity-metrics / WAI public text names no company or product (pull_request) Has been cancelled
standards workspaces build + test / arl-rs (pull_request) Has been cancelled
standards workspaces build + test / eoc-rs (pull_request) Has been cancelled
standards workspaces build + test / jcp-rs (pull_request) Has been cancelled
standards workspaces build + test / joule-ui-rs (pull_request) Has been cancelled
standards workspaces build + test / jouledesk-rs (pull_request) Has been cancelled
standards workspaces build + test / joulehook-rs (pull_request) Has been cancelled
standards workspaces build + test / proof-rs (pull_request) Has been cancelled
standards workspaces build + test / sandbox-rs (pull_request) Has been cancelled
standards workspaces build + test / sense-rs (pull_request) Has been cancelled
standards workspaces build + test / smart-byte-rs (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI section references / cited sections exist (pull_request) Has been cancelled
wai-vector vector graphics / linux-aarch64 (pull_request) Has been cancelled
wai-vector vector graphics / linux-x86_64 (pull_request) Has been cancelled
wai-vector vector graphics / macos-arm64 (pull_request) Has been cancelled
wai-vector vector graphics / wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
wai-vector vector graphics / wasm32 under Node (pull_request) Has been cancelled
wai-vector vector graphics / fuzz, peak allocation, comparison count (pull_request) Has been cancelled
workflow hygiene / every workflow supersedes its own queue (pull_request) Has been cancelled
standards workspaces build + test / openpay (pull_request) Has been cancelled
The reference crate of wai.graphics.vector (no_std + alloc, no dependencies; SHA-256 is wai-ops's, by #[path]): - WOS1 parsed with §2.1's load checks in order, and written; - WVG1 split (steps 1-3, nothing allocated before the exact length), the record pass (step 4), the cross-record checks (step 5(a)-(h)), the sets by digest with at most length + 1 bytes read (step 6), the glyphs (7), the interval check (8) and the per-frame cost (9); - the clock with the frame range by division, track evaluation, and placement; - the JSON authoring form, one member per field, packing back to the same bytes; - the CLI: check, cost, pack, unpack, vectors gen and verify; - a wasm32 module and verify.mjs for Node. The corpus wai/vector-conformance/ (arith, canonical, refusals, geometry, cost) with its README's provenance line and a sha256.json sidecar naming the div255 table; wai-vector.yml with native, wasip2, node and fuzz jobs. Co-Authored-By: Claude Opus 5.5 <[email protected]>
486 lines
17 KiB
Rust
486 lines
17 KiB
Rust
//! The JSON authoring form (§2.12, informative): one member per field of `WVG1`,
|
|
//! which makes a vector reviewable. The form is never a payload, and a sink never
|
|
//! reads it.
|
|
//!
|
|
//! [`to_json`] writes every field as carried, zero fields included (named
|
|
//! `zero@<offset>`), so any payload whose length is exact for its header converts to
|
|
//! the form and back to the same bytes. A payload that cannot be split by its header
|
|
//! is written as `{"raw": "<hex>"}`. [`to_bytes`] reads either form.
|
|
//!
|
|
//! Layout: `header` is an object; `sizes`, `rates`, `sets`, `tracks`, `paths`,
|
|
//! `texts` and `items` are arrays of objects; `tables` (17 entries), `paints`
|
|
//! (`[r, g, b, a]`), `keys` (`[t, value, interp, zero]`), `path_points`
|
|
//! (`[x, y, tag]`), `glyphs` (`[gid, zero, x, y]`) and `clusters`
|
|
//! (`[glyph_count, zero, byte_first, byte_len]`) are arrays of arrays; `path_ends` is
|
|
//! an array of integers; `strings` is a string, or `{"hex": …}` when it is not UTF-8;
|
|
//! `inline` is hexadecimal. A `note` member is ignored.
|
|
|
|
use alloc::format;
|
|
use alloc::string::String;
|
|
use alloc::vec::Vec;
|
|
|
|
use crate::json::{self, Value, hex, push_int, push_str, unhex};
|
|
use crate::wos1::Point;
|
|
use crate::wvg1::*;
|
|
|
|
fn line(out: &mut String, key: &str, v: &str, last: bool) {
|
|
push_str(out, key);
|
|
out.push(':');
|
|
out.push_str(v);
|
|
if !last {
|
|
out.push(',');
|
|
}
|
|
out.push('\n');
|
|
}
|
|
|
|
fn arr_lines(out: &mut String, key: &str, rows: Vec<String>, last: bool) {
|
|
push_str(out, key);
|
|
out.push_str(":[");
|
|
if !rows.is_empty() {
|
|
out.push('\n');
|
|
}
|
|
let n = rows.len();
|
|
for (i, r) in rows.into_iter().enumerate() {
|
|
out.push_str(&r);
|
|
if i + 1 < n {
|
|
out.push(',');
|
|
}
|
|
out.push('\n');
|
|
}
|
|
out.push(']');
|
|
if !last {
|
|
out.push(',');
|
|
}
|
|
out.push('\n');
|
|
}
|
|
|
|
fn ints(v: &[i128]) -> String {
|
|
let mut s = String::from("[");
|
|
for (i, x) in v.iter().enumerate() {
|
|
if i > 0 {
|
|
s.push(',');
|
|
}
|
|
push_int(&mut s, *x);
|
|
}
|
|
s.push(']');
|
|
s
|
|
}
|
|
|
|
fn bytes_arr(b: &[u8]) -> String {
|
|
ints(&b.iter().map(|&x| i128::from(x)).collect::<Vec<_>>())
|
|
}
|
|
|
|
fn o(members: &[(&str, String)]) -> String {
|
|
let mut s = String::from("{");
|
|
for (i, (k, v)) in members.iter().enumerate() {
|
|
if i > 0 {
|
|
s.push(',');
|
|
}
|
|
push_str(&mut s, k);
|
|
s.push(':');
|
|
s.push_str(v);
|
|
}
|
|
s.push('}');
|
|
s
|
|
}
|
|
|
|
fn n(v: impl Into<i128>) -> String {
|
|
let mut s = String::new();
|
|
push_int(&mut s, v.into());
|
|
s
|
|
}
|
|
|
|
/// The form of a payload, with an optional note.
|
|
pub fn to_json(bytes: &[u8], note: &str) -> String {
|
|
let mut out = String::from("{\n");
|
|
if !note.is_empty() {
|
|
let mut s = String::new();
|
|
push_str(&mut s, note);
|
|
line(&mut out, "note", &s, false);
|
|
}
|
|
let splittable = bytes.len() >= 80 && Header::read(bytes).exact_len() == bytes.len() as u64;
|
|
if !splittable {
|
|
let mut s = String::new();
|
|
push_str(&mut s, &hex(bytes));
|
|
line(&mut out, "raw", &s, true);
|
|
out.push_str("}\n");
|
|
return out;
|
|
}
|
|
let d = read_tables(bytes, Header::read(bytes));
|
|
let h = &d.header;
|
|
let magic = if h.magic.iter().all(|c| c.is_ascii_graphic()) {
|
|
let mut s = String::new();
|
|
push_str(&mut s, core::str::from_utf8(&h.magic).unwrap_or(""));
|
|
s
|
|
} else {
|
|
bytes_arr(&h.magic)
|
|
};
|
|
let header = o(&[
|
|
("magic", magic),
|
|
("format", n(h.format)),
|
|
("tb", n(h.tb)),
|
|
("t0", n(h.t0)),
|
|
("dur", n(h.dur)),
|
|
("design_w", n(h.design_w)),
|
|
("design_h", n(h.design_h)),
|
|
("n_sizes", n(h.n_sizes)),
|
|
("n_rates", n(h.n_rates)),
|
|
("n_sets", n(h.n_sets)),
|
|
("n_tables", n(h.n_tables)),
|
|
("n_paints", n(h.n_paints)),
|
|
("n_texts", n(h.n_texts)),
|
|
("n_tracks", n(h.n_tracks)),
|
|
("n_paths", n(h.n_paths)),
|
|
("n_items", n(h.n_items)),
|
|
("n_keys", n(h.n_keys)),
|
|
("n_glyphs", n(h.n_glyphs)),
|
|
("n_clusters", n(h.n_clusters)),
|
|
("path_contours", n(h.path_contours)),
|
|
("path_points", n(h.path_points)),
|
|
("strings_len", n(h.strings_len)),
|
|
("inline_len", n(h.inline_len)),
|
|
("held_len", n(h.held_len)),
|
|
("zero@72", bytes_arr(&h.zero)),
|
|
]);
|
|
line(&mut out, "header", &header, false);
|
|
arr_lines(&mut out, "sizes", d.sizes.iter().map(|s| o(&[("w", n(s.w)), ("h", n(s.h)), ("cost", n(s.cost))])).collect(), false);
|
|
arr_lines(&mut out, "rates", d.rates.iter().map(|r| o(&[("num", n(r.num)), ("den", n(r.den))])).collect(), false);
|
|
arr_lines(
|
|
&mut out,
|
|
"sets",
|
|
d.sets
|
|
.iter()
|
|
.map(|s| {
|
|
let mut h = String::new();
|
|
push_str(&mut h, &hex(&s.sha256));
|
|
o(&[("carriage", n(s.carriage)), ("zero@1", bytes_arr(&s.zero)), ("length", n(s.length)), ("sha256", h)])
|
|
})
|
|
.collect(),
|
|
false,
|
|
);
|
|
arr_lines(&mut out, "tables", d.tables.iter().map(|t| ints(&t.e.map(i128::from))).collect(), false);
|
|
arr_lines(&mut out, "paints", d.paints.iter().map(|p| bytes_arr(&p.rgba)).collect(), false);
|
|
arr_lines(
|
|
&mut out,
|
|
"tracks",
|
|
d.tracks.iter().map(|t| o(&[("kind", n(t.kind)), ("zero@1", n(t.zero1)), ("zero@2", n(t.zero2)), ("n_keys", n(t.n_keys))])).collect(),
|
|
false,
|
|
);
|
|
arr_lines(
|
|
&mut out,
|
|
"keys",
|
|
d.keys.iter().map(|k| format!("[{},{},{},{}]", k.t, k.value, k.interp, bytes_arr(&k.zero))).collect(),
|
|
false,
|
|
);
|
|
arr_lines(
|
|
&mut out,
|
|
"paths",
|
|
d.paths
|
|
.iter()
|
|
.map(|p| {
|
|
o(&[
|
|
("n_contours", n(p.n_contours)),
|
|
("n_points", n(p.n_points)),
|
|
("zero@4", n(p.zero)),
|
|
("xmin", n(p.bbox[0])),
|
|
("ymin", n(p.bbox[1])),
|
|
("xmax", n(p.bbox[2])),
|
|
("ymax", n(p.bbox[3])),
|
|
])
|
|
})
|
|
.collect(),
|
|
false,
|
|
);
|
|
line(&mut out, "path_ends", &ints(&d.path_ends.iter().map(|&e| i128::from(e)).collect::<Vec<_>>()), false);
|
|
arr_lines(&mut out, "path_points", d.path_points.iter().map(|p| format!("[{},{},{}]", p.x, p.y, p.tag)).collect(), false);
|
|
arr_lines(
|
|
&mut out,
|
|
"texts",
|
|
d.texts
|
|
.iter()
|
|
.map(|t| {
|
|
o(&[
|
|
("purpose", n(t.purpose)),
|
|
("dir", n(t.dir)),
|
|
("lang_len", n(t.lang_len)),
|
|
("mode", n(t.mode)),
|
|
("group", n(t.group)),
|
|
("ruby", n(t.ruby)),
|
|
("ruby_start", n(t.ruby_start)),
|
|
("ruby_len", n(t.ruby_len)),
|
|
("str_len", n(t.str_len)),
|
|
])
|
|
})
|
|
.collect(),
|
|
false,
|
|
);
|
|
arr_lines(
|
|
&mut out,
|
|
"items",
|
|
d.items
|
|
.iter()
|
|
.map(|it| {
|
|
o(&[
|
|
("kind", n(it.kind)),
|
|
("rule", n(it.rule)),
|
|
("zero@2", n(it.zero2)),
|
|
("ref", n(it.reference)),
|
|
("paint", n(it.paint)),
|
|
("text", n(it.text)),
|
|
("zero@10", n(it.zero10)),
|
|
("n_glyphs", n(it.n_glyphs)),
|
|
("n_clusters", n(it.n_clusters)),
|
|
("t_on", n(it.t_on)),
|
|
("t_off", n(it.t_off)),
|
|
("unit_scale", n(it.unit_scale)),
|
|
("m00", n(it.slots[0])),
|
|
("m01", n(it.slots[1])),
|
|
("m10", n(it.slots[2])),
|
|
("m11", n(it.slots[3])),
|
|
("tx", n(it.slots[4])),
|
|
("ty", n(it.slots[5])),
|
|
("op", n(it.slots[6])),
|
|
("zero@42", bytes_arr(&it.zero42)),
|
|
])
|
|
})
|
|
.collect(),
|
|
false,
|
|
);
|
|
arr_lines(&mut out, "glyphs", d.glyphs.iter().map(|g| format!("[{},{},{},{}]", g.gid, g.zero, g.x, g.y)).collect(), false);
|
|
arr_lines(
|
|
&mut out,
|
|
"clusters",
|
|
d.clusters.iter().map(|c| format!("[{},{},{},{}]", c.glyph_count, c.zero, c.byte_first, c.byte_len)).collect(),
|
|
false,
|
|
);
|
|
let strings = match core::str::from_utf8(&d.strings) {
|
|
Ok(s) => {
|
|
let mut t = String::new();
|
|
push_str(&mut t, s);
|
|
t
|
|
}
|
|
Err(_) => {
|
|
let mut t = String::from("{\"hex\":");
|
|
push_str(&mut t, &hex(&d.strings));
|
|
t.push('}');
|
|
t
|
|
}
|
|
};
|
|
line(&mut out, "strings", &strings, false);
|
|
let mut inl = String::new();
|
|
push_str(&mut inl, &hex(&d.inline));
|
|
line(&mut out, "inline", &inl, true);
|
|
out.push_str("}\n");
|
|
out
|
|
}
|
|
|
|
type R<T> = Result<T, String>;
|
|
|
|
fn member<'a>(v: &'a Value, k: &str) -> R<&'a Value> {
|
|
v.get(k).ok_or_else(|| format!("missing member {k}"))
|
|
}
|
|
|
|
fn int_in(v: &Value, lo: i128, hi: i128, what: &str) -> R<i128> {
|
|
match v.as_int() {
|
|
Some(x) if (lo..=hi).contains(&x) => Ok(x),
|
|
_ => Err(format!("{what}: not an integer in {lo}..={hi}")),
|
|
}
|
|
}
|
|
|
|
fn uf(v: &Value, k: &str, bits: u32) -> R<i128> {
|
|
int_in(member(v, k)?, 0, (1i128 << bits) - 1, k)
|
|
}
|
|
|
|
fn sf(v: &Value, k: &str, bits: u32) -> R<i128> {
|
|
int_in(member(v, k)?, -(1i128 << (bits - 1)), (1i128 << (bits - 1)) - 1, k)
|
|
}
|
|
|
|
fn arr<'a>(v: &'a Value, k: &str) -> R<&'a [Value]> {
|
|
member(v, k)?.as_arr().ok_or_else(|| format!("{k}: not an array"))
|
|
}
|
|
|
|
fn byte_list<const N: usize>(v: &Value, what: &str) -> R<[u8; N]> {
|
|
let a = v.as_arr().ok_or_else(|| format!("{what}: not an array"))?;
|
|
if a.len() != N {
|
|
return Err(format!("{what}: {N} bytes"));
|
|
}
|
|
let mut out = [0u8; N];
|
|
for (o, x) in out.iter_mut().zip(a) {
|
|
*o = int_in(x, 0, 255, what)? as u8;
|
|
}
|
|
Ok(out)
|
|
}
|
|
|
|
fn tuple<'a>(v: &'a Value, len: usize, what: &str) -> R<&'a [Value]> {
|
|
match v.as_arr() {
|
|
Some(a) if a.len() == len => Ok(a),
|
|
_ => Err(format!("{what}: an array of {len}")),
|
|
}
|
|
}
|
|
|
|
/// The payload bytes of a form.
|
|
pub fn to_bytes(text: &str) -> R<Vec<u8>> {
|
|
let v = json::parse(text).map_err(|e| format!("not JSON at byte {}", e.at))?;
|
|
if let Some(raw) = v.get("raw") {
|
|
return raw.as_str().and_then(unhex).ok_or_else(|| String::from("raw: not hexadecimal"));
|
|
}
|
|
let hv = member(&v, "header")?;
|
|
let magic = match member(hv, "magic")? {
|
|
Value::Str(s) if s.len() == 4 => {
|
|
let b = s.as_bytes();
|
|
[b[0], b[1], b[2], b[3]]
|
|
}
|
|
m => byte_list::<4>(m, "magic")?,
|
|
};
|
|
let h = Header {
|
|
magic,
|
|
format: uf(hv, "format", 32)? as u32,
|
|
tb: uf(hv, "tb", 32)? as u32,
|
|
t0: uf(hv, "t0", 32)? as u32,
|
|
dur: uf(hv, "dur", 32)? as u32,
|
|
design_w: uf(hv, "design_w", 16)? as u16,
|
|
design_h: uf(hv, "design_h", 16)? as u16,
|
|
n_sizes: uf(hv, "n_sizes", 8)? as u8,
|
|
n_rates: uf(hv, "n_rates", 8)? as u8,
|
|
n_sets: uf(hv, "n_sets", 8)? as u8,
|
|
n_tables: uf(hv, "n_tables", 8)? as u8,
|
|
n_paints: uf(hv, "n_paints", 16)? as u16,
|
|
n_texts: uf(hv, "n_texts", 16)? as u16,
|
|
n_tracks: uf(hv, "n_tracks", 16)? as u16,
|
|
n_paths: uf(hv, "n_paths", 16)? as u16,
|
|
n_items: uf(hv, "n_items", 32)? as u32,
|
|
n_keys: uf(hv, "n_keys", 32)? as u32,
|
|
n_glyphs: uf(hv, "n_glyphs", 32)? as u32,
|
|
n_clusters: uf(hv, "n_clusters", 32)? as u32,
|
|
path_contours: uf(hv, "path_contours", 32)? as u32,
|
|
path_points: uf(hv, "path_points", 32)? as u32,
|
|
strings_len: uf(hv, "strings_len", 32)? as u32,
|
|
inline_len: uf(hv, "inline_len", 32)? as u32,
|
|
held_len: uf(hv, "held_len", 32)? as u32,
|
|
zero: byte_list::<8>(member(hv, "zero@72")?, "zero@72")?,
|
|
};
|
|
let mut d = Doc { header: h, ..Doc::default() };
|
|
for s in arr(&v, "sizes")? {
|
|
d.sizes.push(Size { w: uf(s, "w", 16)? as u16, h: uf(s, "h", 16)? as u16, cost: uf(s, "cost", 32)? as u32 });
|
|
}
|
|
for r in arr(&v, "rates")? {
|
|
d.rates.push(Rate { num: uf(r, "num", 32)? as u32, den: uf(r, "den", 32)? as u32 });
|
|
}
|
|
for s in arr(&v, "sets")? {
|
|
let sha = member(s, "sha256")?.as_str().and_then(unhex).filter(|b| b.len() == 32).ok_or("sha256: 64 hex digits")?;
|
|
let mut sha256 = [0u8; 32];
|
|
sha256.copy_from_slice(&sha);
|
|
d.sets.push(SetRec {
|
|
carriage: uf(s, "carriage", 8)? as u8,
|
|
zero: byte_list::<3>(member(s, "zero@1")?, "zero@1")?,
|
|
length: uf(s, "length", 32)? as u32,
|
|
sha256,
|
|
});
|
|
}
|
|
for t in arr(&v, "tables")? {
|
|
let a = tuple(t, 17, "table")?;
|
|
let mut e = [0u32; 17];
|
|
for (x, y) in e.iter_mut().zip(a) {
|
|
*x = int_in(y, 0, u32::MAX.into(), "E")? as u32;
|
|
}
|
|
d.tables.push(Table { e });
|
|
}
|
|
for p in arr(&v, "paints")? {
|
|
d.paints.push(Paint { rgba: byte_list::<4>(p, "paint")? });
|
|
}
|
|
for t in arr(&v, "tracks")? {
|
|
d.tracks.push(Track {
|
|
kind: uf(t, "kind", 8)? as u8,
|
|
zero1: uf(t, "zero@1", 8)? as u8,
|
|
zero2: uf(t, "zero@2", 16)? as u16,
|
|
n_keys: uf(t, "n_keys", 32)? as u32,
|
|
});
|
|
}
|
|
for k in arr(&v, "keys")? {
|
|
let a = tuple(k, 4, "key")?;
|
|
d.keys.push(Key {
|
|
t: int_in(&a[0], 0, u32::MAX.into(), "t")? as u32,
|
|
value: int_in(&a[1], i32::MIN.into(), i32::MAX.into(), "value")? as i32,
|
|
interp: int_in(&a[2], 0, 255, "interp")? as u8,
|
|
zero: byte_list::<3>(&a[3], "key zero")?,
|
|
});
|
|
}
|
|
for p in arr(&v, "paths")? {
|
|
d.paths.push(Path {
|
|
n_contours: uf(p, "n_contours", 16)? as u16,
|
|
n_points: uf(p, "n_points", 16)? as u16,
|
|
zero: uf(p, "zero@4", 32)? as u32,
|
|
bbox: [sf(p, "xmin", 16)? as i16, sf(p, "ymin", 16)? as i16, sf(p, "xmax", 16)? as i16, sf(p, "ymax", 16)? as i16],
|
|
});
|
|
}
|
|
for e in arr(&v, "path_ends")? {
|
|
d.path_ends.push(int_in(e, 0, 65_535, "path end")? as u16);
|
|
}
|
|
for p in arr(&v, "path_points")? {
|
|
let a = tuple(p, 3, "point")?;
|
|
d.path_points.push(Point {
|
|
x: int_in(&a[0], -32_768, 32_767, "x")? as i16,
|
|
y: int_in(&a[1], -32_768, 32_767, "y")? as i16,
|
|
tag: int_in(&a[2], 0, 255, "tag")? as u8,
|
|
});
|
|
}
|
|
for t in arr(&v, "texts")? {
|
|
d.texts.push(Text {
|
|
purpose: uf(t, "purpose", 8)? as u8,
|
|
dir: uf(t, "dir", 8)? as u8,
|
|
lang_len: uf(t, "lang_len", 8)? as u8,
|
|
mode: uf(t, "mode", 8)? as u8,
|
|
group: uf(t, "group", 16)? as u16,
|
|
ruby: uf(t, "ruby", 16)? as u16,
|
|
ruby_start: uf(t, "ruby_start", 32)? as u32,
|
|
ruby_len: uf(t, "ruby_len", 32)? as u32,
|
|
str_len: uf(t, "str_len", 32)? as u32,
|
|
});
|
|
}
|
|
for it in arr(&v, "items")? {
|
|
let mut slots = [0u16; 7];
|
|
for (s, k) in slots.iter_mut().zip(["m00", "m01", "m10", "m11", "tx", "ty", "op"]) {
|
|
*s = uf(it, k, 16)? as u16;
|
|
}
|
|
d.items.push(Item {
|
|
kind: uf(it, "kind", 8)? as u8,
|
|
rule: uf(it, "rule", 8)? as u8,
|
|
zero2: uf(it, "zero@2", 16)? as u16,
|
|
reference: uf(it, "ref", 16)? as u16,
|
|
paint: uf(it, "paint", 16)? as u16,
|
|
text: uf(it, "text", 16)? as u16,
|
|
zero10: uf(it, "zero@10", 16)? as u16,
|
|
n_glyphs: uf(it, "n_glyphs", 16)? as u16,
|
|
n_clusters: uf(it, "n_clusters", 16)? as u16,
|
|
t_on: uf(it, "t_on", 32)? as u32,
|
|
t_off: uf(it, "t_off", 32)? as u32,
|
|
unit_scale: uf(it, "unit_scale", 32)? as u32,
|
|
slots,
|
|
zero42: byte_list::<6>(member(it, "zero@42")?, "zero@42")?,
|
|
});
|
|
}
|
|
for g in arr(&v, "glyphs")? {
|
|
let a = tuple(g, 4, "glyph")?;
|
|
d.glyphs.push(GlyphRec {
|
|
gid: int_in(&a[0], 0, 65_535, "gid")? as u16,
|
|
zero: int_in(&a[1], 0, 65_535, "zero")? as u16,
|
|
x: int_in(&a[2], i32::MIN.into(), i32::MAX.into(), "x")? as i32,
|
|
y: int_in(&a[3], i32::MIN.into(), i32::MAX.into(), "y")? as i32,
|
|
});
|
|
}
|
|
for c in arr(&v, "clusters")? {
|
|
let a = tuple(c, 4, "cluster")?;
|
|
d.clusters.push(Cluster {
|
|
glyph_count: int_in(&a[0], 0, 65_535, "glyph_count")? as u16,
|
|
zero: int_in(&a[1], 0, 65_535, "zero")? as u16,
|
|
byte_first: int_in(&a[2], 0, u32::MAX.into(), "byte_first")? as u32,
|
|
byte_len: int_in(&a[3], 0, u32::MAX.into(), "byte_len")? as u32,
|
|
});
|
|
}
|
|
d.strings = match member(&v, "strings")? {
|
|
Value::Str(s) => s.as_bytes().to_vec(),
|
|
x => member(x, "hex")?.as_str().and_then(unhex).ok_or("strings: hex")?,
|
|
};
|
|
d.inline = member(&v, "inline")?.as_str().and_then(unhex).ok_or("inline: hex")?;
|
|
Ok(d.write())
|
|
}
|