use cadmpeg_ir::be;
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct Node {
pub kind: u8,
pub xmt: u32,
pub pos: usize,
shift: usize,
bytes: Vec<u8>,
}
impl Node {
pub fn xmt_at(&self, offset: usize) -> Option<u32> {
read_xmt(&self.bytes, offset + self.shift).map(|(xmt, _)| xmt)
}
pub fn xmt_sequence(&self, offset: usize, count: usize) -> Option<Vec<u32>> {
let mut at = offset + self.shift;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
let (value, extra) = read_xmt(&self.bytes, at)?;
values.push(value);
at += 2 + extra;
}
Some(values)
}
pub fn byte_at(&self, offset: usize) -> Option<u8> {
self.bytes.get(offset + self.shift).copied()
}
pub fn f64_at(&self, offset: usize) -> Option<f64> {
be::f64_at(&self.bytes, offset + self.shift)
}
}
#[derive(Debug, Default)]
pub struct Graph {
nodes: BTreeMap<(u8, u32), Node>,
by_pos: BTreeMap<usize, (u8, u32)>,
}
#[derive(Debug, Clone, Copy)]
pub struct TrimmedCurve {
pub xmt: u32,
pub basis: u32,
pub parameters: [f64; 2],
}
pub fn trimmed_curves(stream: &[u8]) -> Vec<TrimmedCurve> {
Graph::parse(stream)
.of_kind(133)
.filter_map(|node| {
let basis = node.xmt_at(19)?;
let p0 = node.bytes.get(69 + node.shift..77 + node.shift)?;
let p1 = node.bytes.get(77 + node.shift..85 + node.shift)?;
let p0 = f64::from_be_bytes(p0.try_into().ok()?);
let p1 = f64::from_be_bytes(p1.try_into().ok()?);
(basis > 1 && p0.is_finite() && p1.is_finite()).then_some(TrimmedCurve {
xmt: node.xmt,
basis,
parameters: [p0, p1],
})
})
.collect()
}
impl Graph {
pub fn parse(stream: &[u8]) -> Self {
let mut graph = Self::default();
for pos in 0..stream.len().saturating_sub(3) {
if stream[pos] != 0 {
continue;
}
let kind = stream[pos + 1];
let Some(len) = fixed_len(kind) else {
continue;
};
let Some((xmt, shift)) = read_xmt(stream, pos + 2) else {
continue;
};
if xmt <= 1 {
continue;
}
let Some(bytes) = stream.get(pos..pos + len + shift) else {
continue;
};
let node = Node {
kind,
xmt,
pos,
shift,
bytes: bytes.to_vec(),
};
graph.by_pos.insert(pos, (kind, xmt));
graph.nodes.entry((kind, xmt)).or_insert(node);
}
graph
}
pub fn get(&self, kind: u8, xmt: u32) -> Option<&Node> {
self.nodes.get(&(kind, xmt))
}
pub fn at_pos(&self, pos: usize) -> Option<&Node> {
let &(kind, xmt) = self.by_pos.get(&pos)?;
self.get(kind, xmt)
}
pub fn of_kind(&self, kind: u8) -> impl Iterator<Item = &Node> {
self.nodes.values().filter(move |node| node.kind == kind)
}
}
fn read_xmt(stream: &[u8], at: usize) -> Option<(u32, usize)> {
let first = i16::from_be_bytes([*stream.get(at)?, *stream.get(at + 1)?]);
if first >= 0 {
return Some((first as u32, 0));
}
let remainder = first.unsigned_abs();
let quotient = u16::from_be_bytes([*stream.get(at + 2)?, *stream.get(at + 3)?]);
let value = u32::from(quotient) * 32_767 + u32::from(remainder);
Some((value, 2))
}
fn fixed_len(kind: u8) -> Option<usize> {
Some(match kind {
12 | 13 => 24,
14 => 39,
15 => 16,
16 => 32,
17 => 23,
18 => 28,
19 => 16,
29 => 40,
30 => 67,
31 => 99,
32 => 107,
50 => 91,
51 => 99,
52 => 115,
53 => 99,
54 => 107,
124 | 134 => 23,
133 => 85,
_ => return None,
})
}