Skip to content

Commit 440ee4d

Browse files
committed
Add API to directly load a file
So far, the parse_xcursor() expected a byte slice as its input. Since the use of this crate is usually in reading cursor files, that means one has to first load the whole file to memory and then give the result to this crate, which copies some of that data. For example, here is wayland-cursor using read_to_end() to load a file into memory and then passing the result to xcursor-rs: https://github.com/Smithay/wayland-rs/blob/8fa95cfaae493f748d994ae2aee5433dd183e4ea/wayland-cursor/src/lib.rs#L250-L251 In this commit I want to change this. For API stability reasons, I am not touching parse_xcursor. Instead, a new function parse_xcursor_stream() is added. This function can be called with anything that implements both std::io::Read and Seek. The result is either a list of images or a std::io::Error. The existing parse_xcursor() simply forwards to this new function using std::io::Cursor. All the parsing functions are changed to take a &mut impl Read. The internal StreamExt trait is changed to work on Read. Luckily, the use of Seek is so far contained in parse_xcursor_stream(). Since the new parse_xcursor_stream() function returns an std::io::Error, tag mismatches need to be turned into such an Error. For that, I simply used ErrorKind::Other with the text "Tag mismatch". This error is externally visible through this new function. No behavioural changes to the code are intended. Signed-off-by: Uli Schlachter <psychon@znc.in>
1 parent b57f683 commit 440ee4d

File tree

1 file changed

+65
-66
lines changed

1 file changed

+65
-66
lines changed

src/parser.rs

+65-66
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{
22
convert::TryInto,
33
fmt,
44
fmt::{Debug, Formatter},
5+
io::{Cursor, Read, Result as IoResult, Seek, SeekFrom},
56
mem::size_of,
67
};
78

@@ -55,31 +56,28 @@ impl std::fmt::Display for Image {
5556
}
5657
}
5758

