diff --git a/src/animation.rs b/src/animation.rs index 1138c04..0ba07f4 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -6,11 +6,19 @@ //! and so forth. use color::default_color_index; +use data::Format; +use data::Header; use data::IldaEntry; +use data::TrueColorPoint2d; use error::IldaError; -use parser::read_bytes; -use parser::read_file; +use parser::stream_with_error; +use parser::IldaEntryIteratorWithError; use point::SimplePoint; +use std::fs::File; +use std::io::Cursor; +use std::io::{Read, Write}; +use std::vec::IntoIter; +use writer::IldaWriter; /// An animation is comprised of one or more frames. #[derive(Clone)] @@ -26,7 +34,83 @@ pub struct Frame { company_name: Option, } +/// Output ILDA frames into a Writer that implements Write +pub struct AnimationStreamWriter where T: Write { + inner: IldaWriter, + finalized: bool, +} + +impl AnimationStreamWriter { + /// Create a new AnimationStreamWriter instance + pub fn new(inner: W) -> AnimationStreamWriter { + AnimationStreamWriter { + inner: IldaWriter::new(inner), + finalized: false + } + } + + /// Write a frame into the stream. (Simple version for pseudo streaming) + /// This will set the frame number and total_frames value of the header to 0. + /// To specify these values, use `write_frame_ext` instead. + pub fn write_frame(&mut self, frame: &Frame) -> Result<(), IldaError> { + self.write_frame_ext(frame, 0, 0) + } + + /// Write a frame into the stream. + pub fn write_frame_ext(&mut self, frame: &Frame, number: u16, total_frames: u16) -> Result<(), IldaError> { + let len = frame.points.len(); + + if len > u16::max_value() as usize { + return Err(IldaError::TooManyPoints(len)); + } + + let header = Header::new(Format::TrueColor2d, frame.frame_name.clone(), frame.company_name.clone(), len as u16, number, total_frames, 0); + + self.inner.write(IldaEntry::HeaderEntry(header))?; + + for (i, point) in frame.points.iter().enumerate() { + let ilda_point = TrueColorPoint2d::new(point.x, point.y, point.r, point.g, point.b, i + 1 == len, point.is_blank); + self.inner.write(IldaEntry::TcPoint2dEntry(ilda_point))? + } + + Ok(()) + } + + fn write_finishing_header(&mut self) -> Result<(), IldaError> { + let header = Header::new(Format::TrueColor2d, None, None, 0, 0, 0, 0); + + self.inner.write(IldaEntry::HeaderEntry(header)) + } + + /// Consume the writer and finish the ILDA file by writing the final header. + /// If this method is not called, Drop will take care of writing the final header. + /// Therefore calling this function is optional but recommended to catch possible errors. + pub fn finalize(mut self) -> Result<(), IldaError> { + self.finalized = true; + self.write_finishing_header() + } +} + +impl Drop for AnimationStreamWriter { + fn drop(&mut self) { + if !self.finalized { + self.write_finishing_header().unwrap(); + } + } +} + +/// Iterator over animation Frame items. Panics on error. +pub struct AnimationFrameIterator<'a>(IldaEntryIteratorWithError<'a>); + +/// Iterator over animation Result items. +pub struct AnimationFrameIteratorWithError<'a>(IldaEntryIteratorWithError<'a>); + impl Animation { + /// Creates a new animation from frames. + pub fn new(frames: Vec) -> Animation { + Animation { frames } + } + /// Read an animation from an ILDA file. /// /// ``` @@ -34,22 +118,59 @@ impl Animation { /// let filename = "examples/files/ildatest.ild"; /// let animation = Animation::read_file(filename).unwrap(); /// - /// assert_eq!(2, animation.frame_count()); + /// assert_eq!(1, animation.frame_count()); /// ``` pub fn read_file(filename: &str) -> Result { - let entries = read_file(filename)?; - Animation::process_entries(entries) + let mut file = File::open(filename)?; + let iter = Self::stream_with_error(&mut file); + let result: Result, IldaError> = iter.collect(); + Ok(Animation { frames: result? }) } /// Read an animation from raw ILDA bytes. pub fn read_bytes(ilda_bytes: &[u8]) -> Result { - let entries = read_bytes(ilda_bytes)?; - Animation::process_entries(entries) + let mut cursor = Cursor::new(ilda_bytes); + let iter = Self::stream_with_error(&mut cursor); + let result: Result, IldaError> = iter.collect(); + Ok(Animation { frames: result? }) + } + + /// Stream Animation Frames from a reader + pub fn stream(ilda_reader: &mut Read) -> AnimationFrameIterator { + let parser_iter = stream_with_error(ilda_reader); + AnimationFrameIterator(parser_iter) + } + + /// Stream Animation Frames (with error handling) from a reader + pub fn stream_with_error(ilda_reader: &mut Read) -> AnimationFrameIteratorWithError { + let parser_iter = stream_with_error(ilda_reader); + AnimationFrameIteratorWithError(parser_iter) + } + + /// Write Animation to a file + pub fn write_file(&self, filename: &str) -> Result<(), IldaError> { + let mut file = File::create(filename)?; + self.write(&mut file) + } + + /// Write Animation to Writer + pub fn write(&self, writer: T) -> Result<(), IldaError> where T: Write { + let mut streamer = AnimationStreamWriter::new(writer); + let len = self.frames.len(); + if len > u16::max_value() as usize { + return Err(IldaError::TooManyFrames(len)); + } + + for (i, frame) in self.frames.iter().enumerate() { + streamer.write_frame_ext(frame, i as u16, len as u16)?; + } + + streamer.finalize() } /// Get an frame iterator for the animation. - pub fn into_frame_iter<'a>(&'a self) -> AnimationFrameIterator<'a> { - AnimationFrameIterator { animation: self, index: 0 } + pub fn into_frame_iter(self) -> IntoIter { + self.frames.into_iter() } /// Get a point iterator for the animation, which will iterate over all points @@ -77,58 +198,65 @@ impl Animation { pub fn get_frame(&self, position: usize) -> Option<&Frame> { self.frames.get(position) } +} - fn process_entries(entries: Vec) -> Result { - let mut frames = Vec::new(); - let mut current_frame = None; - - // NB: This does not check for format consistency. - // Frame-type / point-type mismatch is allowed. - for entry in entries { - match entry { - IldaEntry::HeaderEntry(mut header) => { - if current_frame.is_some() { - let frame = current_frame.take().unwrap(); - frames.push(frame); - } - - current_frame = Some(Frame { - points: Vec::new(), - frame_name: header.name.take(), - company_name: header.company_name.take(), - }); - - continue; - }, - _ => {}, - } +fn next_frame(iter: &mut IldaEntryIteratorWithError) -> Result, IldaError> { + let entry = match iter.next().transpose()? { + Some(entry) => entry, + None => return Ok(None), // no more data + }; - let mut frame = match current_frame { - // TODO: Better error type / message - None => return Err(IldaError::InvalidData), - Some(ref mut frame) => frame, - }; + let mut points_to_read; - let point = ilda_entry_to_point(entry)?; - frame.points.push(point); + let mut frame = match entry { + IldaEntry::HeaderEntry(mut header) => { + points_to_read = header.record_count; + Frame { + points: Vec::new(), + frame_name: header.name.take(), + company_name: header.company_name.take(), + } } + _ => return Err(IldaError::InvalidData), // expected header + }; - // Take the last frame. - match current_frame.take() { - None => {}, - Some(frame) => frames.push(frame), - } + if points_to_read == 0 { + // EOF header + return Ok(None); + } - if frames.is_empty() { - return Err(IldaError::NoData); - } + while points_to_read > 0 { + let entry = match iter.next().transpose()? { + Some(entry) => entry, + None => return Err(IldaError::InvalidData), // premature end of stream + }; - Ok(Animation { - frames: frames, - }) + points_to_read = points_to_read - 1; + + let point = ilda_entry_to_point(entry)?; + frame.points.push(point); + } + + Ok(Some(frame)) +} + +impl<'a> Iterator for AnimationFrameIterator<'a> { + type Item = Frame; + + fn next(&mut self) -> Option { + next_frame(&mut self.0).unwrap() } } +impl<'a> Iterator for AnimationFrameIteratorWithError<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + next_frame(&mut self.0).transpose() + } +} + + /// Convert an IldaEntry containing a point into a respective animation point. /// Color palettes and headers will return errors. pub fn ilda_entry_to_point(entry: IldaEntry) -> Result { @@ -187,6 +315,11 @@ pub fn ilda_entry_to_point(entry: IldaEntry) -> Result { } impl Frame { + /// Create a new frame from points. + pub fn new(points: Vec, frame_name: Option, company_name: Option) -> Frame { + Frame { points, frame_name, company_name } + } + /// Get a reference to the points in the frame. pub fn get_points(&self) -> &Vec { &self.points @@ -203,13 +336,8 @@ impl Frame { } } -/// Iterator over all the frames in the animation. -pub struct AnimationFrameIterator<'a> { - animation: &'a Animation, - index: usize, -} - /// Iterator over all the points from all of the frames in the animation. +#[derive(Clone, Copy)] pub struct AnimationPointIterator<'a> { animation: &'a Animation, current_frame: Option<&'a Frame>, // Iteration ends when None. @@ -258,16 +386,6 @@ impl<'a> IntoIterator for &'a Frame { } } -impl<'a> Iterator for AnimationFrameIterator<'a> { - type Item = &'a Frame; - - fn next(&mut self) -> Option { - let item = self.animation.get_frame(self.index); - self.index += 1; - item - } -} - impl<'a> Iterator for AnimationPointIterator<'a> { type Item = &'a SimplePoint; diff --git a/src/data.rs b/src/data.rs index 5e7e62e..25b90ae 100644 --- a/src/data.rs +++ b/src/data.rs @@ -16,11 +16,12 @@ pub const INDEXED_3D_DATA_SIZE: usize = 8; pub const TRUE_COLOR_2D_DATA_SIZE: usize = 8; /// Size of an ILDA True Color 3D point data section in bytes. pub const TRUE_COLOR_3D_DATA_SIZE: usize = 10; +/// The ILDA format header; "ILDA" in ASCII. +pub const ILDA_HEADER: [u8; 4] = [73u8, 76u8, 68u8, 65u8]; /// The payload encoding formats currently supported. #[allow(missing_docs)] pub enum Format { - Unknown, ColorPalette, Indexed2d, Indexed3d, @@ -65,18 +66,51 @@ pub struct Header { impl Header { /// Returns the format of the header. - pub fn get_format(&self) -> Format { - match self.format_code { + pub fn get_format(&self) -> Result { + Ok(match self.format_code { 0u8 => Format::Indexed3d, 1u8 => Format::Indexed2d, 2u8 => Format::ColorPalette, 4u8 => Format::TrueColor3d, 5u8 => Format::TrueColor2d, - _ => Format::Unknown, + _ => return Err(IldaError::InvalidHeader) + }) + } + + /// Create new Header + pub fn new( + format: Format, + name: Option, + company_name: Option, + record_count: u16, + number: u16, + total_frames: u16, + projector_number: u8 + ) -> Header { + Header { + reserved: 0, + format_code: match format { + Format::Indexed3d => 0u8, + Format::Indexed2d => 1u8, + Format::ColorPalette => 2u8, + Format::TrueColor3d => 4u8, + Format::TrueColor2d => 5u8 + }, + name, + company_name, + record_count, + number, + total_frames, + projector_number, + reserved_2: 0 } } } +fn build_status_code(is_blank: bool, is_last: bool) -> u8 { + (if is_last { 1 << 7 } else { 0 } | if is_blank { 1 << 6 } else { 0 }) +} + /// 3D Coordinates with Indexed Color (format 0) #[derive(Clone, Debug, Default)] pub struct IndexedPoint3d { @@ -122,6 +156,17 @@ impl IndexedPoint3d { // 7th high order bit is the blanking bit. self.status_code & 64 == 64 } + + /// Create a new IndexedPoint3d point. + pub fn new(x: i16, y: i16, z: i16, color_index: u8, is_last: bool, is_blank: bool) -> IndexedPoint3d { + IndexedPoint3d { + x, + y, + z, + status_code: build_status_code(is_blank, is_last), + color_index + } + } } /// 2D Coordinates with Indexed Color (format 1) @@ -166,6 +211,16 @@ impl IndexedPoint2d { // 7th high order bit is the blanking bit. self.status_code & 64 == 64 } + + /// Create a new IndexedPoint2d point. + pub fn new(x: i16, y: i16, color_index: u8, is_last: bool, is_blank: bool) -> IndexedPoint2d { + IndexedPoint2d { + x, + y, + status_code: build_status_code(is_blank, is_last), + color_index + } + } } /// Color Palette (format 2) @@ -200,6 +255,15 @@ impl ColorPalette { Ok(out) } + + /// Create a new ColorPalette point. + pub fn new(r: u8, g: u8, b: u8) -> ColorPalette { + ColorPalette { + r, + g, + b + } + } } /// 3D Coordinates with True Color (format 4) @@ -253,6 +317,19 @@ impl TrueColorPoint3d { // 7th high order bit is the blanking bit. self.status_code & 64 == 64 } + + /// Create a new TrueColorPoint2d point. + pub fn new(x: i16, y: i16, z: i16, r: u8, g: u8, b: u8, is_last: bool, is_blank: bool) -> TrueColorPoint3d { + TrueColorPoint3d { + x, + y, + z, + status_code: build_status_code(is_blank, is_last), + r, + g, + b + } + } } /// 3D Coordinates with True Color (format 5) @@ -303,6 +380,18 @@ impl TrueColorPoint2d { // 7th high order bit is the blanking bit. self.status_code & 64 == 64 } + + /// Create a new TrueColorPoint2d point. + pub fn new(x: i16, y: i16, r: u8, g: u8, b: u8, is_last: bool, is_blank: bool) -> TrueColorPoint2d { + TrueColorPoint2d { + x, + y, + status_code: build_status_code(is_blank, is_last), + r, + g, + b + } + } } /// ILDA header and data records. diff --git a/src/error.rs b/src/error.rs index ac7e9ce..fd02eab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,12 @@ pub enum IldaError { /// Problems were encountered while reading the ILDA data. InvalidData, + /// Too many points in a frame + TooManyPoints(usize), + + /// Too many frames in an animation + TooManyFrames(usize), + /// Problems were encountered while reading the ILDA data, specifically with /// an invalid ILDA header section. InvalidHeader, @@ -40,6 +46,8 @@ impl Error for IldaError { IldaError::InvalidHeader => "InvalidHeader", IldaError::IoError { .. } => "IoError", IldaError::NoData => "NoData", + IldaError::TooManyPoints(_) => "TooManyPoints", + IldaError::TooManyFrames(_) => "TooManyFrames", IldaError::Unsupported => "Unsupported", } } diff --git a/src/lib.rs b/src/lib.rs index 2403dbe..a139257 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub mod animation; pub mod data; pub mod limit; pub mod parser; +pub mod writer; mod color; mod error; diff --git a/src/parser.rs b/src/parser.rs index 0ef4825..1c10feb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,6 +3,7 @@ //! Low level parsing that returns headers and data fields closer to the //! underlying ILDA data model. +use data::ILDA_HEADER; use data::COLOR_PALETTE_SIZE; use data::ColorPalette; use data::Format; @@ -20,103 +21,135 @@ use data::TrueColorPoint3d; use error::IldaError; use std::fs::File; use std::io::Read; - -/// The ILDA format header; "ILDA" in ASCII. -const ILDA_HEADER : [u8; 4] = [73u8, 76u8, 68u8, 65u8]; +use std::io::Cursor; +use std::io::Error; +use std::io::ErrorKind; /// Read ILDA data from a file. pub fn read_file(filename: &str) -> Result, IldaError> { - let mut contents = Vec::new(); let mut file = File::open(filename)?; - let _r = file.read_to_end(&mut contents); - read_bytes(&contents[..]) + stream_with_error(&mut file).collect() } /// Read ILDA data from raw bytes. pub fn read_bytes(ilda_bytes: &[u8]) -> Result, IldaError> { - if ilda_bytes.len() < 32 { - return Err(IldaError::FileTooSmall); + let mut cursor = Cursor::new(ilda_bytes); + stream_with_error(&mut cursor).collect() +} + +/// Stream ILDA entries from a reader. +/// The iterator will panic if it encounters an error. +pub fn stream<'a>(reader: &'a mut Read) -> IldaEntryIterator<'a> { + IldaEntryIterator(IldaEntryIteratorData::new(reader)) +} + +/// Stream ILDA entries (with error handling) from a reader. +pub fn stream_with_error<'a>(reader: &'a mut Read) -> IldaEntryIteratorWithError<'a> { + IldaEntryIteratorWithError(IldaEntryIteratorData::new(reader)) +} + +/// Data for the Iterators. +struct IldaEntryIteratorData<'a> { + source: &'a mut Read, + current_format: Option, + frames_to_read: u16 +} + +/// Iterator over IldaEntry items. Panics on error. +pub struct IldaEntryIterator<'a>(IldaEntryIteratorData<'a>); + +/// Iterator over Result items. +pub struct IldaEntryIteratorWithError<'a>(IldaEntryIteratorData<'a>); + +impl<'a> Iterator for IldaEntryIterator<'a> { + type Item = IldaEntry; + + fn next(&mut self) -> Option { + self.0._next().unwrap() + } +} + +impl<'a> Iterator for IldaEntryIteratorWithError<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + return self.0._next().transpose() } +} - enum NextRead { Header, I3d, I2d, Color, Tc3d, Tc2d }; - - let mut vec = Vec::new(); - let mut i : usize = 0; - let mut next_read = NextRead::Header; - let mut frames_to_read = 0; - - // TODO(echelon): This isn't very concise. - while i < ilda_bytes.len() { - match next_read { - NextRead::Header => { - let header = read_header(&ilda_bytes[i .. i + HEADER_SIZE]) - .map_err(|_| IldaError::InvalidHeader)?; - next_read = match header.get_format() { - Format::Indexed3d => NextRead::I3d, - Format::Indexed2d => NextRead::I2d, - Format::ColorPalette => NextRead::Color, - Format::TrueColor3d => NextRead::Tc3d, - Format::TrueColor2d => NextRead::Tc2d, - Format::Unknown => return Err(IldaError::InvalidHeader), - }; - frames_to_read = header.record_count; - vec.push(IldaEntry::HeaderEntry(header)); - i += HEADER_SIZE; - }, - NextRead::I3d => { - let end = INDEXED_3D_DATA_SIZE * frames_to_read as usize; - let points = IndexedPoint3d::read_bytes(&ilda_bytes[i .. i + end])?; - let mut entries = points.iter() - .map(|x| IldaEntry::IdxPoint3dEntry(x.clone())) - .collect(); - vec.append(&mut entries); - next_read = NextRead::Header; - i += end; +impl<'a> IldaEntryIteratorData<'a> { + fn new(source: &'a mut Read) -> IldaEntryIteratorData<'a> { + IldaEntryIteratorData { + source, + current_format: None, + frames_to_read: 0 + } + } + + fn _next(&mut self) -> Result, IldaError> { + if self.frames_to_read == 0 { + // currentry no frames are expected to follow the stream, read new header + let mut buffer = [0; HEADER_SIZE]; + + // The following logic behaves like read_exact but return Ok(None) if it immediately encounters EOF + let mut bytes_read = 0; + while bytes_read < HEADER_SIZE { + match self.source.read(&mut buffer[bytes_read..HEADER_SIZE]) { + Ok(0) => return if bytes_read == 0 { + Ok(None) + } + else { + Err(IldaError::IoError { cause: Error::new(ErrorKind::UnexpectedEof, "unexpected end of header") }) + }, + Ok(size) => bytes_read += size, + Err(cause) => return Err(IldaError::IoError { cause }) + } + } + + let header = read_header( & buffer)?; + + self.frames_to_read = header.record_count; + self.current_format = Some(header.get_format()?); + return Ok(Some(IldaEntry::HeaderEntry(header))) + } + + let entry = match self.current_format.as_ref().unwrap() { + Format::Indexed3d => { + let mut buffer = [0; INDEXED_3D_DATA_SIZE]; + self.source.read_exact( & mut buffer)?; + let point = IndexedPoint3d::read_bytes( &buffer) ?.remove(0); + IldaEntry::IdxPoint3dEntry(point) }, - NextRead::I2d => { - let end = INDEXED_2D_DATA_SIZE * frames_to_read as usize; - let points = IndexedPoint2d::read_bytes(&ilda_bytes[i .. i + end])?; - let mut entries = points.iter() - .map(|x| IldaEntry::IdxPoint2dEntry(x.clone())) - .collect(); - vec.append(&mut entries); - next_read = NextRead::Header; - i += end; + Format::ColorPalette => { + let mut buffer = [0; COLOR_PALETTE_SIZE]; + self.source.read_exact( & mut buffer)?; + let point = ColorPalette::read_bytes( &buffer) ?.remove(0); + IldaEntry::ColorPaletteEntry(point) }, - NextRead::Color => { - let end = COLOR_PALETTE_SIZE * frames_to_read as usize; - let points = ColorPalette::read_bytes(&ilda_bytes[i .. i + end])?; - let mut entries = points.iter() - .map(|x| IldaEntry::ColorPaletteEntry(x.clone())) - .collect(); - vec.append(&mut entries); - next_read = NextRead::Header; - i += end; + Format::Indexed2d => { + let mut buffer = [0; INDEXED_2D_DATA_SIZE]; + self.source.read_exact( & mut buffer)?; + let point = IndexedPoint2d::read_bytes( &buffer) ?.remove(0); + IldaEntry::IdxPoint2dEntry(point) }, - NextRead::Tc3d => { - let end = TRUE_COLOR_3D_DATA_SIZE * frames_to_read as usize; - let points = TrueColorPoint3d::read_bytes(&ilda_bytes[i .. i + end])?; - let mut entries = points.iter() - .map(|x| IldaEntry::TcPoint3dEntry(x.clone())) - .collect(); - vec.append(&mut entries); - next_read = NextRead::Header; - i += end; + Format::TrueColor3d => { + let mut buffer = [0; TRUE_COLOR_3D_DATA_SIZE]; + self.source.read_exact( & mut buffer)?; + let point = TrueColorPoint3d::read_bytes( &buffer) ?.remove(0); + IldaEntry::TcPoint3dEntry(point) }, - NextRead::Tc2d => { - let end = TRUE_COLOR_2D_DATA_SIZE * frames_to_read as usize; - let points = TrueColorPoint2d::read_bytes(&ilda_bytes[i .. i + end])?; - let mut entries = points.iter() - .map(|x| IldaEntry::TcPoint2dEntry(x.clone())) - .collect(); - vec.append(&mut entries); - next_read = NextRead::Header; - i += end; + Format::TrueColor2d => { + let mut buffer = [0; TRUE_COLOR_2D_DATA_SIZE]; + self.source.read_exact( & mut buffer)?; + let point = TrueColorPoint2d::read_bytes( &buffer) ?.remove(0); + IldaEntry::TcPoint2dEntry(point) }, }; - } - Ok(vec) + self.frames_to_read -= 1; + + Ok(Some(entry)) + } } fn read_header(header_bytes: &[u8]) -> Result { diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..35c7703 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,98 @@ +//! Low level writing of ILDA frames. + +use data::IldaEntry; +use data::ILDA_HEADER; +use error::IldaError; +use std::fs::File; +use std::io::{BufWriter, Write}; + +/// A struct that can be used to write IldaEntries to an underlying Write object +pub struct IldaWriter where W: Write { + inner: W, +} + +impl IldaWriter> { + /// Crate an IldaWriter that writes to a file. (Buffered) + pub fn create(filename: &str) -> Result>, IldaError> { + let inner = BufWriter::new(File::create(filename)?); + Ok(IldaWriter::new(inner)) + } +} + +fn u16_be(value: u16) -> [u8; 2] { + [(value >> 8) as u8, (value & 0xFF) as u8] +} + +fn str_8c(value: Option) -> [u8; 8] { + let value = value.as_ref().map_or("", String::as_ref); + let mut arr = [0; 8]; + let bytes = value.as_bytes(); + + for i in 0..bytes.len().min(8) { + arr[i] = bytes[i] + } + + arr +} + +impl IldaWriter where W: Write { + /// Create a new IldaWriter. If writing to a file use `create` instead. + /// Using a buffered writer is highly recommended. + pub fn new(inner: W) -> IldaWriter { + IldaWriter { inner } + } + + /// Write an IldaEntry to the undelying writer. + pub fn write(&mut self, entry: IldaEntry) -> Result<(), IldaError> { + match entry { + IldaEntry::HeaderEntry(header) => { + self.inner.write(&ILDA_HEADER)?; + self.inner.write(&[0, 0, 0, header.format_code])?; + self.inner.write(&str_8c(header.name))?; + self.inner.write(&str_8c(header.company_name))?; + self.inner.write(&u16_be(header.record_count))?; + self.inner.write(&u16_be(header.number))?; + self.inner.write(&u16_be(header.total_frames))?; + self.inner.write(&[header.projector_number])?; + self.inner.write(&[0])?; + } + IldaEntry::TcPoint3dEntry(point) => { + self.inner.write(&u16_be(point.x as u16))?; + self.inner.write(&u16_be(point.y as u16))?; + self.inner.write(&u16_be(point.z as u16))?; + self.inner.write(&[point.status_code])?; + self.inner.write(&[point.b])?; + self.inner.write(&[point.g])?; + self.inner.write(&[point.r])?; + } + IldaEntry::TcPoint2dEntry(point) => { + self.inner.write(&u16_be(point.x as u16))?; + self.inner.write(&u16_be(point.y as u16))?; + self.inner.write(&[point.status_code])?; + self.inner.write(&[point.b])?; + self.inner.write(&[point.g])?; + self.inner.write(&[point.r])?; + } + IldaEntry::ColorPaletteEntry(palette) => { + self.inner.write(&[palette.r])?; + self.inner.write(&[palette.g])?; + self.inner.write(&[palette.b])?; + } + IldaEntry::IdxPoint3dEntry(point) => { + self.inner.write(&u16_be(point.x as u16))?; + self.inner.write(&u16_be(point.y as u16))?; + self.inner.write(&u16_be(point.z as u16))?; + self.inner.write(&[point.status_code])?; + self.inner.write(&[point.color_index])?; + } + IldaEntry::IdxPoint2dEntry(point) => { + self.inner.write(&u16_be(point.x as u16))?; + self.inner.write(&u16_be(point.y as u16))?; + self.inner.write(&[point.status_code])?; + self.inner.write(&[point.color_index])?; + } + }; + + Ok(()) + } +}