diff --git a/crates/pdn/src/lib.rs b/crates/pdn/src/lib.rs index 4217dd912..313d68b17 100644 --- a/crates/pdn/src/lib.rs +++ b/crates/pdn/src/lib.rs @@ -313,18 +313,45 @@ impl<'a> Document<'a> { /// [`Layer::surface`] names). Only one decoded surface is held at a time unless `each` keeps /// them. Data after the last block is an error. pub fn read_surfaces(&self, keep_going: &dyn Fn() -> bool, each: &mut dyn FnMut(usize, Surface)) -> Result<()> { + self.try_read_surfaces(keep_going, &mut |index, surface| -> std::result::Result<(), std::convert::Infallible> { + each(index, surface); + Ok(()) + }) + .map_err(|error| match error { + ReadSurfacesError::Decode(error) => error, + ReadSurfacesError::Callback(never) => match never {}, + }) + } + + /// Decodes surfaces as [`Self::read_surfaces`], stopping when `each` returns an error. + /// Cancellation returned by the callback is propagated before the next surface is allocated. + pub fn try_read_surfaces( + &self, + keep_going: &dyn Fn() -> bool, + each: &mut dyn FnMut(usize, Surface) -> std::result::Result<(), E>, + ) -> std::result::Result<(), ReadSurfacesError> { let mut reader = Reader { bytes: self.bytes, pos: self.pixels_at }; for (i, block) in self.blocks.iter().enumerate() { - let data = pixels(&mut reader, block.length, keep_going)?; - each(i, Surface { width: self.width, stride: block.stride, data }); + check(keep_going).map_err(ReadSurfacesError::Decode)?; + let data = pixels(&mut reader, block.length, keep_going).map_err(ReadSurfacesError::Decode)?; + each(i, Surface { width: self.width, stride: block.stride, data }).map_err(ReadSurfacesError::Callback)?; } if reader.pos != self.bytes.len() { - return Err(invalid("unexpected trailing data")); + return Err(ReadSurfacesError::Decode(invalid("unexpected trailing data"))); } Ok(()) } } +/// Failure while decoding surfaces or handling one in [`Document::try_read_surfaces`]. +#[derive(Debug)] +pub enum ReadSurfacesError { + /// The PDN pixel stream is invalid, or decoding was cancelled through `keep_going`. + Decode(Error), + /// The caller stopped while handling a decoded surface. + Callback(E), +} + /// Reads a memory block's numbered chunks in their on-disk order; each chunk has its own gzip /// stream and checksum. Inflates directly into the bounded destination, checking its exact size. fn pixels(reader: &mut Reader<'_>, length: usize, keep_going: &dyn Fn() -> bool) -> Result> { diff --git a/crates/pdn/src/tests.rs b/crates/pdn/src/tests.rs index bf6c8586b..b5bc713cf 100644 --- a/crates/pdn/src/tests.rs +++ b/crates/pdn/src/tests.rs @@ -229,6 +229,14 @@ fn reading_can_be_cancelled_during_parsing_and_pixel_decode() { let seen = Cell::new(0); let result = doc.read_surfaces(&|| seen.get() == 0, &mut |_, _| seen.set(seen.get() + 1)); assert_eq!((result, seen.get()), (Err(Error::Cancelled), 1), "cancelled after the first surface"); + + let seen = Cell::new(0); + let result = doc.try_read_surfaces(GO, &mut |_, _| { + seen.set(seen.get() + 1); + Err(Error::Cancelled) + }); + assert!(matches!(result, Err(ReadSurfacesError::Callback(Error::Cancelled)))); + assert_eq!(seen.get(), 1, "a failed callback stops before decoding another surface"); } proptest::proptest! {