use std::{
fmt,
fmt::{Debug, Formatter},
};
use nom::bytes::complete as bytes;
use nom::number::complete as number;
use nom::IResult;
#[derive(Debug, Clone, Eq, PartialEq)]
struct TOC {
toctype: u32,
subtype: u32,
pos: u32,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Image {
pub size: u32,
pub width: u32,
pub height: u32,
pub xhot: u32,
pub yhot: u32,
pub delay: u32,
pub pixels_rgba: Vec<u8>,
pub pixels_argb: Vec<u8>,
}
impl std::fmt::Display for Image {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Image")
.field("size", &self.size)
.field("width", &self.width)
.field("height", &self.height)
.field("xhot", &self.xhot)
.field("yhot", &self.yhot)
.field("delay", &self.delay)
.field("pixels", &"/* omitted */")
.finish()
}
}
fn parse_header(i: &[u8]) -> IResult<&[u8], u32> {
let (i, _) = bytes::tag("Xcur")(i)?;
let (i, _) = number::le_u32(i)?;
let (i, _) = number::le_u32(i)?;
let (i, ntoc) = number::le_u32(i)?;
Ok((i, ntoc))
}
fn parse_toc(i: &[u8]) -> IResult<&[u8], TOC> {
let (i, toctype) = number::le_u32(i)?;
let (i, subtype) = number::le_u32(i)?;
let (i, pos) = number::le_u32(i)?;
Ok((
i,
TOC {
toctype,
subtype,
pos,
},
))
}
fn parse_img(i: &[u8]) -> IResult<&[u8], Image> {
let (i, _) = bytes::tag([0x24, 0x00, 0x00, 0x00])(i)?;
let (i, _) = bytes::tag([0x02, 0x00, 0xfd, 0xff])(i)?;
let (i, size) = number::le_u32(i)?;
let (i, _) = bytes::tag([0x01, 0x00, 0x00, 0x00])(i)?;
let (i, width) = number::le_u32(i)?;
let (i, height) = number::le_u32(i)?;
let (i, xhot) = number::le_u32(i)?;
let (i, yhot) = number::le_u32(i)?;
let (i, delay) = number::le_u32(i)?;
let img_length: usize = (4 * width * height) as usize;
let (i, pixels_slice) = bytes::take(img_length)(i)?;
let pixels_argb = rgba_to_argb(pixels_slice);
let pixels_rgba = Vec::from(pixels_slice);
Ok((
i,
Image {
size,
width,
height,
xhot,
yhot,
delay,
pixels_argb,
pixels_rgba,
},
))
}
fn rgba_to_argb(i: &[u8]) -> Vec<u8> {
let mut res = Vec::with_capacity(i.len());
for rgba in i.chunks(4) {
if rgba.len() < 4 {
break;
}
res.push(rgba[3]);
res.push(rgba[0]);
res.push(rgba[1]);
res.push(rgba[2]);
}
res
}
pub fn parse_xcursor(content: &[u8]) -> Option<Vec<Image>> {
let (mut i, ntoc) = parse_header(content).ok()?;
let mut imgs = Vec::with_capacity(ntoc as usize);
for _ in 0..ntoc {
let (j, toc) = parse_toc(i).ok()?;
i = j;
if toc.toctype == 0xfffd_0002 {
let index = toc.pos as usize..;
let (_, img) = parse_img(&content[index]).ok()?;
imgs.push(img);
}
}
Some(imgs)
}
#[cfg(test)]
mod tests {
use super::{parse_header, parse_toc, rgba_to_argb, TOC};
const FILE_CONTENTS: [u8; 128] = [
0x58, 0x63, 0x75, 0x72, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00,
0x00, 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
];
#[test]
fn test_parse_header() {
assert_eq!(
parse_header(&FILE_CONTENTS).unwrap(),
(&FILE_CONTENTS[16..], 1)
)
}
#[test]
fn test_parse_toc() {
let toc = TOC {
toctype: 0xfffd0002,
subtype: 4,
pos: 0x1c,
};
assert_eq!(
parse_toc(&FILE_CONTENTS[16..]).unwrap(),
(&FILE_CONTENTS[28..], toc)
)
}
#[test]
fn test_rgba_to_argb() {
let initial: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
assert_eq!(rgba_to_argb(&initial), [3u8, 0, 1, 2, 7, 4, 5, 6])
}
#[test]
fn test_rgba_to_argb_extra_items() {
let initial: [u8; 9] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
assert_eq!(rgba_to_argb(&initial), &[3u8, 0, 1, 2, 7, 4, 5, 6]);
}
#[test]
fn test_rgba_to_argb_no_items() {
let initial: &[u8] = &[];
assert_eq!(initial, &rgba_to_argb(initial)[..]);
}
}