58-
fn parse_header(mut i: Stream<'_>) -> Option<(Stream<'_>, u32)> {
59+
fn parse_header(i: &mut impl Read) -> IoResult<u32> {
5960
i.tag(b"Xcur")?;
6061
i.u32_le()?;
6162
i.u32_le()?;
6263
let ntoc = i.u32_le()?;
6364

64-
Some((i, ntoc))
65+
Ok(ntoc)
6566
}
6667

67-
fn parse_toc(mut i: Stream<'_>) -> Option<(Stream<'_>, Toc)> {
68+
fn parse_toc(i: &mut impl Read) -> IoResult<Toc> {
6869
let toctype = i.u32_le()?; // Type
6970
let subtype = i.u32_le()?; // Subtype
7071
let pos = i.u32_le()?; // Position
7172

72-
Some((
73-
i,
74-
Toc {
75-
toctype,
76-
subtype,
77-
pos,
78-
},
79-
))
73+
Ok(Toc {
74+
toctype,
75+
subtype,
76+
pos,
77+
})
8078
}
8179

82-
fn parse_img(mut i: Stream<'_>) -> Option<(Stream<'_>, Image)> {
80+
fn parse_img(i: &mut impl Read) -> IoResult<Image> {
8381
i.tag(&[0x24, 0x00, 0x00, 0x00])?; // Header size
8482
i.tag(&[0x02, 0x00, 0xfd, 0xff])?; // Type
8583
let size = i.u32_le()?;
@@ -91,23 +89,19 @@ fn parse_img(mut i: Stream<'_>) -> Option<(Stream<'_>, Image)> {
9189
let delay = i.u32_le()?;
9290

9391
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-
))
92+
let pixels_rgba = i.take_bytes(img_length)?;
93+
let pixels_argb = rgba_to_argb(&pixels_rgba);
94+
95+
Ok(Image {
96+
size,
97+
width,
98+
height,
99+
xhot,
100+
yhot,
101+
delay,
102+
pixels_argb,
103+
pixels_rgba,
104+
})
111105
}
112106

113107
/// Converts a RGBA slice into an ARGB vec
@@ -133,57 +127,63 @@ fn rgba_to_argb(i: &[u8]) -> Vec<u8> {
133127

134128
/// Parse an XCursor file into its images.
135129
pub fn parse_xcursor(content: &[u8]) -> Option<Vec<Image>> {
136-
let (mut i, ntoc) = parse_header(content)?;
137-
let mut imgs = Vec::with_capacity(ntoc as usize);
130+
parse_xcursor_stream(&mut Cursor::new(content)).ok()
131+
}
138132

133+
/// Parse an XCursor file into its images.
134+
pub fn parse_xcursor_stream<R: Read + Seek>(input: &mut R) -> IoResult<Vec<Image>> {
135+
let ntoc = parse_header(input)?;
136+
137+
let mut img_indices = Vec::new();
139138
for _ in 0..ntoc {
140-
let (j, toc) = parse_toc(i)?;
141-
i = j;
139+
let toc = parse_toc(input)?;
142140

143141
if toc.toctype == 0xfffd_0002 {
144-
let index = toc.pos as usize..;
145-
let (_, img) = parse_img(&content[index])?;
146-
imgs.push(img);
142+
img_indices.push(toc.pos);
147143
}
148144
}
149145

150-
Some(imgs)
151-
}
146+
let mut imgs = Vec::with_capacity(ntoc as usize);
147+
for index in img_indices {
148+
input.seek(SeekFrom::Start(index.into()))?;
149+
imgs.push(parse_img(input)?);
150+
}
152151

153-
type Stream<'a> = &'a [u8];
152+
Ok(imgs)
153+
}
154154

155-
trait StreamExt<'a>: 'a {
155+
trait StreamExt {
156156
/// Parse a series of bytes, returning `None` if it doesn't exist.
157-
fn tag(&mut self, tag: &[u8]) -> Option<()>;
157+
fn tag(&mut self, tag: &[u8]) -> IoResult<()>;
158158

159159
/// Take a slice of bytes.
160-
fn take_bytes(&mut self, len: usize) -> Option<&'a [u8]>;
160+
fn take_bytes(&mut self, len: usize) -> IoResult<Vec<u8>>;
161161

162162
/// Parse a 32-bit little endian number.
163-
fn u32_le(&mut self) -> Option<u32>;
163+
fn u32_le(&mut self) -> IoResult<u32>;
164164
}
165165

166-
impl<'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
166+
impl<R: Read> StreamExt for R {
167+
fn tag(&mut self, tag: &[u8]) -> IoResult<()> {
168+
let mut data = vec![0; tag.len()];
169+
self.read_exact(&mut data)?;
170+
if data != *tag {
171+
Err(std::io::Error::new(
172+
std::io::ErrorKind::Other,
173+
"Tag mismatch",
174+
))
170175
} else {
171-
*self = &self[tag.len()..];
172-
Some(())
176+
Ok(())
173177
}
174178
}
175179

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-
}
180+
fn take_bytes(&mut self, len: usize) -> IoResult<Vec<u8>> {
181+
let mut data = vec![0; len];
182+
self.read_exact(&mut data)?;
183+
Ok(data)
184184
}
185185

186-
fn u32_le(&mut self) -> Option<u32> {
186+
fn u32_le(&mut self) -> IoResult<u32> {
187187
self.take_bytes(size_of::<u32>())
188188
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
189189
}
@@ -192,6 +192,7 @@ impl<'a> StreamExt<'a> for Stream<'a> {
192192
#[cfg(test)]
193193
mod tests {
194194
use super::{parse_header, parse_toc, rgba_to_argb, Toc};
195+
use std::io::Cursor;
195196

196197
// A sample (and simple) XCursor file generated with xcursorgen.
197198
// Contains a single 4x4 image.
@@ -209,10 +210,9 @@ mod tests {
209210

210211
#[test]
211212
fn test_parse_header() {
212-
assert_eq!(
213-
parse_header(&FILE_CONTENTS).unwrap(),
214-
(&FILE_CONTENTS[16..], 1)
215-
)
213+
let mut cursor = Cursor::new(&FILE_CONTENTS);
214+
assert_eq!(parse_header(&mut cursor).unwrap(), 1);
215+
assert_eq!(cursor.position(), 16);
216216
}
217217

218218
#[test]
@@ -222,10 +222,9 @@ mod tests {
222222
subtype: 4,
223223
pos: 0x1c,
224224
};
225-
assert_eq!(
226-
parse_toc(&FILE_CONTENTS[16..]).unwrap(),
227-
(&FILE_CONTENTS[28..], toc)
228-
)
225+
let mut cursor = Cursor::new(&FILE_CONTENTS[16..]);
226+
assert_eq!(parse_toc(&mut cursor).unwrap(), toc);
227+
assert_eq!(cursor.position(), 28 - 16);
229228
}
230229

231230
#[test]

0 commit comments

Comments
 (0)