use crate::CursorError;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Limits {
pub max_frames: usize,
pub max_dim: u32,
}
impl Default for Limits {
fn default() -> Self {
Self {
max_frames: 256,
max_dim: 512,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RawImage {
pub nominal_size: u32,
pub width: u32,
pub height: u32,
pub xhot: u32,
pub yhot: u32,
pub delay_ms: u32,
pub pixels: Vec<u32>,
}
const MAGIC: &[u8; 4] = b"Xcur";
const CHUNK_IMAGE: u32 = 0xfffd_0002;
const IMAGE_HEADER_LEN: u32 = 0x24;
const IMAGE_VERSION: u32 = 1;
const TOC_ENTRY_LEN: usize = 12;
struct Reader<'a> {
bytes: &'a [u8],
at: usize,
}
impl<'a> Reader<'a> {
const fn new(bytes: &'a [u8]) -> Self {
Self { bytes, at: 0 }
}
fn seek(&mut self, to: usize) -> Result<(), CursorError> {
if to > self.bytes.len() {
return Err(CursorError::Malformed);
}
self.at = to;
Ok(())
}
fn u32_le(&mut self) -> Result<u32, CursorError> {
let end = self.at.checked_add(4).ok_or(CursorError::Malformed)?;
let slice = self.bytes.get(self.at..end).ok_or(CursorError::Malformed)?;
self.at = end;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn tag(&mut self, expected: [u8; 4]) -> Result<(), CursorError> {
let end = self.at.checked_add(4).ok_or(CursorError::Malformed)?;
let slice = self.bytes.get(self.at..end).ok_or(CursorError::Malformed)?;
self.at = end;
if slice == expected {
Ok(())
} else {
Err(CursorError::Malformed)
}
}
fn pixel_bytes(&mut self, count: usize) -> Result<&'a [u8], CursorError> {
let len = count.checked_mul(4).ok_or(CursorError::Malformed)?;
let end = self.at.checked_add(len).ok_or(CursorError::Malformed)?;
let slice = self.bytes.get(self.at..end).ok_or(CursorError::Malformed)?;
self.at = end;
Ok(slice)
}
}
pub(crate) fn parse(
bytes: &[u8],
limits: Limits,
requested: u32,
) -> Result<Vec<RawImage>, CursorError> {
let mut reader = Reader::new(bytes);
reader.tag(*MAGIC)?;
let header_len = reader.u32_le()?;
let _version = reader.u32_le()?;
let entry_count = reader.u32_le()?;
let entry_count = entry_count as usize;
let room = bytes.len().saturating_sub(header_len as usize) / TOC_ENTRY_LEN;
if entry_count > room {
return Err(CursorError::Malformed);
}
reader.seek(header_len as usize)?;
let mut entries: Vec<(u32, u32)> = Vec::new();
for _ in 0..entry_count {
let chunk_type = reader.u32_le()?;
let subtype = reader.u32_le()?;
let position = reader.u32_le()?;
if chunk_type == CHUNK_IMAGE {
entries.push((subtype, position));
}
}
let chosen = nearest_size(entries.iter().map(|(size, _)| *size), requested)
.ok_or(CursorError::NoImages)?;
let offsets: Vec<u32> = entries
.iter()
.filter(|(size, _)| *size == chosen)
.map(|(_, position)| *position)
.collect();
if offsets.len() > limits.max_frames {
return Err(CursorError::TooLarge);
}
let mut images = Vec::with_capacity(offsets.len());
for offset in offsets {
reader.seek(offset as usize)?;
images.push(parse_image(&mut reader, limits)?);
}
Ok(images)
}
fn parse_image(reader: &mut Reader<'_>, limits: Limits) -> Result<RawImage, CursorError> {
if reader.u32_le()? != IMAGE_HEADER_LEN {
return Err(CursorError::Malformed);
}
if reader.u32_le()? != CHUNK_IMAGE {
return Err(CursorError::Malformed);
}
let nominal_size = reader.u32_le()?;
if reader.u32_le()? != IMAGE_VERSION {
return Err(CursorError::Malformed);
}
let width = reader.u32_le()?;
let height = reader.u32_le()?;
let xhot = reader.u32_le()?;
let yhot = reader.u32_le()?;
let delay_ms = reader.u32_le()?;
if width == 0 || height == 0 {
return Err(CursorError::Malformed);
}
if width > limits.max_dim || height > limits.max_dim {
return Err(CursorError::TooLarge);
}
if xhot > width || yhot > height {
return Err(CursorError::Malformed);
}
let count = width as usize * height as usize;
let raw = reader.pixel_bytes(count)?;
let pixels = raw
.chunks_exact(4)
.map(|p| u32::from_le_bytes([p[0], p[1], p[2], p[3]]))
.collect();
Ok(RawImage {
nominal_size,
width,
height,
xhot,
yhot,
delay_ms,
pixels,
})
}
fn nearest_size(sizes: impl Iterator<Item = u32>, requested: u32) -> Option<u32> {
sizes.min_by_key(|size| (size.abs_diff(requested), *size))
}