Skip to content

Commit eff2da4

Browse files
authored
Merge pull request #19 from psychon/direct-file-reading
Add API to directly load a file
2 parents b57f683 + 440ee4d commit eff2da4

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)