1use std::{
2 convert::TryInto,
3 fmt,
4 fmt::{Debug, Formatter},
5 mem::size_of,
6};
7
8#[derive(Debug, Clone, Eq, PartialEq)]
9struct Toc {
10 toctype: u32,
11 subtype: u32,
12 pos: u32,
13}
14
15/// A struct representing an image.
16/// Pixels are in ARGB format, with each byte representing a single channel.
17#[derive(Clone, Eq, PartialEq, Debug)]
18pub struct Image {
19 /// The nominal size of the image.
20 pub size: u32,
21
22 /// The actual width of the image. Doesn't need to match `size`.
23 pub width: u32,
24
25 /// The actual height of the image. Doesn't need to match `size`.
26 pub height: u32,
27
28 /// The X coordinate of the hotspot pixel (the pixel where the tip of the arrow is situated)
29 pub xhot: u32,
30
31 /// The Y coordinate of the hotspot pixel (the pixel where the tip of the arrow is situated)
32 pub yhot: u32,
33
34 /// The amount of time (in milliseconds) that this image should be shown for, before switching to the next.
35 pub delay: u32,
36
37 /// A slice containing the pixels' bytes, in RGBA format (or, in the order of the file).
38 pub pixels_rgba: Vec<u8>,
39
40 /// A slice containing the pixels' bytes, in ARGB format.
41 pub pixels_argb: Vec<u8>,
42}
43
44impl std::fmt::Display for Image {
45 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
46 f&mut DebugStruct<'_, '_>.debug_struct("Image")
47 .field("size", &self.size)
48 .field("width", &self.width)
49 .field("height", &self.height)
50 .field("xhot", &self.xhot)
51 .field("yhot", &self.yhot)
52 .field("delay", &self.delay)
53 .field(name:"pixels", &"/* omitted */")
54 .finish()
55 }
56}
57
58fn parse_header(mut i: Stream<'_>) -> Option<(Stream<'_>, u32)> {
59 i.tag(b"Xcur")?;
60 i.u32_le()?;
61 i.u32_le()?;
62 let ntoc: u32 = i.u32_le()?;
63
64 Some((i, ntoc))
65}
66
67fn parse_toc(mut i: Stream<'_>) -> Option<(Stream<'_>, Toc)> {
68 let toctype: u32 = i.u32_le()?; // Type
69 let subtype: u32 = i.u32_le()?; // Subtype
70 let pos: u32 = i.u32_le()?; // Position
71
72 Some((
73 i,
74 Toc {
75 toctype,
76 subtype,
77 pos,
78 },
79 ))
80}
81
82fn parse_img(mut i: Stream<'_>) -> Option<(Stream<'_>, Image)> {
83 i.tag(&[0x24, 0x00, 0x00, 0x00])?; // Header size
84 i.tag(&[0x02, 0x00, 0xfd, 0xff])?; // Type
85 let size = i.u32_le()?;
86 i.tag(&[0x01, 0x00, 0x00, 0x00])?; // Image version (1)
87 let width = i.u32_le()?;
88 let height = i.u32_le()?;
89 let xhot = i.u32_le()?;
90 let yhot = i.u32_le()?;
91 let delay = i.u32_le()?;
92
93 let img_length: usize = (4 * width * height) as usize;
94 let pixels_slice = i.take_bytes(img_length)?;
95 let pixels_argb = rgba_to_argb(pixels_slice);
96 let pixels_rgba = Vec::from(pixels_slice);
97
98 Some((
99 i,
100 Image {
101 size,
102 width,
103 height,
104 xhot,
105 yhot,
106 delay,
107 pixels_argb,
108 pixels_rgba,
109 },
110 ))
111}
112
113/// Converts a RGBA slice into an ARGB vec
114///
115/// Note that, if the input length is not
116/// a multiple of 4, the extra elements are ignored.
117fn rgba_to_argb(i: &[u8]) -> Vec<u8> {
118 let mut res: Vec = Vec::with_capacity(i.len());
119
120 for rgba: &[u8] in i.chunks(chunk_size:4) {
121 if rgba.len() < 4 {
122 break;
123 }
124
125 res.push(rgba[3]);
126 res.push(rgba[0]);
127 res.push(rgba[1]);
128 res.push(rgba[2]);
129 }
130
131 res
132}
133
134/// Parse an XCursor file into its images.
135pub fn parse_xcursor(content: &[u8]) -> Option<Vec<Image>> {
136 let (mut i: &[u8], ntoc: u32) = parse_header(content)?;
137 let mut imgs: Vec = Vec::with_capacity(ntoc as usize);
138
139 for _ in 0..ntoc {
140 let (j: &[u8], toc: Toc) = parse_toc(i)?;
141 i = j;
142
143 if toc.toctype == 0xfffd_0002 {
144 let index: RangeFrom = toc.pos as usize..;
145 let (_, img: Image) = parse_img(&content[index])?;
146 imgs.push(img);
147 }
148 }
149
150 Some(imgs)
151}
152
153type Stream<'a> = &'a [u8];
154
155trait StreamExt<'a>: 'a {
156 /// Parse a series of bytes, returning `None` if it doesn't exist.
157 fn tag(&mut self, tag: &[u8]) -> Option<()>;
158
159 /// Take a slice of bytes.
160 fn take_bytes(&mut self, len: usize) -> Option<&'a [u8]>;
161
162 /// Parse a 32-bit little endian number.
163 fn u32_le(&mut self) -> Option<u32>;
164}
165
166impl<'a> StreamExt<'a> for Stream<'a> {
167 fn tag(&mut self, tag: &[u8]) -> Option<()> {
168 if self.len() < tag.len() || self[..tag.len()] != *tag {
169 None
170 } else {
171 *self = &self[tag.len()..];
172 Some(())
173 }
174 }
175
176 fn take_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
177 if self.len() < len {
178 None
179 } else {
180 let (value, tail) = self.split_at(len);
181 *self = tail;
182 Some(value)
183 }
184 }
185
186 fn u32_le(&mut self) -> Option<u32> {
187 self.take_bytes(size_of::<u32>())
188 .map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::{parse_header, parse_toc, rgba_to_argb, Toc};
195
196 // A sample (and simple) XCursor file generated with xcursorgen.
197 // Contains a single 4x4 image.
198 const FILE_CONTENTS: [u8; 128] = [
199 0x58, 0x63, 0x75, 0x72, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
200 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00,
201 0x00, 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04,
202 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
203 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
204 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
205 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
206 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
207 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
208 ];
209
210 #[test]
211 fn test_parse_header() {
212 assert_eq!(
213 parse_header(&FILE_CONTENTS).unwrap(),
214 (&FILE_CONTENTS[16..], 1)
215 )
216 }
217
218 #[test]
219 fn test_parse_toc() {
220 let toc = Toc {
221 toctype: 0xfffd0002,
222 subtype: 4,
223 pos: 0x1c,
224 };
225 assert_eq!(
226 parse_toc(&FILE_CONTENTS[16..]).unwrap(),
227 (&FILE_CONTENTS[28..], toc)
228 )
229 }
230
231 #[test]
232 fn test_rgba_to_argb() {
233 let initial: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
234
235 assert_eq!(rgba_to_argb(&initial), [3u8, 0, 1, 2, 7, 4, 5, 6])
236 }
237
238 #[test]
239 fn test_rgba_to_argb_extra_items() {
240 let initial: [u8; 9] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
241
242 assert_eq!(rgba_to_argb(&initial), &[3u8, 0, 1, 2, 7, 4, 5, 6]);
243 }
244
245 #[test]
246 fn test_rgba_to_argb_no_items() {
247 let initial: &[u8] = &[];
248
249 assert_eq!(initial, &rgba_to_argb(initial)[..]);
250 }
251}
252