1use std::{
2 fmt::{self, Debug, Formatter},
3 io::{Cursor, Error, ErrorKind, Read, Result as IoResult, Seek, SeekFrom},
4};
5
6#[derive(Debug, Clone, Eq, PartialEq)]
7struct Toc {
8 toctype: u32,
9 subtype: u32,
10 pos: u32,
11}
12
13#[derive(Clone, Eq, PartialEq, Debug)]
16pub struct Image {
17 pub size: u32,
19
20 pub width: u32,
22
23 pub height: u32,
25
26 pub xhot: u32,
28
29 pub yhot: u32,
31
32 pub delay: u32,
34
35 pub pixels_rgba: Vec<u8>,
37
38 pub pixels_argb: Vec<u8>,
40}
41
42impl std::fmt::Display for Image {
43 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
44 f.debug_struct("Image")
45 .field("size", &self.size)
46 .field("width", &self.width)
47 .field("height", &self.height)
48 .field("xhot", &self.xhot)
49 .field("yhot", &self.yhot)
50 .field("delay", &self.delay)
51 .field("pixels", &"/* omitted */")
52 .finish()
53 }
54}
55
56fn parse_header(i: &mut impl Read) -> IoResult<(u32, u32)> {
57 i.tag(*b"Xcur")?;
58 let header = i.u32_le()?;
59 let _version = i.u32_le()?;
60 let ntoc = i.u32_le()?;
61
62 Ok((header, ntoc))
63}
64
65fn parse_toc(i: &mut impl Read) -> IoResult<Toc> {
66 let toctype = i.u32_le()?; let subtype = i.u32_le()?; let pos = i.u32_le()?; Ok(Toc {
71 toctype,
72 subtype,
73 pos,
74 })
75}
76
77fn parse_img(i: &mut impl Read) -> IoResult<Image> {
78 i.tag([0x24, 0x00, 0x00, 0x00])?; i.tag([0x02, 0x00, 0xfd, 0xff])?; let size = i.u32_le()?;
81 i.tag([0x01, 0x00, 0x00, 0x00])?; let width = i.u32_le()?;
83 let height = i.u32_le()?;
84 let xhot = i.u32_le()?;
85 let yhot = i.u32_le()?;
86 let delay = i.u32_le()?;
87
88 if width > 0x7fff || height > 0x7fff {
90 return Err(Error::new(ErrorKind::Other, "Image too large"));
91 }
92 if width == 0 || height == 0 {
93 return Err(Error::new(
94 ErrorKind::Other,
95 "Image with zero width or height",
96 ));
97 }
98 if xhot > width || yhot > height {
99 return Err(Error::new(ErrorKind::Other, "Hotspot outside image"));
100 }
101
102 let img_length: usize = (4 * width * height) as usize;
103 let pixels_rgba = i.take_bytes(img_length)?;
104 let pixels_argb = rgba_to_argb(&pixels_rgba);
105
106 Ok(Image {
107 size,
108 width,
109 height,
110 xhot,
111 yhot,
112 delay,
113 pixels_argb,
114 pixels_rgba,
115 })
116}
117
118fn rgba_to_argb(i: &[u8]) -> Vec<u8> {
123 let mut res = Vec::with_capacity(i.len());
124
125 for rgba in i.chunks_exact(4) {
126 res.push(rgba[3]);
127 res.push(rgba[0]);
128 res.push(rgba[1]);
129 res.push(rgba[2]);
130 }
131
132 res
133}
134
135pub fn parse_xcursor(content: &[u8]) -> Option<Vec<Image>> {
137 parse_xcursor_stream(&mut Cursor::new(content)).ok()
138}
139
140pub fn parse_xcursor_stream<R: Read + Seek>(input: &mut R) -> IoResult<Vec<Image>> {
142 let (header, ntoc) = parse_header(input)?;
143 input.seek(SeekFrom::Start(header as u64))?;
144
145 let mut img_indices = Vec::new();
146 for _ in 0..ntoc {
147 let toc = parse_toc(input)?;
148
149 if toc.toctype == 0xfffd_0002 {
150 img_indices.push(toc.pos);
151 }
152 }
153
154 let mut imgs = Vec::with_capacity(ntoc as usize);
155 for index in img_indices {
156 input.seek(SeekFrom::Start(index.into()))?;
157 imgs.push(parse_img(input)?);
158 }
159
160 Ok(imgs)
161}
162
163trait StreamExt {
164 fn tag(&mut self, tag: [u8; 4]) -> IoResult<()>;
166
167 fn take_bytes(&mut self, len: usize) -> IoResult<Vec<u8>>;
169
170 fn u32_le(&mut self) -> IoResult<u32>;
172}
173
174impl<R: Read> StreamExt for R {
175 fn tag(&mut self, tag: [u8; 4]) -> IoResult<()> {
176 let mut data = [0u8; 4];
177 self.read_exact(&mut data)?;
178 if data != tag {
179 Err(Error::new(ErrorKind::Other, "Tag mismatch"))
180 } else {
181 Ok(())
182 }
183 }
184
185 fn take_bytes(&mut self, len: usize) -> IoResult<Vec<u8>> {
186 let mut data = vec![0; len];
187 self.read_exact(&mut data)?;
188 Ok(data)
189 }
190
191 fn u32_le(&mut self) -> IoResult<u32> {
192 let mut data = [0u8; 4];
193 self.read_exact(&mut data)?;
194 Ok(u32::from_le_bytes(data))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::{parse_header, parse_toc, parse_xcursor, rgba_to_argb, Image, Toc};
201 use std::io::Cursor;
202
203 const FILE_CONTENTS: [u8; 128] = [
206 0x58, 0x63, 0x75, 0x72, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
207 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00,
208 0x00, 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04,
209 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
210 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
211 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
212 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
213 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
214 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
215 ];
216
217 #[test]
218 fn test_parse_header() {
219 let mut cursor = Cursor::new(&FILE_CONTENTS[..]);
220 assert_eq!(parse_header(&mut cursor).unwrap(), (16, 1));
221 assert_eq!(cursor.position(), 16);
222 }
223
224 #[test]
225 fn test_parse_toc() {
226 let toc = Toc {
227 toctype: 0xfffd0002,
228 subtype: 4,
229 pos: 0x1c,
230 };
231 let mut cursor = Cursor::new(&FILE_CONTENTS[16..]);
232 assert_eq!(parse_toc(&mut cursor).unwrap(), toc);
233 assert_eq!(cursor.position(), 28 - 16);
234 }
235
236 #[test]
237 fn test_parse_image() {
238 let make_pixels = |pixel: [u8; 4]| {
240 std::iter::repeat(pixel)
242 .take(4 * 4)
243 .flat_map(|p| p.iter().cloned().collect::<Vec<_>>())
244 .collect()
245 };
246 let expected = Image {
247 size: 4,
248 width: 4,
249 height: 4,
250 xhot: 1,
251 yhot: 1,
252 delay: 1,
253 pixels_rgba: make_pixels([0, 0, 0, 128]),
254 pixels_argb: make_pixels([128, 0, 0, 0]),
255 };
256 assert_eq!(Some(vec![expected]), parse_xcursor(&FILE_CONTENTS));
257 }
258
259 #[test]
260 fn test_one_image_three_times() {
261 let data = [
262 b'X', b'c', b'u', b'r', 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfd, 0xff, 0x04, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfd, 0xff, 0x03, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfd, 0xff, 0x04, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x02, 0x00, 0xfd, 0xff, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, ];
288 let expected = Image {
289 size: 4,
290 width: 1,
291 height: 1,
292 xhot: 0,
293 yhot: 0,
294 delay: 0,
295 pixels_rgba: vec![0x12, 0x34, 0x56, 0x78],
296 pixels_argb: vec![0x78, 0x12, 0x34, 0x56],
297 };
298 assert_eq!(
299 Some(vec![expected.clone(), expected.clone(), expected.clone()]),
300 parse_xcursor(&data)
301 );
302 }
303
304 #[test]
305 fn test_rgba_to_argb() {
306 let initial: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
307
308 assert_eq!(rgba_to_argb(&initial), [3u8, 0, 1, 2, 7, 4, 5, 6])
309 }
310
311 #[test]
312 fn test_rgba_to_argb_extra_items() {
313 let initial: [u8; 9] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
314
315 assert_eq!(rgba_to_argb(&initial), &[3u8, 0, 1, 2, 7, 4, 5, 6]);
316 }
317
318 #[test]
319 fn test_rgba_to_argb_no_items() {
320 let initial: &[u8] = &[];
321
322 assert_eq!(initial, &rgba_to_argb(initial)[..]);
323 }
324}