//! `WVG1` (§2.2–§2.5, §2.7): the header and tables, parsed with the refusal order of //! §2.7, and the canonical write. //! //! - [`split`] reads the tables once the exact length holds (steps 1–3). Nothing is //! allocated before that, so a header's counts cannot reserve memory the bytes do not //! back. //! - [`parse`] adds the record pass (step 4) and the cross-record checks (step 5). //! - [`load`] adds the sets (step 6), the glyph check (step 7), the interval check //! (step 8) and the cost (step 9): a payload [`load`] returns renders at every //! listed size, rate and frame. //! //! [`Doc`] holds every field as carried, zero fields included, so [`Doc::write`] of a //! parsed payload gives back its bytes: the verifier's round trip (§2.5). use alloc::vec; use alloc::vec::Vec; use core::cmp::Ordering; use crate::arith::gcd; use crate::bounds::*; use crate::refuse::{Code, Refusal, refuse}; use crate::wos1::{self, Point, Wos1, rd16, rd32}; #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Header { pub magic: [u8; 4], pub format: u32, pub tb: u32, pub t0: u32, pub dur: u32, pub design_w: u16, pub design_h: u16, pub n_sizes: u8, pub n_rates: u8, pub n_sets: u8, pub n_tables: u8, pub n_paints: u16, pub n_texts: u16, pub n_tracks: u16, pub n_paths: u16, pub n_items: u32, pub n_keys: u32, pub n_glyphs: u32, pub n_clusters: u32, pub path_contours: u32, pub path_points: u32, pub strings_len: u32, pub inline_len: u32, pub held_len: u32, pub zero: [u8; 8], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Size { pub w: u16, pub h: u16, pub cost: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Rate { pub num: u32, pub den: u32, } impl Rate { /// Ticks per frame, `P = den·tb/num` (§3.1); an integer once the record pass holds. pub fn ticks_per_frame(&self, tb: u32) -> u64 { u64::from(self.den) * u64::from(tb) / u64::from(self.num) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SetRec { /// 0 held, 1 inline. pub carriage: u8, pub zero: [u8; 3], pub length: u32, pub sha256: [u8; 32], } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Table { pub e: [u32; 17], } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Paint { pub rgba: [u8; 4], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Track { /// 1 opacity, 2 translation, 3 matrix entry. pub kind: u8, pub zero1: u8, pub zero2: u16, pub n_keys: u32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Key { pub t: u32, pub value: i32, /// 0 hold, 1 linear, `2 + j` easing table `j`. pub interp: u8, pub zero: [u8; 3], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Path { pub n_contours: u16, pub n_points: u16, pub zero: u32, /// `xmin, ymin, xmax, ymax`. pub bbox: [i16; 4], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Text { pub purpose: u8, pub dir: u8, pub lang_len: u8, pub mode: u8, pub group: u16, pub ruby: u16, pub ruby_start: u32, pub ruby_len: u32, pub str_len: u32, } pub const KIND_BEGIN: u8 = 0; pub const KIND_END: u8 = 1; pub const KIND_PATH: u8 = 2; pub const KIND_RUN: u8 = 3; /// The track slots of an item, in their order. pub const M00: usize = 0; pub const M01: usize = 1; pub const M10: usize = 2; pub const M11: usize = 3; pub const TX: usize = 4; pub const TY: usize = 5; pub const OP: usize = 6; /// The track kind each slot requires (§2.4). pub const SLOT_KIND: [u8; 7] = [3, 3, 3, 3, 2, 2, 1]; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Item { pub kind: u8, pub rule: u8, pub zero2: u16, pub reference: u16, pub paint: u16, pub text: u16, pub zero10: u16, pub n_glyphs: u16, pub n_clusters: u16, pub t_on: u32, pub t_off: u32, pub unit_scale: u32, pub slots: [u16; 7], pub zero42: [u8; 6], } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct GlyphRec { pub gid: u16, pub zero: u16, pub x: i32, pub y: i32, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cluster { pub glyph_count: u16, pub zero: u16, pub byte_first: u32, pub byte_len: u32, } /// Where each owner's records start in their tables: prefix sums of the owners' /// counts, valid once the sum rules (step 5(a)) hold. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Ix { pub track_key0: Vec, pub path_end0: Vec, pub path_pt0: Vec, /// The offset of each block's language tag in `STRINGS`; its string follows it. pub text_off: Vec, /// For each item, its first glyph record and first cluster (runs only). pub item_glyph0: Vec, pub item_cluster0: Vec, } /// A `WVG1` payload, every field as carried. #[derive(Clone, Debug, PartialEq, Eq, Default)] pub struct Doc { pub header: Header, pub sizes: Vec, pub rates: Vec, pub sets: Vec, pub tables: Vec, pub paints: Vec, pub tracks: Vec, pub keys: Vec, pub paths: Vec, pub path_ends: Vec, pub path_points: Vec, pub texts: Vec, pub items: Vec, pub glyphs: Vec, pub clusters: Vec, pub strings: Vec, pub inline: Vec, pub ix: Ix, } impl Header { /// The exact length the counts give (§2.2). pub fn exact_len(&self) -> u64 { let h = self; WVG1_HEADER as u64 + REC_SIZE * u64::from(h.n_sizes) + REC_RATE * u64::from(h.n_rates) + REC_SET * u64::from(h.n_sets) + REC_TABLE * u64::from(h.n_tables) + REC_PAINT * u64::from(h.n_paints) + REC_TRACK * u64::from(h.n_tracks) + REC_KEY * u64::from(h.n_keys) + REC_PATH * u64::from(h.n_paths) + REC_PATH_END * u64::from(h.path_contours) + REC_PATH_POINT * u64::from(h.path_points) + REC_TEXT * u64::from(h.n_texts) + REC_ITEM * u64::from(h.n_items) + REC_GLYPH * u64::from(h.n_glyphs) + REC_CLUSTER * u64::from(h.n_clusters) + u64::from(h.strings_len) + u64::from(h.inline_len) } /// Read the 80-byte header. `b` holds at least 80 bytes. pub fn read(b: &[u8]) -> Header { let mut zero = [0u8; 8]; zero.copy_from_slice(&b[72..80]); Header { magic: [b[0], b[1], b[2], b[3]], format: rd32(b, 4), tb: rd32(b, 8), t0: rd32(b, 12), dur: rd32(b, 16), design_w: rd16(b, 20), design_h: rd16(b, 22), n_sizes: b[24], n_rates: b[25], n_sets: b[26], n_tables: b[27], n_paints: rd16(b, 28), n_texts: rd16(b, 30), n_tracks: rd16(b, 32), n_paths: rd16(b, 34), n_items: rd32(b, 36), n_keys: rd32(b, 40), n_glyphs: rd32(b, 44), n_clusters: rd32(b, 48), path_contours: rd32(b, 52), path_points: rd32(b, 56), strings_len: rd32(b, 60), inline_len: rd32(b, 64), held_len: rd32(b, 68), zero, } } pub fn write(&self, o: &mut Vec) { let h = self; o.extend_from_slice(&h.magic); for v in [h.format, h.tb, h.t0, h.dur] { o.extend_from_slice(&v.to_le_bytes()); } o.extend_from_slice(&h.design_w.to_le_bytes()); o.extend_from_slice(&h.design_h.to_le_bytes()); o.extend_from_slice(&[h.n_sizes, h.n_rates, h.n_sets, h.n_tables]); for v in [h.n_paints, h.n_texts, h.n_tracks, h.n_paths] { o.extend_from_slice(&v.to_le_bytes()); } for v in [h.n_items, h.n_keys, h.n_glyphs, h.n_clusters, h.path_contours, h.path_points, h.strings_len, h.inline_len, h.held_len] { o.extend_from_slice(&v.to_le_bytes()); } o.extend_from_slice(&h.zero); } } const PM: Code = Code::PayloadMalformed; const FR: Code = Code::FieldRange; /// Steps 1–3 of §2.7, then the tables read in file order. No record is validated. pub fn split(b: &[u8]) -> Result { // Step 1. A payload too short to hold its header has no readable magic. if b.len() < WVG1_HEADER { return refuse(PM, "1", "the header is incomplete", 0); } let h = Header::read(b); if &h.magic != b"WVG1" { return refuse(PM, "1", "magic", 0); } if h.format != 1 { return refuse(PM, "1", "format", 0); } if h.zero != [0; 8] { return refuse(PM, "1", "zero", 0); } // Step 2, in field order. let caps: [(&'static str, u64, u64); 15] = [ ("n_sizes", u64::from(h.n_sizes), SIZES_MAX), ("n_rates", u64::from(h.n_rates), RATES_MAX), ("n_sets", u64::from(h.n_sets), SETS_MAX), ("n_tables", u64::from(h.n_tables), TABLES_MAX), ("n_paints", u64::from(h.n_paints), PAINTS_MAX), ("n_paths", u64::from(h.n_paths), PATHS_MAX), ("n_items", u64::from(h.n_items), ITEMS_MAX), ("n_keys", u64::from(h.n_keys), KEYS_MAX), ("n_glyphs", u64::from(h.n_glyphs), GLYPHS_MAX), ("n_clusters", u64::from(h.n_clusters), CLUSTERS_MAX), ("path_contours", u64::from(h.path_contours), PATH_CONTOURS_MAX), ("path_points", u64::from(h.path_points), PATH_POINTS_MAX), ("strings_len", u64::from(h.strings_len), STRINGS_MAX), ("inline_len", u64::from(h.inline_len), INLINE_MAX), ("held_len", u64::from(h.held_len), HELD_MAX), ]; for (name, v, cap) in caps { if v > cap { return refuse(Code::TooLarge, "2", name, 0); } } if u64::from(h.inline_len) + u64::from(h.held_len) > SETS_BYTES_MAX { return refuse(Code::TooLarge, "2", "inline_len + held_len", 0); } let exact = h.exact_len(); if exact > PAYLOAD_MAX { return refuse(Code::TooLarge, "2", "exact length", 0); } // Step 3. if exact != b.len() as u64 { return refuse(PM, "3", "exact length", 0); } Ok(read_tables(b, h)) } /// The tables of a payload whose length is exact for its header. Every count is /// backed by bytes, so the reservations below are bounded by the input. pub fn read_tables(b: &[u8], h: Header) -> Doc { let mut o = WVG1_HEADER; let mut take = |n: usize| { let s = o; o += n; s }; let mut d = Doc::default(); for _ in 0..h.n_sizes { let p = take(8); d.sizes.push(Size { w: rd16(b, p), h: rd16(b, p + 2), cost: rd32(b, p + 4) }); } for _ in 0..h.n_rates { let p = take(8); d.rates.push(Rate { num: rd32(b, p), den: rd32(b, p + 4) }); } for _ in 0..h.n_sets { let p = take(40); let mut sha = [0u8; 32]; sha.copy_from_slice(&b[p + 8..p + 40]); d.sets.push(SetRec { carriage: b[p], zero: [b[p + 1], b[p + 2], b[p + 3]], length: rd32(b, p + 4), sha256: sha }); } for _ in 0..h.n_tables { let p = take(68); let mut e = [0u32; 17]; for (i, v) in e.iter_mut().enumerate() { *v = rd32(b, p + 4 * i); } d.tables.push(Table { e }); } d.paints = Vec::with_capacity(h.n_paints as usize); for _ in 0..h.n_paints { let p = take(4); d.paints.push(Paint { rgba: [b[p], b[p + 1], b[p + 2], b[p + 3]] }); } d.tracks = Vec::with_capacity(h.n_tracks as usize); for _ in 0..h.n_tracks { let p = take(8); d.tracks.push(Track { kind: b[p], zero1: b[p + 1], zero2: rd16(b, p + 2), n_keys: rd32(b, p + 4) }); } d.keys = Vec::with_capacity(h.n_keys as usize); for _ in 0..h.n_keys { let p = take(12); d.keys.push(Key { t: rd32(b, p), value: rd32(b, p + 4) as i32, interp: b[p + 8], zero: [b[p + 9], b[p + 10], b[p + 11]] }); } d.paths = Vec::with_capacity(h.n_paths as usize); for _ in 0..h.n_paths { let p = take(16); d.paths.push(Path { n_contours: rd16(b, p), n_points: rd16(b, p + 2), zero: rd32(b, p + 4), bbox: [rd16(b, p + 8) as i16, rd16(b, p + 10) as i16, rd16(b, p + 12) as i16, rd16(b, p + 14) as i16], }); } d.path_ends = Vec::with_capacity(h.path_contours as usize); for _ in 0..h.path_contours { let p = take(2); d.path_ends.push(rd16(b, p)); } d.path_points = Vec::with_capacity(h.path_points as usize); for _ in 0..h.path_points { let p = take(5); d.path_points.push(Point { x: rd16(b, p) as i16, y: rd16(b, p + 2) as i16, tag: b[p + 4] }); } d.texts = Vec::with_capacity(h.n_texts as usize); for _ in 0..h.n_texts { let p = take(20); d.texts.push(Text { purpose: b[p], dir: b[p + 1], lang_len: b[p + 2], mode: b[p + 3], group: rd16(b, p + 4), ruby: rd16(b, p + 6), ruby_start: rd32(b, p + 8), ruby_len: rd32(b, p + 12), str_len: rd32(b, p + 16), }); } d.items = Vec::with_capacity(h.n_items as usize); for _ in 0..h.n_items { let p = take(48); let mut slots = [0u16; 7]; for (i, s) in slots.iter_mut().enumerate() { *s = rd16(b, p + 28 + 2 * i); } let mut z = [0u8; 6]; z.copy_from_slice(&b[p + 42..p + 48]); d.items.push(Item { kind: b[p], rule: b[p + 1], zero2: rd16(b, p + 2), reference: rd16(b, p + 4), paint: rd16(b, p + 6), text: rd16(b, p + 8), zero10: rd16(b, p + 10), n_glyphs: rd16(b, p + 12), n_clusters: rd16(b, p + 14), t_on: rd32(b, p + 16), t_off: rd32(b, p + 20), unit_scale: rd32(b, p + 24), slots, zero42: z, }); } d.glyphs = Vec::with_capacity(h.n_glyphs as usize); for _ in 0..h.n_glyphs { let p = take(12); d.glyphs.push(GlyphRec { gid: rd16(b, p), zero: rd16(b, p + 2), x: rd32(b, p + 4) as i32, y: rd32(b, p + 8) as i32 }); } d.clusters = Vec::with_capacity(h.n_clusters as usize); for _ in 0..h.n_clusters { let p = take(12); d.clusters.push(Cluster { glyph_count: rd16(b, p), zero: rd16(b, p + 2), byte_first: rd32(b, p + 4), byte_len: rd32(b, p + 8) }); } let p = take(h.strings_len as usize); d.strings = b[p..p + h.strings_len as usize].to_vec(); let p = take(h.inline_len as usize); d.inline = b[p..p + h.inline_len as usize].to_vec(); d.header = h; d } /// Steps 1–5 of §2.7. pub fn parse(b: &[u8]) -> Result { let mut d = split(b)?; record_pass(&d)?; sums(&d)?; d.index(); cross(&d)?; Ok(d) } /// Step 4: the record pass, in file order. pub fn record_pass(d: &Doc) -> Result<(), Refusal> { let h = &d.header; let side = |v: u16| (SIDE_MIN..=SIDE_MAX).contains(&u32::from(v)); // The header's remaining fields, in field order. if !(1..=TB_MAX).contains(&h.tb) { return refuse(FR, "4", "tb", 0); } if u64::from(h.t0) >= TICK_LIMIT { return refuse(FR, "4", "t0", 0); } if h.dur < 1 || u64::from(h.dur) > TICK_LIMIT - u64::from(h.t0) { return refuse(FR, "4", "dur", 0); } if !side(h.design_w) { return refuse(FR, "4", "design_w", 0); } if !side(h.design_h) { return refuse(FR, "4", "design_h", 0); } if h.n_sizes == 0 { return refuse(FR, "4", "n_sizes", 0); } if h.n_rates == 0 { return refuse(FR, "4", "n_rates", 0); } if h.n_items == 0 { return refuse(FR, "4", "n_items", 0); } let (dw, dh) = (u64::from(h.design_w), u64::from(h.design_h)); for (i, s) in d.sizes.iter().enumerate() { let (w, hh) = (u64::from(s.w), u64::from(s.h)); if !side(s.w) || 8 * w < dw || w > 4 * dw { return refuse(FR, "4", "SIZE.w", i as u64); } if !side(s.h) || w * dh != hh * dw { return refuse(FR, "4", "SIZE.h", i as u64); } } for (i, r) in d.rates.iter().enumerate() { if !(1..=RATE_TERM_MAX).contains(&r.num) { return refuse(FR, "4", "RATE.num", i as u64); } let (num, den) = (u64::from(r.num), u64::from(r.den)); if !(1..=RATE_TERM_MAX).contains(&r.den) || gcd(num, den) != 1 || num > RATE_MAX_FPS * den || (den * u64::from(h.tb)) % num != 0 { return refuse(FR, "4", "RATE.den", i as u64); } } for (i, s) in d.sets.iter().enumerate() { if s.carriage > 1 { return refuse(FR, "4", "SET.carriage", i as u64); } if s.zero != [0; 3] { return refuse(PM, "4", "SET.zero", i as u64); } if !(SET_LEN_MIN..=SET_LEN_MAX).contains(&s.length) { return refuse(FR, "4", "SET.length", i as u64); } } for (i, t) in d.tables.iter().enumerate() { for (j, &e) in t.e.iter().enumerate() { let ok = match j { 0 => e == 0, 16 => e == Q16_ONE, _ => e <= Q16_ONE, }; if !ok { return refuse(FR, "4", "TABLE.E", i as u64); } } } for (i, p) in d.paints.iter().enumerate() { let [r, g, b, a] = p.rgba; if r > a || g > a || b > a { return refuse(FR, "4", "PAINT: r, g, b ≤ a", i as u64); } } for (i, t) in d.tracks.iter().enumerate() { if !(1..=3).contains(&t.kind) { return refuse(FR, "4", "TRACK.kind", i as u64); } if t.zero1 != 0 || t.zero2 != 0 { return refuse(PM, "4", "TRACK.zero", i as u64); } if !(1..=TRACK_KEYS_MAX).contains(&t.n_keys) { return refuse(FR, "4", "TRACK.n_keys", i as u64); } } for (i, k) in d.keys.iter().enumerate() { if u64::from(k.t) >= TICK_LIMIT { return refuse(FR, "4", "KEY.t", i as u64); } if u32::from(k.interp) >= 2 + u32::from(h.n_tables) { return refuse(FR, "4", "KEY.interp", i as u64); } if k.zero != [0; 3] { return refuse(PM, "4", "KEY.zero", i as u64); } } for (i, p) in d.paths.iter().enumerate() { if p.n_contours < 1 { return refuse(FR, "4", "PATH.n_contours", i as u64); } if u64::from(p.n_points) > POINTS_MAX { return refuse(Code::PointsLimit, "4", "PATH.n_points", i as u64); } if p.n_points < 1 || p.n_contours > p.n_points { return refuse(FR, "4", "PATH.n_points", i as u64); } if p.zero != 0 { return refuse(PM, "4", "PATH.zero", i as u64); } } if let Some(i) = d.path_points.iter().position(|p| p.tag > 2) { return refuse(FR, "4", "PATH_POINT.tag", i as u64); } for (i, t) in d.texts.iter().enumerate() { let i = i as u64; if t.purpose == 0 { return refuse(FR, "4", "TEXT.purpose", i); } if t.dir > 1 { return refuse(FR, "4", "TEXT.dir", i); } if !(1..=LANG_MAX).contains(&t.lang_len) { return refuse(FR, "4", "TEXT.lang_len", i); } if t.mode > 2 { return refuse(FR, "4", "TEXT.mode", i); } if t.ruby > h.n_texts { return refuse(FR, "4", "TEXT.ruby", i); } if t.ruby == 0 && t.ruby_start != 0 { return refuse(FR, "4", "TEXT.ruby_start", i); } if (t.ruby == 0) != (t.ruby_len == 0) { return refuse(FR, "4", "TEXT.ruby_len", i); } if !(1..=STR_MAX).contains(&t.str_len) { return refuse(FR, "4", "TEXT.str_len", i); } } for (i, it) in d.items.iter().enumerate() { item_fields(h, it, i as u64)?; } for (i, g) in d.glyphs.iter().enumerate() { if g.zero != 0 { return refuse(PM, "4", "GLYPH.zero", i as u64); } if !(-GLYPH_POS_LIMIT..GLYPH_POS_LIMIT).contains(&g.x) { return refuse(FR, "4", "GLYPH.x", i as u64); } if !(-GLYPH_POS_LIMIT..GLYPH_POS_LIMIT).contains(&g.y) { return refuse(FR, "4", "GLYPH.y", i as u64); } } if let Some(i) = d.clusters.iter().position(|c| c.zero != 0) { return refuse(PM, "4", "CLUSTER.zero", i as u64); } Ok(()) } /// An `ITEM`'s fields in offset order (§2.4): each field's own range, then any rule /// that relates it to an earlier field of the record. fn item_fields(h: &Header, it: &Item, i: u64) -> Result<(), Refusal> { let k = it.kind; if k > KIND_RUN { return refuse(FR, "4", "ITEM.kind", i); } let ok = match k { KIND_BEGIN | KIND_PATH => it.rule <= 1, _ => it.rule == 0, }; if !ok { return refuse(FR, "4", "ITEM.rule", i); } if it.zero2 != 0 { return refuse(PM, "4", "ITEM.zero@2", i); } let r = it.reference; let ok = match k { KIND_BEGIN => r == NONE || r < h.n_paths, KIND_END => r == 0, KIND_PATH => r < h.n_paths, _ => r < u16::from(h.n_sets), }; if !ok || (k == KIND_BEGIN && r == NONE && it.rule != 0) { return refuse(FR, "4", "ITEM.ref", i); } let ok = match k { KIND_BEGIN => it.paint == NONE, KIND_END => it.paint == 0, _ => it.paint < h.n_paints, }; if !ok { return refuse(FR, "4", "ITEM.paint", i); } let ok = match k { KIND_BEGIN => it.text == NONE, KIND_END => it.text == 0, KIND_PATH => it.text == NONE || it.text < h.n_texts, _ => it.text < h.n_texts, }; if !ok { return refuse(FR, "4", "ITEM.text", i); } if it.zero10 != 0 { return refuse(PM, "4", "ITEM.zero@10", i); } let run = k == KIND_RUN; if (run && !(1..=RUN_GLYPHS_MAX).contains(&it.n_glyphs)) || (!run && it.n_glyphs != 0) { return refuse(FR, "4", "ITEM.n_glyphs", i); } if (run && !(1..=RUN_CLUSTERS_MAX).contains(&it.n_clusters)) || (!run && it.n_clusters != 0) { return refuse(FR, "4", "ITEM.n_clusters", i); } let end = k == KIND_END; if (end && it.t_on != 0) || (!end && u64::from(it.t_on) >= TICK_LIMIT) { return refuse(FR, "4", "ITEM.t_on", i); } if (end && it.t_off != 0) || (!end && (u64::from(it.t_off) > TICK_LIMIT || it.t_on >= it.t_off)) { return refuse(FR, "4", "ITEM.t_off", i); } let us = it.unit_scale; let ok = match k { KIND_BEGIN => us <= UNIT_SCALE_MAX && (r == NONE) == (us == 0), KIND_END => us == 0, _ => (1..=UNIT_SCALE_MAX).contains(&us), }; if !ok { return refuse(FR, "4", "ITEM.unit_scale", i); } for (s, &v) in it.slots.iter().enumerate() { let ok = match k { KIND_END => v == 0, KIND_BEGIN if s < TX => v == NONE, _ => v == NONE || v < h.n_tracks, }; if !ok { return refuse(FR, "4", "ITEM.slot", i); } } if it.zero42 != [0; 6] { return refuse(PM, "4", "ITEM.zero@42", i); } Ok(()) } /// Step 5(a): the sum rules. pub fn sums(d: &Doc) -> Result<(), Refusal> { let h = &d.header; let by = |c: u8| d.sets.iter().filter(|s| s.carriage == c).map(|s| u64::from(s.length)).sum::(); if by(1) != u64::from(h.inline_len) { return refuse(PM, "5a", "SET lengths, inline", 0); } if by(0) != u64::from(h.held_len) { return refuse(PM, "5a", "SET lengths, held", 0); } if d.tracks.iter().map(|t| u64::from(t.n_keys)).sum::() != u64::from(h.n_keys) { return refuse(PM, "5a", "TRACK n_keys", 0); } if d.paths.iter().map(|p| u64::from(p.n_contours)).sum::() != u64::from(h.path_contours) { return refuse(PM, "5a", "PATH n_contours", 0); } if d.paths.iter().map(|p| u64::from(p.n_points)).sum::() != u64::from(h.path_points) { return refuse(PM, "5a", "PATH n_points", 0); } if d.texts.iter().map(|t| u64::from(t.lang_len) + u64::from(t.str_len)).sum::() != u64::from(h.strings_len) { return refuse(PM, "5a", "TEXT lang_len + str_len", 0); } let runs = || d.items.iter().filter(|it| it.kind == KIND_RUN); if runs().map(|it| u64::from(it.n_glyphs)).sum::() != u64::from(h.n_glyphs) { return refuse(PM, "5a", "run n_glyphs", 0); } if runs().map(|it| u64::from(it.n_clusters)).sum::() != u64::from(h.n_clusters) { return refuse(PM, "5a", "run n_clusters", 0); } Ok(()) } impl Doc { /// Fill [`Ix`] from the owners' counts. pub fn index(&mut self) { let mut ix = Ix::default(); let mut a = 0u32; for t in &self.tracks { ix.track_key0.push(a); a += t.n_keys; } let (mut e, mut p) = (0u32, 0u32); for q in &self.paths { ix.path_end0.push(e); ix.path_pt0.push(p); e += u32::from(q.n_contours); p += u32::from(q.n_points); } let mut s = 0u32; for t in &self.texts { ix.text_off.push(s); s += u32::from(t.lang_len) + t.str_len; } let (mut g, mut c) = (0u32, 0u32); for it in &self.items { ix.item_glyph0.push(g); ix.item_cluster0.push(c); if it.kind == KIND_RUN { g += u32::from(it.n_glyphs); c += u32::from(it.n_clusters); } } self.ix = ix; } pub fn track_keys(&self, t: usize) -> &[Key] { &self.keys[self.ix.track_key0[t] as usize..][..self.tracks[t].n_keys as usize] } pub fn path_ends_of(&self, p: usize) -> &[u16] { &self.path_ends[self.ix.path_end0[p] as usize..][..self.paths[p].n_contours as usize] } pub fn path_points_of(&self, p: usize) -> &[Point] { &self.path_points[self.ix.path_pt0[p] as usize..][..self.paths[p].n_points as usize] } pub fn lang(&self, t: usize) -> &[u8] { &self.strings[self.ix.text_off[t] as usize..][..self.texts[t].lang_len as usize] } pub fn string(&self, t: usize) -> &[u8] { let o = self.ix.text_off[t] as usize + self.texts[t].lang_len as usize; &self.strings[o..o + self.texts[t].str_len as usize] } pub fn run_glyphs(&self, i: usize) -> &[GlyphRec] { &self.glyphs[self.ix.item_glyph0[i] as usize..][..self.items[i].n_glyphs as usize] } pub fn run_clusters(&self, i: usize) -> &[Cluster] { &self.clusters[self.ix.item_cluster0[i] as usize..][..self.items[i].n_clusters as usize] } /// Set the header's counts from the tables' lengths, so the length is exact. pub fn sync_counts(&mut self) { let h = &mut self.header; h.n_sizes = self.sizes.len() as u8; h.n_rates = self.rates.len() as u8; h.n_sets = self.sets.len() as u8; h.n_tables = self.tables.len() as u8; h.n_paints = self.paints.len() as u16; h.n_texts = self.texts.len() as u16; h.n_tracks = self.tracks.len() as u16; h.n_paths = self.paths.len() as u16; h.n_items = self.items.len() as u32; h.n_keys = self.keys.len() as u32; h.n_glyphs = self.glyphs.len() as u32; h.n_clusters = self.clusters.len() as u32; h.path_contours = self.path_ends.len() as u32; h.path_points = self.path_points.len() as u32; h.strings_len = self.strings.len() as u32; h.inline_len = self.inline.len() as u32; } /// The canonical bytes: the header as held, then every table in file order. pub fn write(&self) -> Vec { let mut o = Vec::with_capacity(self.header.exact_len().min(PAYLOAD_MAX) as usize); self.header.write(&mut o); for s in &self.sizes { o.extend_from_slice(&s.w.to_le_bytes()); o.extend_from_slice(&s.h.to_le_bytes()); o.extend_from_slice(&s.cost.to_le_bytes()); } for r in &self.rates { o.extend_from_slice(&r.num.to_le_bytes()); o.extend_from_slice(&r.den.to_le_bytes()); } for s in &self.sets { o.push(s.carriage); o.extend_from_slice(&s.zero); o.extend_from_slice(&s.length.to_le_bytes()); o.extend_from_slice(&s.sha256); } for t in &self.tables { for e in t.e { o.extend_from_slice(&e.to_le_bytes()); } } for p in &self.paints { o.extend_from_slice(&p.rgba); } for t in &self.tracks { o.push(t.kind); o.push(t.zero1); o.extend_from_slice(&t.zero2.to_le_bytes()); o.extend_from_slice(&t.n_keys.to_le_bytes()); } for k in &self.keys { o.extend_from_slice(&k.t.to_le_bytes()); o.extend_from_slice(&k.value.to_le_bytes()); o.push(k.interp); o.extend_from_slice(&k.zero); } for p in &self.paths { o.extend_from_slice(&p.n_contours.to_le_bytes()); o.extend_from_slice(&p.n_points.to_le_bytes()); o.extend_from_slice(&p.zero.to_le_bytes()); for v in p.bbox { o.extend_from_slice(&v.to_le_bytes()); } } for e in &self.path_ends { o.extend_from_slice(&e.to_le_bytes()); } for p in &self.path_points { o.extend_from_slice(&p.x.to_le_bytes()); o.extend_from_slice(&p.y.to_le_bytes()); o.push(p.tag); } for t in &self.texts { o.extend_from_slice(&[t.purpose, t.dir, t.lang_len, t.mode]); o.extend_from_slice(&t.group.to_le_bytes()); o.extend_from_slice(&t.ruby.to_le_bytes()); for v in [t.ruby_start, t.ruby_len, t.str_len] { o.extend_from_slice(&v.to_le_bytes()); } } for it in &self.items { o.push(it.kind); o.push(it.rule); for v in [it.zero2, it.reference, it.paint, it.text, it.zero10, it.n_glyphs, it.n_clusters] { o.extend_from_slice(&v.to_le_bytes()); } for v in [it.t_on, it.t_off, it.unit_scale] { o.extend_from_slice(&v.to_le_bytes()); } for v in it.slots { o.extend_from_slice(&v.to_le_bytes()); } o.extend_from_slice(&it.zero42); } for g in &self.glyphs { o.extend_from_slice(&g.gid.to_le_bytes()); o.extend_from_slice(&g.zero.to_le_bytes()); o.extend_from_slice(&g.x.to_le_bytes()); o.extend_from_slice(&g.y.to_le_bytes()); } for c in &self.clusters { o.extend_from_slice(&c.glyph_count.to_le_bytes()); o.extend_from_slice(&c.zero.to_le_bytes()); o.extend_from_slice(&c.byte_first.to_le_bytes()); o.extend_from_slice(&c.byte_len.to_le_bytes()); } o.extend_from_slice(&self.strings); o.extend_from_slice(&self.inline); o } /// The `TEXTS` table's bytes as carried. pub fn texts_bytes(&self) -> Vec { let mut o = Vec::with_capacity(20 * self.texts.len()); for t in &self.texts { o.extend_from_slice(&[t.purpose, t.dir, t.lang_len, t.mode]); o.extend_from_slice(&t.group.to_le_bytes()); o.extend_from_slice(&t.ruby.to_le_bytes()); for v in [t.ruby_start, t.ruby_len, t.str_len] { o.extend_from_slice(&v.to_le_bytes()); } } o } } /// Step 5 after the sum rules: (b) ordering, (c) typing, (d) groups, (e) paths, (f) /// strings, (g) the canonical rules, (h) decorative path alternatives. pub fn cross(d: &Doc) -> Result<(), Refusal> { // (b) for i in 1..d.sizes.len() { if d.sizes[i].w <= d.sizes[i - 1].w { return refuse(PM, "5b", "SIZEs ascend by w", i as u64); } } for i in 1..d.rates.len() { let (a, b) = (d.rates[i - 1], d.rates[i]); if u64::from(a.num) * u64::from(b.den) >= u64::from(b.num) * u64::from(a.den) { return refuse(PM, "5b", "RATEs ascend by num/den", i as u64); } } for t in 0..d.tracks.len() { let keys = d.track_keys(t); if keys.windows(2).any(|w| w[1].t <= w[0].t) { return refuse(PM, "5b", "keys ascend in t", t as u64); } if keys.last().is_some_and(|k| k.interp != 0) { return refuse(PM, "5b", "the last key's interp is 0", t as u64); } } // (c) typing, field_range: KEYS in order, then ITEMS in order. for t in 0..d.tracks.len() { let (lo, hi) = match d.tracks[t].kind { 1 => (0, OPACITY_MAX), 2 => (-TRANSLATION_MAX, TRANSLATION_MAX), _ => (-MATRIX_MAX, MATRIX_MAX), }; if let Some(j) = d.track_keys(t).iter().position(|k| !(lo..=hi).contains(&k.value)) { return refuse(FR, "5c", "KEY.value for its track's kind", (d.ix.track_key0[t] as usize + j) as u64); } } for (i, it) in d.items.iter().enumerate() { if it.kind == KIND_END { continue; } for (s, &v) in it.slots.iter().enumerate() { if v != NONE && d.tracks[v as usize].kind != SLOT_KIND[s] { return refuse(FR, "5c", "a slot names a track of its kind", i as u64); } } } // (d) let mut depth = 0u32; for (i, it) in d.items.iter().enumerate() { match it.kind { KIND_BEGIN => { depth += 1; if depth > GROUP_DEPTH_MAX { return refuse(PM, "5d", "group depth", i as u64); } } KIND_END => { if depth == 0 { return refuse(PM, "5d", "a group end with no group open", i as u64); } depth -= 1; } _ => {} } } if depth != 0 { return refuse(PM, "5d", "a group open after the last item", 0); } // (e) for p in 0..d.paths.len() { let (ends, pts) = (d.path_ends_of(p), d.path_points_of(p)); if !wos1::ends_ok(ends, d.paths[p].n_points) { return refuse(PM, "5e", "PATH contour ends", p as u64); } if !wos1::outline_grammar_ok(pts, ends) { return refuse(PM, "5e", "PATH contour grammar", p as u64); } if wos1::exact_box(pts) != d.paths[p].bbox { return refuse(PM, "5e", "PATH box", p as u64); } } // (f) for t in 0..d.texts.len() { if !lang_tag_ok(d.lang(t)) { return refuse(PM, "5f", "language tag", t as u64); } if !string_ok(d.string(t)) { return refuse(PM, "5f", "string", t as u64); } } // (g) first_reference(d)?; no_duplicates(d)?; text_blocks(d)?; clusters(d)?; // (h) for (i, it) in d.items.iter().enumerate() { if it.kind == KIND_PATH && it.text != NONE && d.texts[it.text as usize].purpose == DECORATIVE { return refuse(PM, "5h", "a path's alternative is decorative", i as u64); } } Ok(()) } /// §2.5 rule 6: ASCII letters, digits and hyphens, with no leading, trailing or /// doubled hyphen. pub fn lang_tag_ok(t: &[u8]) -> bool { !t.is_empty() && t.iter().all(|c| c.is_ascii_alphanumeric() || *c == b'-') && t[0] != b'-' && t[t.len() - 1] != b'-' && !t.windows(2).any(|w| w == b"--") } /// §2.5 rule 6: valid UTF-8, no C0 control but U+000A, no U+007F, no C1 control. pub fn string_ok(s: &[u8]) -> bool { match core::str::from_utf8(s) { Ok(s) => s.chars().all(|c| { let v = c as u32; !(v < 0x20 && v != 0x0A || v == 0x7F || (0x80..=0x9F).contains(&v)) }), Err(_) => false, } } /// A code-point boundary of a valid UTF-8 string: its end, or a byte that does not /// continue a sequence. fn boundary(s: &[u8], i: u64) -> bool { i == s.len() as u64 || (i < s.len() as u64 && s[i as usize] & 0xC0 != 0x80) } /// First references in order 0, 1, 2, …, and every entry referenced. struct FirstRef { seen: Vec, next: usize, } impl FirstRef { fn new(n: usize) -> FirstRef { FirstRef { seen: vec![false; n], next: 0 } } fn see(&mut self, v: usize) -> bool { if !self.seen[v] { if v != self.next { return false; } self.seen[v] = true; self.next += 1; } true } fn complete(&self) -> bool { self.next == self.seen.len() } } /// §2.5 rule 2. fn first_reference(d: &Doc) -> Result<(), Refusal> { let (mut paths, mut sets) = (FirstRef::new(d.paths.len()), FirstRef::new(d.sets.len())); let (mut paints, mut tracks) = (FirstRef::new(d.paints.len()), FirstRef::new(d.tracks.len())); for (i, it) in d.items.iter().enumerate() { if it.kind == KIND_END { continue; } let ok = match it.kind { KIND_RUN => sets.see(it.reference as usize), _ if it.reference == NONE => true, _ => paths.see(it.reference as usize), } && (it.paint == NONE || paints.see(it.paint as usize)) && it.slots.iter().all(|&s| s == NONE || tracks.see(s as usize)); if !ok { return refuse(PM, "5g", "rule 2: first reference", i as u64); } } if !(paths.complete() && sets.complete() && paints.complete() && tracks.complete()) { return refuse(PM, "5g", "rule 2: every entry referenced", 0); } let mut tables = FirstRef::new(d.tables.len()); for t in 0..d.tracks.len() { for k in d.track_keys(t) { if k.interp >= 2 && !tables.see(k.interp as usize - 2) { return refuse(PM, "5g", "rule 2: table first reference", t as u64); } } } if !tables.complete() { return refuse(PM, "5g", "rule 2: every table referenced", 0); } Ok(()) } /// Whether two entries of a pool of `n` compare equal, found by sorting the pool's /// indices with `cmp`: at most about `n·log2 n` comparisons, never every pair /// (§2.5 rule 3). pub fn has_duplicate(n: usize, mut cmp: impl FnMut(usize, usize) -> Ordering) -> bool { if n < 2 { return false; } let mut idx: Vec = (0..n as u32).collect(); idx.sort_unstable_by(|&a, &b| cmp(a as usize, b as usize)); idx.windows(2).any(|w| cmp(w[0] as usize, w[1] as usize) == Ordering::Equal) } /// Tracks compare by kind, then by their key sequences. pub fn cmp_tracks(d: &Doc, a: usize, b: usize) -> Ordering { let key = |k: &Key| (k.t, k.value, k.interp); d.tracks[a].kind.cmp(&d.tracks[b].kind).then_with(|| d.track_keys(a).iter().map(key).cmp(d.track_keys(b).iter().map(key))) } /// Paths compare by record, then contour ends, then points. pub fn cmp_paths(d: &Doc, a: usize, b: usize) -> Ordering { let rec = |p: &Path| (p.n_contours, p.n_points, p.zero, p.bbox); rec(&d.paths[a]) .cmp(&rec(&d.paths[b])) .then_with(|| d.path_ends_of(a).cmp(d.path_ends_of(b))) .then_with(|| d.path_points_of(a).cmp(d.path_points_of(b))) } /// §2.5 rule 3. fn no_duplicates(d: &Doc) -> Result<(), Refusal> { if has_duplicate(d.sets.len(), |a, b| d.sets[a].sha256.cmp(&d.sets[b].sha256)) { return refuse(PM, "5g", "rule 3: duplicate set", 0); } if has_duplicate(d.paints.len(), |a, b| d.paints[a].cmp(&d.paints[b])) { return refuse(PM, "5g", "rule 3: duplicate paint", 0); } if has_duplicate(d.tracks.len(), |a, b| cmp_tracks(d, a, b)) { return refuse(PM, "5g", "rule 3: duplicate track", 0); } if has_duplicate(d.paths.len(), |a, b| cmp_paths(d, a, b)) { return refuse(PM, "5g", "rule 3: duplicate path", 0); } if has_duplicate(d.tables.len(), |a, b| d.tables[a].cmp(&d.tables[b])) { return refuse(PM, "5g", "rule 3: duplicate table", 0); } Ok(()) } /// §2.5 rule 4. fn text_blocks(d: &Doc) -> Result<(), Refusal> { let mut named = vec![false; d.texts.len()]; for it in &d.items { if (it.kind == KIND_RUN || it.kind == KIND_PATH) && it.text != NONE { named[it.text as usize] = true; } } if let Some(t) = named.iter().position(|n| !n) { return refuse(PM, "5g", "rule 4: a block nothing draws", t as u64); } let mut max_group = 0u16; for (i, t) in d.texts.iter().enumerate() { let g = t.group; if g != 0 { if g > max_group.saturating_add(1) { return refuse(PM, "5g", "rule 4: group numbered out of first use", i as u64); } if g <= max_group && d.texts[i - 1].group != g { return refuse(PM, "5g", "rule 4: a group's blocks are not contiguous", i as u64); } max_group = max_group.max(g); } if t.ruby != 0 { let base = t.ruby as usize - 1; if base >= i { return refuse(PM, "5g", "rule 4: ruby names a later block", i as u64); } let s = d.string(base); let (a, n) = (u64::from(t.ruby_start), u64::from(t.ruby_len)); if a + n > s.len() as u64 || !boundary(s, a) || !boundary(s, a + n) { return refuse(PM, "5g", "rule 4: ruby range", i as u64); } } } Ok(()) } /// §2.5 rule 5. fn clusters(d: &Doc) -> Result<(), Refusal> { for (i, it) in d.items.iter().enumerate() { if it.kind != KIND_RUN { continue; } let cs = d.run_clusters(i); if cs.iter().map(|c| u64::from(c.glyph_count)).sum::() != u64::from(it.n_glyphs) { return refuse(PM, "5g", "rule 5: glyph counts sum to n_glyphs", i as u64); } let s = d.string(it.text as usize); for c in cs { let (a, n) = (u64::from(c.byte_first), u64::from(c.byte_len)); if a + n > s.len() as u64 || !boundary(s, a) || !boundary(s, a + n) { return refuse(PM, "5g", "rule 5: cluster range", i as u64); } if c.glyph_count == 0 && c.byte_len == 0 { return refuse(PM, "5g", "rule 5: an empty cluster", i as u64); } } } Ok(()) } /// How a sink obtains held sets (§2.7 step 6): by digest, as a stream it reads from. pub trait Resolver { /// Begin reading the held set named by `sha256`; false if the sink cannot obtain it. fn open(&mut self, sha256: &[u8; 32]) -> bool; /// Read the next bytes of the set last opened into `buf`; 0 at its end. fn read(&mut self, buf: &mut [u8]) -> usize; } /// A resolver that holds no set. pub struct NoSets; impl Resolver for NoSets { fn open(&mut self, _: &[u8; 32]) -> bool { false } fn read(&mut self, _: &mut [u8]) -> usize { 0 } } /// A resolver over sets held in memory, found by their SHA-256. #[derive(Default)] pub struct SetStore { sets: Vec<([u8; 32], Vec)>, open: Option<(usize, usize)>, } impl SetStore { pub fn new() -> SetStore { SetStore::default() } /// Hold `bytes` under their SHA-256. pub fn add(&mut self, bytes: Vec) { let h = crate::sha256::sha256(&bytes); if !self.sets.iter().any(|(k, _)| *k == h) { self.sets.push((h, bytes)); } } /// Hold `bytes` under a digest of the caller's choosing (for refusal vectors). pub fn add_as(&mut self, sha256: [u8; 32], bytes: Vec) { self.sets.push((sha256, bytes)); } pub fn len(&self) -> usize { self.sets.len() } pub fn is_empty(&self) -> bool { self.sets.is_empty() } } impl Resolver for SetStore { fn open(&mut self, sha256: &[u8; 32]) -> bool { self.open = self.sets.iter().position(|(k, _)| k == sha256).map(|i| (i, 0)); self.open.is_some() } fn read(&mut self, buf: &mut [u8]) -> usize { let Some((i, at)) = self.open else { return 0 }; let src = &self.sets[i].1[at..]; let n = src.len().min(buf.len()); buf[..n].copy_from_slice(&src[..n]); self.open = Some((i, at + n)); n } } /// Read at most `limit` bytes of the set the resolver has open. pub fn read_held(r: &mut dyn Resolver, limit: usize) -> Vec { const CHUNK: usize = 1 << 16; let mut data: Vec = Vec::new(); while data.len() < limit { let start = data.len(); let n = (limit - start).min(CHUNK); data.resize(start + n, 0); let got = r.read(&mut data[start..start + n]); data.truncate(start + got.min(n)); if got == 0 { break; } } data } /// A payload that passed every check of §2.7, with its sets and each listed size's /// cost. #[derive(Clone, Debug)] pub struct Loaded { pub doc: Doc, pub sets: Vec, pub costs: Vec, } /// Steps 6 and 7: the sets, then the glyphs. pub fn load_sets(d: &Doc, r: &mut dyn Resolver) -> Result, Refusal> { let mut out = Vec::with_capacity(d.sets.len()); let mut at = 0usize; for (i, s) in d.sets.iter().enumerate() { let i = i as u64; let len = s.length as usize; let bytes: Vec = if s.carriage == 1 { let b = d.inline[at..at + len].to_vec(); at += len; b } else { if !r.open(&s.sha256) { return refuse(Code::OutlineNotHeld, "6", "held set not obtained", i); } read_held(r, len + 1) }; if bytes.len() != len { return refuse(Code::OutlineMismatch, "6", "length", i); } if crate::sha256::sha256(&bytes) != s.sha256 { return refuse(Code::OutlineMismatch, "6", "sha256", i); } match wos1::parse(&bytes) { Ok(w) => out.push(w), Err(e) => return Err(Refusal { step: "6", index: i, ..e }), } } for (i, it) in d.items.iter().enumerate() { if it.kind != KIND_RUN { continue; } let set = &out[it.reference as usize]; for (k, g) in d.run_glyphs(i).iter().enumerate() { if set.glyph(g.gid).is_none() { return refuse(Code::GlyphMissing, "7", "gid not in the run's set", (d.ix.item_glyph0[i] as usize + k) as u64); } } } Ok(out) } /// Steps 1–9 of §2.7. pub fn load(b: &[u8], r: &mut dyn Resolver) -> Result { let doc = parse(b)?; let sets = load_sets(&doc, r)?; crate::place::interval_check(&doc, &sets)?; let costs = crate::place::check_costs(&doc, &sets)?; Ok(Loaded { doc, sets, costs }) }