summaryrefslogtreecommitdiff
path: root/vendor/gif/src/reader/decoder.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
committerValentin Popov <valentin@popov.link>2024-01-08 00:21:28 +0300
commit1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch)
tree7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/gif/src/reader/decoder.rs
parent5ecd8cf2cba827454317368b68571df0d13d7842 (diff)
downloadfparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz
fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/gif/src/reader/decoder.rs')
-rw-r--r--vendor/gif/src/reader/decoder.rs724
1 files changed, 724 insertions, 0 deletions
diff --git a/vendor/gif/src/reader/decoder.rs b/vendor/gif/src/reader/decoder.rs
new file mode 100644
index 0000000..f0f8eea
--- /dev/null
+++ b/vendor/gif/src/reader/decoder.rs
@@ -0,0 +1,724 @@
+use std::cmp;
+use std::error;
+use std::fmt;
+use std::io;
+use std::mem;
+use std::default::Default;
+
+use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame};
+use crate::reader::DecodeOptions;
+
+use weezl::{BitOrder, decode::Decoder as LzwDecoder, LzwStatus};
+
+/// GIF palettes are RGB
+pub const PLTE_CHANNELS: usize = 3;
+
+/// An error returned in the case of the image not being formatted properly.
+#[derive(Debug)]
+pub struct DecodingFormatError {
+ underlying: Box<dyn error::Error + Send + Sync + 'static>
+}
+
+impl fmt::Display for DecodingFormatError {
+ fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::Display::fmt(&*self.underlying, fmt)
+ }
+}
+
+impl error::Error for DecodingFormatError {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ Some(&*self.underlying as _)
+ }
+}
+
+impl DecodingFormatError {
+ fn new(
+ err: impl Into<Box<dyn error::Error + Send + Sync>>,
+ ) -> Self {
+ DecodingFormatError {
+ underlying: err.into(),
+ }
+ }
+}
+
+#[derive(Debug)]
+/// Decoding error.
+pub enum DecodingError {
+ /// Returned if the image is found to be malformed.
+ Format(DecodingFormatError),
+ /// Wraps `std::io::Error`.
+ Io(io::Error),
+}
+
+impl DecodingError {
+ #[inline]
+ pub(crate) fn format(
+ err: impl Into<Box<dyn error::Error + Send + Sync>>,
+ ) -> Self {
+ DecodingError::Format(DecodingFormatError::new(err))
+ }
+}
+
+impl fmt::Display for DecodingError {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ DecodingError::Format(ref d) => d.fmt(fmt),
+ DecodingError::Io(ref err) => err.fmt(fmt),
+ }
+ }
+}
+
+impl error::Error for DecodingError {
+ fn source(&self) -> Option<&(dyn error::Error + 'static)> {
+ match *self {
+ DecodingError::Format(ref err) => Some(err),
+ DecodingError::Io(ref err) => Some(err),
+ }
+ }
+}
+
+impl From<io::Error> for DecodingError {
+ fn from(err: io::Error) -> Self {
+ DecodingError::Io(err)
+ }
+}
+
+impl From<DecodingFormatError> for DecodingError {
+ fn from(err: DecodingFormatError) -> Self {
+ DecodingError::Format(err)
+ }
+}
+
+/// Configures how extensions should be handled
+#[derive(PartialEq, Debug)]
+pub enum Extensions {
+ /// Saves all extention data
+ Save,
+ /// Skips the data of unknown extensions
+ /// and extracts the data from known ones
+ Skip
+}
+
+/// Indicates whether a certain object has been decoded
+#[derive(Debug)]
+pub enum Decoded<'a> {
+ /// Decoded nothing.
+ Nothing,
+ /// Global palette.
+ GlobalPalette(Vec<u8>),
+ /// Index of the background color in the global palette.
+ BackgroundColor(u8),
+ /// Decoded the image trailer.
+ Trailer,
+ /// The start of a block.
+ BlockStart(Block),
+ /// Decoded a sub-block. More sub-block are available.
+ ///
+ /// Indicates the label of the extension which might be unknown. A label of `0` is used when
+ /// the sub block does not belong to an extension.
+ SubBlockFinished(AnyExtension, &'a [u8]),
+ /// Decoded the last (or only) sub-block of a block.
+ ///
+ /// Indicates the label of the extension which might be unknown. A label of `0` is used when
+ /// the sub block does not belong to an extension.
+ BlockFinished(AnyExtension, &'a [u8]),
+ /// Decoded all information of the next frame.
+ ///
+ /// The returned frame does **not** contain any owned image data.
+ Frame(&'a Frame<'static>),
+ /// Decoded some data of the current frame.
+ Data(&'a [u8]),
+ /// No more data available the current frame.
+ DataEnd,
+
+}
+
+/// Internal state of the GIF decoder
+#[derive(Debug)]
+enum State {
+ Magic(usize, [u8; 6]),
+ U16Byte1(U16Value, u8),
+ U16(U16Value),
+ Byte(ByteValue),
+ GlobalPalette(usize),
+ BlockStart(Option<Block>),
+ /// Block end, with remaining expected data. NonZero for invalid EOF.
+ BlockEnd(u8),
+ ExtensionBlock(AnyExtension),
+ SkipBlock(usize),
+ LocalPalette(usize),
+ LzwInit(u8),
+ DecodeSubBlock(usize),
+ FrameDecoded,
+ Trailer
+}
+use self::State::*;
+
+/// U16 values that may occur in a GIF image
+#[derive(Debug)]
+enum U16Value {
+ /// Logical screen descriptor width
+ ScreenWidth,
+ /// Logical screen descriptor height
+ ScreenHeight,
+ /// Delay time
+ Delay,
+ /// Left frame offset
+ ImageLeft,
+ /// Top frame offset
+ ImageTop,
+ /// Frame width
+ ImageWidth,
+ /// Frame height
+ ImageHeight,
+}
+
+/// Single byte screen descriptor values
+#[derive(Debug)]
+enum ByteValue {
+ GlobalFlags,
+ Background { table_size: usize },
+ AspectRatio { table_size: usize },
+ ControlFlags,
+ ImageFlags,
+ TransparentIdx,
+ CodeSize,
+}
+
+/// GIF decoder which supports streaming
+pub struct StreamingDecoder {
+ state: Option<State>,
+ lzw_reader: Option<LzwDecoder>,
+ decode_buffer: Vec<u8>,
+ skip_extensions: bool,
+ check_frame_consistency: bool,
+ check_for_end_code: bool,
+ allow_unknown_blocks: bool,
+ version: Version,
+ width: u16,
+ height: u16,
+ global_color_table: Vec<u8>,
+ background_color: [u8; 4],
+ /// ext buffer
+ ext: ExtensionData,
+ /// Frame data
+ current: Option<Frame<'static>>,
+}
+
+/// One version number of the GIF standard.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+pub enum Version {
+ /// Version 87a, from May 1987.
+ V87a,
+ /// Version 89a, from July 1989.
+ V89a,
+}
+
+struct ExtensionData {
+ id: AnyExtension,
+ data: Vec<u8>,
+ is_block_end: bool,
+}
+
+impl StreamingDecoder {
+ /// Creates a new streaming decoder
+ pub fn new() -> StreamingDecoder {
+ let options = DecodeOptions::new();
+ Self::with_options(&options)
+ }
+
+ pub(crate) fn with_options(options: &DecodeOptions) -> Self {
+ StreamingDecoder {
+ state: Some(Magic(0, [0; 6])),
+ lzw_reader: None,
+ decode_buffer: vec![],
+ skip_extensions: true,
+ check_frame_consistency: options.check_frame_consistency,
+ check_for_end_code: options.check_for_end_code,
+ allow_unknown_blocks: options.allow_unknown_blocks,
+ version: Version::V87a,
+ width: 0,
+ height: 0,
+ global_color_table: Vec::new(),
+ background_color: [0, 0, 0, 0xFF],
+ ext: ExtensionData {
+ id: AnyExtension(0),
+ data: Vec::with_capacity(256), // 0xFF + 1 byte length
+ is_block_end: true,
+ },
+ current: None
+ }
+ }
+
+ /// Updates the internal state of the decoder.
+ ///
+ /// Returns the number of bytes consumed from the input buffer
+ /// and the last decoding result.
+ pub fn update<'a>(&'a mut self, mut buf: &[u8])
+ -> Result<(usize, Decoded<'a>), DecodingError> {
+ // NOTE: Do not change the function signature without double-checking the
+ // unsafe block!
+ let len = buf.len();
+ while buf.len() > 0 && self.state.is_some() {
+ match self.next_state(buf) {
+ Ok((bytes, Decoded::Nothing)) => {
+ buf = &buf[bytes..]
+ }
+ Ok((bytes, Decoded::Trailer)) => {
+ buf = &buf[bytes..];
+ break
+ }
+ Ok((bytes, result)) => {
+ buf = &buf[bytes..];
+ return Ok(
+ (len-buf.len(),
+ // This transmute just casts the lifetime away. Since Rust only
+ // has SESE regions, this early return cannot be worked out and
+ // such that the borrow region of self includes the whole block.
+ // The explixit lifetimes in the function signature ensure that
+ // this is safe.
+ // ### NOTE
+ // To check that everything is sound, return the result without
+ // the match (e.g. `return Ok(self.next_state(buf)?)`). If
+ // it compiles the returned lifetime is correct.
+ unsafe {
+ mem::transmute::<Decoded, Decoded>(result)
+ }
+ ))
+ }
+ Err(err) => return Err(err)
+ }
+ }
+ Ok((len-buf.len(), Decoded::Nothing))
+
+ }
+
+ /// Returns the data of the last extension that has been decoded.
+ pub fn last_ext(&self) -> (AnyExtension, &[u8], bool) {
+ (self.ext.id, &self.ext.data, self.ext.is_block_end)
+ }
+
+ #[inline(always)]
+ /// Current frame info as a mutable ref.
+ pub fn current_frame_mut<'a>(&'a mut self) -> &'a mut Frame<'static> {
+ self.current.as_mut().unwrap()
+ }
+
+ #[inline(always)]
+ /// Current frame info as a ref.
+ pub fn current_frame<'a>(&'a self) -> &'a Frame<'static> {
+ self.current.as_ref().unwrap()
+ }
+
+ /// Width of the image
+ pub fn width(&self) -> u16 {
+ self.width
+ }
+
+ /// Height of the image
+ pub fn height(&self) -> u16 {
+ self.height
+ }
+
+ /// The version number of the GIF standard used in this image.
+ ///
+ /// We suppose a minimum of `V87a` compatibility. This value will be reported until we have
+ /// read the version information in the magic header bytes.
+ pub fn version(&self) -> Version {
+ self.version
+ }
+
+ /// Configure whether extensions are saved or skipped.
+ #[deprecated = "Does not work as intended. In fact, doesn't do anything. This may disappear soon."]
+ pub fn set_extensions(&mut self, extensions: Extensions) {
+ self.skip_extensions = match extensions {
+ Extensions::Skip => true,
+ Extensions::Save => false,
+ }
+ }
+
+ fn next_state<'a>(&'a mut self, buf: &[u8]) -> Result<(usize, Decoded<'a>), DecodingError> {
+ macro_rules! goto (
+ ($n:expr, $state:expr) => ({
+ self.state = Some($state);
+ Ok(($n, Decoded::Nothing))
+ });
+ ($state:expr) => ({
+ self.state = Some($state);
+ Ok((1, Decoded::Nothing))
+ });
+ ($n:expr, $state:expr, emit $res:expr) => ({
+ self.state = Some($state);
+ Ok(($n, $res))
+ });
+ ($state:expr, emit $res:expr) => ({
+ self.state = Some($state);
+ Ok((1, $res))
+ })
+ );
+
+ let b = buf[0];
+
+ // Driver should ensure that state is never None
+ let state = self.state.take().unwrap();
+ //println!("{:?}", state);
+
+ match state {
+ Magic(i, mut version) => if i < 6 {
+ version[i] = b;
+ goto!(Magic(i+1, version))
+ } else if &version[..3] == b"GIF" {
+ self.version = match &version[3..] {
+ b"87a" => Version::V87a,
+ b"89a" => Version::V89a,
+ _ => return Err(DecodingError::format("unsupported GIF version"))
+ };
+ goto!(U16Byte1(U16Value::ScreenWidth, b))
+ } else {
+ Err(DecodingError::format("malformed GIF header"))
+ },
+ U16(next) => goto!(U16Byte1(next, b)),
+ U16Byte1(next, value) => {
+ use self::U16Value::*;
+ let value = ((b as u16) << 8) | value as u16;
+ match (next, value) {
+ (ScreenWidth, width) => {
+ self.width = width;
+ goto!(U16(U16Value::ScreenHeight))
+ },
+ (ScreenHeight, height) => {
+ self.height = height;
+ goto!(Byte(ByteValue::GlobalFlags))
+ },
+ (Delay, delay) => {
+ self.ext.data.push(value as u8);
+ self.ext.data.push(b);
+ self.current_frame_mut().delay = delay;
+ goto!(Byte(ByteValue::TransparentIdx))
+ },
+ (ImageLeft, left) => {
+ self.current_frame_mut().left = left;
+ goto!(U16(U16Value::ImageTop))
+ },
+ (ImageTop, top) => {
+ self.current_frame_mut().top = top;
+ goto!(U16(U16Value::ImageWidth))
+ },
+ (ImageWidth, width) => {
+ self.current_frame_mut().width = width;
+ goto!(U16(U16Value::ImageHeight))
+ },
+ (ImageHeight, height) => {
+ self.current_frame_mut().height = height;
+ goto!(Byte(ByteValue::ImageFlags))
+ }
+ }
+ }
+ Byte(value) => {
+ use self::ByteValue::*;
+ match value {
+ GlobalFlags => {
+ let global_table = b & 0x80 != 0;
+ let entries = if global_table {
+ let entries = PLTE_CHANNELS*(1 << ((b & 0b111) + 1) as usize);
+ self.global_color_table.reserve_exact(entries);
+ entries
+ } else {
+ 0usize
+ };
+ goto!(Byte(Background { table_size: entries }))
+ },
+ Background { table_size } => {
+ goto!(
+ Byte(AspectRatio { table_size: table_size }),
+ emit Decoded::BackgroundColor(b)
+ )
+ },
+ AspectRatio { table_size } => {
+ goto!(GlobalPalette(table_size))
+ },
+ ControlFlags => {
+ self.ext.data.push(b);
+ let control_flags = b;
+ if control_flags & 1 != 0 {
+ // Set to Some(...), gets overwritten later
+ self.current_frame_mut().transparent = Some(0)
+ }
+ self.current_frame_mut().needs_user_input =
+ control_flags & 0b10 != 0;
+ self.current_frame_mut().dispose = match DisposalMethod::from_u8(
+ (control_flags & 0b11100) >> 2
+ ) {
+ Some(method) => method,
+ None => DisposalMethod::Any
+ };
+ goto!(U16(U16Value::Delay))
+ }
+ TransparentIdx => {
+ self.ext.data.push(b);
+ if let Some(ref mut idx) = self.current_frame_mut().transparent {
+ *idx = b
+ }
+ goto!(SkipBlock(0))
+ //goto!(AwaitBlockEnd)
+ }
+ ImageFlags => {
+ let local_table = (b & 0b1000_0000) != 0;
+ let interlaced = (b & 0b0100_0000) != 0;
+ let table_size = b & 0b0000_0111;
+
+ self.current_frame_mut().interlaced = interlaced;
+
+ if self.check_frame_consistency {
+ // Consistency checks.
+ let (width, height) = (self.width, self.height);
+ let frame = self.current_frame_mut();
+ if width.checked_sub(frame.width) < Some(frame.left)
+ || height.checked_sub(frame.height) < Some(frame.top)
+ {
+ return Err(DecodingError::format("frame descriptor is out-of-bounds"))
+ }
+ }
+
+ if local_table {
+ let entries = PLTE_CHANNELS * (1 << (table_size + 1));
+
+ self.current_frame_mut().palette =
+ Some(Vec::with_capacity(entries));
+ goto!(LocalPalette(entries))
+ } else {
+ goto!(Byte(CodeSize))
+ }
+ },
+ CodeSize => goto!(LzwInit(b))
+ }
+ }
+ GlobalPalette(left) => {
+ let n = cmp::min(left, buf.len());
+ if left > 0 {
+ self.global_color_table.extend_from_slice(&buf[..n]);
+ goto!(n, GlobalPalette(left - n))
+ } else {
+ let idx = self.background_color[0];
+ match self.global_color_table.chunks(PLTE_CHANNELS).nth(idx as usize) {
+ Some(chunk) => self.background_color[..PLTE_CHANNELS]
+ .copy_from_slice(&chunk[..PLTE_CHANNELS]),
+ None => self.background_color[0] = 0
+ }
+ goto!(BlockStart(Block::from_u8(b)), emit Decoded::GlobalPalette(
+ mem::replace(&mut self.global_color_table, Vec::new())
+ ))
+ }
+ }
+ BlockStart(type_) => {
+ match type_ {
+ Some(Block::Image) => {
+ self.add_frame();
+ goto!(U16Byte1(U16Value::ImageLeft, b), emit Decoded::BlockStart(Block::Image))
+ }
+ Some(Block::Extension) => {
+ goto!(ExtensionBlock(AnyExtension(b)), emit Decoded::BlockStart(Block::Extension))
+ }
+ Some(Block::Trailer) => {
+ goto!(0, State::Trailer, emit Decoded::BlockStart(Block::Trailer))
+ }
+ None => {
+ if self.allow_unknown_blocks {
+ goto!(SkipBlock(b as usize))
+ } else {
+ Err(DecodingError::format("unknown block type encountered"))
+ }
+ }
+ }
+ }
+ BlockEnd(terminator) => {
+ if terminator == 0 {
+ if b == Block::Trailer as u8 {
+ goto!(0, BlockStart(Some(Block::Trailer)))
+ } else {
+ goto!(BlockStart(Block::from_u8(b)))
+ }
+ } else {
+ return Err(DecodingError::format(
+ "expected block terminator not found"
+ ))
+ }
+ }
+ ExtensionBlock(id) => {
+ use Extension::*;
+ self.ext.id = id;
+ self.ext.data.clear();
+ self.ext.data.push(b);
+ if let Some(ext) = Extension::from_u8(id.0) {
+ match ext {
+ Control => {
+ goto!(self.read_control_extension(b)?)
+ }
+ Text | Comment | Application => {
+ goto!(SkipBlock(b as usize))
+ }
+ }
+ } else {
+ return Err(DecodingError::format(
+ "unknown extention block encountered"
+ ))
+ }
+ }
+ SkipBlock(left) => {
+ let n = cmp::min(left, buf.len());
+ if left > 0 {
+ self.ext.data.extend_from_slice(&buf[..n]);
+ goto!(n, SkipBlock(left - n))
+ } else {
+ if b == 0 {
+ self.ext.is_block_end = true;
+ goto!(BlockEnd(b), emit Decoded::BlockFinished(self.ext.id, &self.ext.data))
+ } else {
+ self.ext.is_block_end = false;
+ goto!(SkipBlock(b as usize), emit Decoded::SubBlockFinished(self.ext.id, &self.ext.data))
+ }
+ }
+ }
+ LocalPalette(left) => {
+ let n = cmp::min(left, buf.len());
+ if left > 0 {
+
+ self.current_frame_mut().palette
+ .as_mut().unwrap().extend(buf[..n].iter().cloned());
+ goto!(n, LocalPalette(left - n))
+ } else {
+ goto!(LzwInit(b))
+ }
+ }
+ LzwInit(code_size) => {
+ // LZW spec: max 12 bits per code
+ if code_size > 11 {
+ return Err(DecodingError::format(
+ "invalid minimal code size"
+ ))
+ }
+ self.lzw_reader = Some(LzwDecoder::new(BitOrder::Lsb, code_size));
+ goto!(DecodeSubBlock(b as usize), emit Decoded::Frame(self.current_frame_mut()))
+ }
+ DecodeSubBlock(left) => {
+ if left > 0 {
+ let n = cmp::min(left, buf.len());
+ let max_bytes = self.current_frame().required_bytes();
+ let decoder = self.lzw_reader.as_mut().unwrap();
+ if decoder.has_ended() {
+ debug_assert!(n > 0, "Made forward progress after LZW end");
+ return goto!(n, DecodeSubBlock(0), emit Decoded::Data(&[]));
+ }
+
+ let mut dummy_target;
+ let decode_target;
+
+ if self.decode_buffer.is_empty() {
+ let size = (1 << 14).min(max_bytes);
+ self.decode_buffer = vec![0; size];
+ }
+
+ if max_bytes == 0 {
+ dummy_target = [0; 16];
+ decode_target = &mut dummy_target[..];
+ } else {
+ decode_target = self.decode_buffer.as_mut_slice();
+ }
+
+ debug_assert!(!decode_target.is_empty(), "LZW decoding can make forward progress.");
+ let decoded = decoder.decode_bytes(&buf[..n], decode_target);
+
+ if let Err(err) = decoded.status {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, &*format!("{:?}", err)).into());
+ }
+
+ let bytes = &self.decode_buffer[..decoded.consumed_out.min(max_bytes)];
+ let consumed = decoded.consumed_in;
+ goto!(consumed, DecodeSubBlock(left - consumed), emit Decoded::Data(bytes))
+ } else if b != 0 { // decode next sub-block
+ goto!(DecodeSubBlock(b as usize))
+ } else {
+ let max_bytes = self.current_frame().required_bytes();
+ // The end of the lzw stream is only reached if left == 0 and an additional call
+ // to `decode_bytes` results in an empty slice.
+ let decoder = self.lzw_reader.as_mut().unwrap();
+ // Some mutable bytes to decode into. We need this for forward progress in
+ // `lzw`. However, in some cases we do not actually need any bytes, when
+ // `max_bytes` is `0`.
+ let mut dummy_target;
+ let decode_target;
+
+ if self.decode_buffer.is_empty() {
+ let size = (1 << 14).min(max_bytes);
+ self.decode_buffer = vec![0; size];
+ }
+
+ if max_bytes == 0 {
+ dummy_target = [0; 16];
+ decode_target = &mut dummy_target[..];
+ } else {
+ decode_target = self.decode_buffer.as_mut_slice();
+ }
+
+ debug_assert!(!decode_target.is_empty(), "LZW decoding can make forward progress.");
+ let decoded = decoder.decode_bytes(&[], decode_target);
+
+ match decoded.status {
+ Ok(LzwStatus::Done) | Ok(LzwStatus::Ok) => {},
+ Ok(LzwStatus::NoProgress) => {
+ if self.check_for_end_code {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, "No end code in lzw stream").into());
+ } else {
+ self.current = None;
+ return goto!(0, FrameDecoded, emit Decoded::DataEnd);
+ }
+ },
+ Err(err) => {
+ return Err(io::Error::new(io::ErrorKind::InvalidData, &*format!("{:?}", err)).into());
+ }
+ }
+ let bytes = &self.decode_buffer[..decoded.consumed_out.min(max_bytes)];
+
+ if bytes.len() > 0 {
+ goto!(0, DecodeSubBlock(0), emit Decoded::Data(bytes))
+ } else {
+ // end of image data reached
+ self.current = None;
+ goto!(0, FrameDecoded, emit Decoded::DataEnd)
+ }
+ }
+ }
+ FrameDecoded => {
+ goto!(BlockEnd(b))
+ }
+ Trailer => {
+ self.state = None;
+ Ok((1, Decoded::Trailer))
+ //panic!("EOF {:?}", self)
+ }
+ }
+ }
+
+ fn read_control_extension(&mut self, b: u8) -> Result<State, DecodingError> {
+ self.add_frame();
+ self.ext.data.push(b);
+ if b != 4 {
+ return Err(DecodingError::format(
+ "control extension has wrong length"
+ ))
+ }
+ Ok(Byte(ByteValue::ControlFlags))
+ }
+
+ fn add_frame(&mut self) {
+ if self.current.is_none() {
+ self.current = Some(Frame::default())
+ }
+ }
+}
+
+#[test]
+fn error_cast() {
+ let _ : Box<dyn error::Error> = DecodingError::Format(DecodingFormatError::new("testing")).into();
+}