use cadmpeg_ir::math::{Point3, Vector3};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Cursor<'a> {
bytes: &'a [u8],
position: usize,
}
impl<'a> Cursor<'a> {
pub(crate) fn new_at(bytes: &'a [u8], position: usize) -> Self {
Self { bytes, position }
}
pub(crate) fn position(&self) -> usize {
self.position
}
pub(crate) fn object_ref(&mut self, extended: bool) -> Option<u32> {
let lead = *self.bytes.get(self.position)?;
let get = |offset: usize| self.bytes.get(self.position + offset).copied();
let (value, width) = match lead {
0x38 => (u32::from_le_bytes([get(1)?, get(2)?, get(3)?, 0]), 4),
0x30 if extended => (u32::from(u16::from_le_bytes([get(1)?, get(2)?])) << 8, 3),
0x28 if extended => (u32::from(get(1)?) | (u32::from(get(2)?) << 16), 3),
0x20 if extended => (u32::from(get(1)?) << 16, 2),
0x18 => (u32::from(u16::from_le_bytes([get(1)?, get(2)?])), 3),
0x10 => (u32::from(get(1)?) << 8, 2),
0x08 => (u32::from(get(1)?), 2),
0x80..=0xff => (u32::from(lead - 0x80), 1),
_ => return None,
};
self.position += width;
Some(value)
}
pub(crate) fn compact_uint(&mut self) -> Option<u32> {
let lead = *self.bytes.get(self.position)?;
if lead % 4 == 1 {
self.position += 1;
Some(u32::from((lead - 1) / 4))
} else if lead != 0 && lead % 4 == 0 {
let width = usize::from(lead / 4);
if width > 4 {
return None;
}
let mut value = 0u32;
for (shift, byte) in self
.bytes
.get(self.position + 1..self.position + 1 + width)?
.iter()
.enumerate()
{
value |= u32::from(*byte) << (8 * shift);
}
self.position += width + 1;
Some(value)
} else {
None
}
}
}
impl Cursor<'_> {
fn take(&mut self, count: usize) -> Option<&[u8]> {
let end = self.position.checked_add(count)?;
let bytes = self.bytes.get(self.position..end)?;
self.position = end;
Some(bytes)
}
pub(crate) fn skip(&mut self, count: usize) -> Option<()> {
self.take(count).map(|_| ())
}
fn f64_raw(&mut self) -> Option<f64> {
Some(f64::from_le_bytes(self.take(8)?.try_into().ok()?))
}
pub(crate) fn f64(&mut self) -> Option<f64> {
let value = self.f64_raw()?;
value.is_finite().then_some(value)
}
pub(crate) fn point3(&mut self) -> Option<Point3> {
Some(Point3::new(self.f64()?, self.f64()?, self.f64()?))
}
pub(crate) fn vector3(&mut self) -> Option<Vector3> {
Some(Vector3::new(self.f64()?, self.f64()?, self.f64()?))
}
pub(crate) fn unit3(&mut self) -> Option<Vector3> {
self.vector3()?.unit()
}
}
#[cfg(test)]
mod tests {
use super::Cursor;
#[test]
fn object_ref_extended_reads_all_dialect_leads() {
let mut position = 0;
let mut cursor = Cursor::new_at(&[0x28, 0x34, 0x02], position);
assert_eq!(cursor.object_ref(true), Some(0x02_0034));
position = cursor.position();
assert_eq!(position, 3);
}
#[test]
fn object_ref_restricted_rejects_extended_only_leads() {
assert_eq!(
Cursor::new_at(&[0x30, 0x07, 0x00], 0).object_ref(false),
None
);
assert_eq!(Cursor::new_at(&[0x8b], 0).object_ref(false), Some(11));
}
#[test]
fn compact_uint_matches_single_and_multi_byte_encodings() {
assert_eq!(Cursor::new_at(&[0x05], 0).compact_uint(), Some(1));
let mut cursor = Cursor::new_at(&[0x08, 0x2a, 0x00], 0);
assert_eq!(cursor.compact_uint(), Some(42));
assert_eq!(cursor.position(), 3);
}
}