From 3d86a78b06b6a3c5d2ad1957c8db0c89b21f30d2 Mon Sep 17 00:00:00 2001 From: ZeWaka Date: Sat, 24 Apr 2021 18:47:49 -0700 Subject: [PATCH] Cargo fmt --- rustfmt.toml | 2 + src/dmi.rs | 413 ++++++++++++------------- src/dmi/icon.rs | 795 ++++++++++++++++++++++++------------------------ src/dmi/iend.rs | 288 +++++++++--------- src/dmi/ztxt.rs | 661 ++++++++++++++++++++-------------------- src/tests.rs | 26 +- 6 files changed, 1110 insertions(+), 1075 deletions(-) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..fb2338f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +tab_spaces = 2 +hard_tabs = true diff --git a/src/dmi.rs b/src/dmi.rs index 61c5c0a..e3f0574 100644 --- a/src/dmi.rs +++ b/src/dmi.rs @@ -1,204 +1,209 @@ -pub mod chunk; -pub mod crc; -pub mod error; -pub mod icon; -pub mod iend; -pub mod ztxt; - -use std::convert::TryFrom; -use std::io::prelude::*; - -/// The PNG magic header -pub const PNG_HEADER: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; - -#[derive(Clone, Eq, PartialEq, Debug, Default)] -pub struct RawDmi { - pub header: [u8; 8], - pub chunk_ihdr: chunk::RawGenericChunk, - pub chunk_ztxt: Option, - pub chunk_plte: Option, - pub other_chunks: Option>, - pub chunks_idat: Vec, - pub chunk_iend: iend::RawIendChunk, -} - -impl RawDmi { - pub fn new() -> RawDmi { - RawDmi { - ..Default::default() - } - } - - pub fn load(mut reader: R) -> Result { - let mut dmi_bytes = Vec::new(); - reader.read_to_end(&mut dmi_bytes)?; - // 8 bytes for the PNG file signature. - // 12 + 13 bytes for the IHDR chunk. - // 12 for the IDAT chunk. - // 12 + 3 for the zTXt chunk. - // 12 for the IEND chunk. - - // Total minimum size for a DMI file: 72 bytes. - - if dmi_bytes.len() < 72 { - return Err(error::DmiError::Generic(format!("Failed to load DMI. Supplied reader contained size of {} bytes, lower than the required 72.", dmi_bytes.len()))); - }; - - let header = &dmi_bytes[0..8]; - if dmi_bytes[0..8] != PNG_HEADER { - return Err(error::DmiError::Generic(format!( - "PNG header mismatch (expected {:#?}, found {:#?})", - PNG_HEADER, header - ))); - }; - let header = PNG_HEADER; - let mut chunk_ihdr = None; - let mut chunk_ztxt = None; - let mut chunk_plte = None; - let mut chunks_idat = vec![]; - let chunk_iend; - let mut other_chunks = vec![]; - - // Index starts after the PNG header. - let mut index = 8; - - loop { - if index + 12 > dmi_bytes.len() { - return Err(error::DmiError::Generic( - "Failed to load DMI. Buffer end reached without finding an IEND chunk." - .to_string(), - )); - } - - let chunk_data_length = u32::from_be_bytes([ - dmi_bytes[index], - dmi_bytes[index + 1], - dmi_bytes[index + 2], - dmi_bytes[index + 3], - ]) as usize; - - // 12 minimum necessary bytes from the chunk plus the data length. - let chunk_bytes = dmi_bytes[index..(index + 12 + chunk_data_length)].to_vec(); - let raw_chunk = chunk::RawGenericChunk::load(&mut &*chunk_bytes)?; - index += 12 + chunk_data_length; - - match &raw_chunk.chunk_type { - b"IHDR" => chunk_ihdr = Some(raw_chunk), - b"zTXt" => chunk_ztxt = Some(ztxt::RawZtxtChunk::try_from(raw_chunk)?), - b"PLTE" => chunk_plte = Some(raw_chunk), - b"IDAT" => chunks_idat.push(raw_chunk), - b"IEND" => { - chunk_iend = Some(iend::RawIendChunk::try_from(raw_chunk)?); - break; - } - _ => other_chunks.push(raw_chunk), - } - } - if chunk_ihdr == None { - return Err(error::DmiError::Generic(format!( - "Failed to load DMI. Buffer end reached without finding an IHDR chunk." - ))); - }; - if chunks_idat.len() == 0 { - return Err(error::DmiError::Generic(format!( - "Failed to load DMI. Buffer end reached without finding an IDAT chunk." - ))); - } - let other_chunks = match other_chunks.len() { - 0 => None, - _ => Some(other_chunks), - }; - let chunk_ihdr = chunk_ihdr.unwrap(); - let chunk_iend = chunk_iend.unwrap(); - - Ok(RawDmi { - header, - chunk_ihdr, - chunk_ztxt, - chunk_plte, - other_chunks, - chunks_idat, - chunk_iend, - }) - } - - pub fn save(&self, mut writter: &mut W) -> Result { - let bytes_written = writter.write(&self.header)?; - let mut total_bytes_written = bytes_written; - if bytes_written < 8 { - return Err(error::DmiError::Generic(format!( - "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = self.chunk_ihdr.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(self.chunk_ihdr.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!( - "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - match &self.chunk_ztxt { - Some(chunk_ztxt) => { - let bytes_written = chunk_ztxt.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(chunk_ztxt.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!("Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", total_bytes_written))); - }; - } - None => (), - }; - - match &self.chunk_plte { - Some(chunk_plte) => { - let bytes_written = chunk_plte.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(chunk_plte.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!("Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", total_bytes_written))); - }; - } - None => (), - }; - - match &self.other_chunks { - Some(other_chunks) => { - for chunk in other_chunks { - let bytes_written = chunk.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(chunk.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!( - "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - } - } - None => (), - } - - for chunk in &self.chunks_idat { - let bytes_written = chunk.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(chunk.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!( - "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - } - - let bytes_written = self.chunk_iend.save(&mut writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(self.chunk_iend.data_length) as usize + 12 { - return Err(error::DmiError::Generic(format!( - "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - Ok(total_bytes_written) - } -} +pub mod chunk; +pub mod crc; +pub mod error; +pub mod icon; +pub mod iend; +pub mod ztxt; + +use std::convert::TryFrom; +use std::io::prelude::*; + +/// The PNG magic header +pub const PNG_HEADER: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10]; + +#[derive(Clone, Eq, PartialEq, Debug, Default)] +pub struct RawDmi { + pub header: [u8; 8], + pub chunk_ihdr: chunk::RawGenericChunk, + pub chunk_ztxt: Option, + pub chunk_plte: Option, + pub other_chunks: Option>, + pub chunks_idat: Vec, + pub chunk_iend: iend::RawIendChunk, +} + +impl RawDmi { + pub fn new() -> RawDmi { + RawDmi { + ..Default::default() + } + } + + pub fn load(mut reader: R) -> Result { + let mut dmi_bytes = Vec::new(); + reader.read_to_end(&mut dmi_bytes)?; + // 8 bytes for the PNG file signature. + // 12 + 13 bytes for the IHDR chunk. + // 12 for the IDAT chunk. + // 12 + 3 for the zTXt chunk. + // 12 for the IEND chunk. + + // Total minimum size for a DMI file: 72 bytes. + + if dmi_bytes.len() < 72 { + return Err(error::DmiError::Generic(format!("Failed to load DMI. Supplied reader contained size of {} bytes, lower than the required 72.", dmi_bytes.len()))); + }; + + let header = &dmi_bytes[0..8]; + if dmi_bytes[0..8] != PNG_HEADER { + return Err(error::DmiError::Generic(format!( + "PNG header mismatch (expected {:#?}, found {:#?})", + PNG_HEADER, header + ))); + }; + let header = PNG_HEADER; + let mut chunk_ihdr = None; + let mut chunk_ztxt = None; + let mut chunk_plte = None; + let mut chunks_idat = vec![]; + let chunk_iend; + let mut other_chunks = vec![]; + + // Index starts after the PNG header. + let mut index = 8; + + loop { + if index + 12 > dmi_bytes.len() { + return Err(error::DmiError::Generic( + "Failed to load DMI. Buffer end reached without finding an IEND chunk.".to_string(), + )); + } + + let chunk_data_length = u32::from_be_bytes([ + dmi_bytes[index], + dmi_bytes[index + 1], + dmi_bytes[index + 2], + dmi_bytes[index + 3], + ]) as usize; + + // 12 minimum necessary bytes from the chunk plus the data length. + let chunk_bytes = dmi_bytes[index..(index + 12 + chunk_data_length)].to_vec(); + let raw_chunk = chunk::RawGenericChunk::load(&mut &*chunk_bytes)?; + index += 12 + chunk_data_length; + + match &raw_chunk.chunk_type { + b"IHDR" => chunk_ihdr = Some(raw_chunk), + b"zTXt" => chunk_ztxt = Some(ztxt::RawZtxtChunk::try_from(raw_chunk)?), + b"PLTE" => chunk_plte = Some(raw_chunk), + b"IDAT" => chunks_idat.push(raw_chunk), + b"IEND" => { + chunk_iend = Some(iend::RawIendChunk::try_from(raw_chunk)?); + break; + } + _ => other_chunks.push(raw_chunk), + } + } + if chunk_ihdr == None { + return Err(error::DmiError::Generic(format!( + "Failed to load DMI. Buffer end reached without finding an IHDR chunk." + ))); + }; + if chunks_idat.len() == 0 { + return Err(error::DmiError::Generic(format!( + "Failed to load DMI. Buffer end reached without finding an IDAT chunk." + ))); + } + let other_chunks = match other_chunks.len() { + 0 => None, + _ => Some(other_chunks), + }; + let chunk_ihdr = chunk_ihdr.unwrap(); + let chunk_iend = chunk_iend.unwrap(); + + Ok(RawDmi { + header, + chunk_ihdr, + chunk_ztxt, + chunk_plte, + other_chunks, + chunks_idat, + chunk_iend, + }) + } + + pub fn save(&self, mut writter: &mut W) -> Result { + let bytes_written = writter.write(&self.header)?; + let mut total_bytes_written = bytes_written; + if bytes_written < 8 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = self.chunk_ihdr.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(self.chunk_ihdr.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + match &self.chunk_ztxt { + Some(chunk_ztxt) => { + let bytes_written = chunk_ztxt.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(chunk_ztxt.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + } + None => (), + }; + + match &self.chunk_plte { + Some(chunk_plte) => { + let bytes_written = chunk_plte.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(chunk_plte.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + } + None => (), + }; + + match &self.other_chunks { + Some(other_chunks) => { + for chunk in other_chunks { + let bytes_written = chunk.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(chunk.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + } + } + None => (), + } + + for chunk in &self.chunks_idat { + let bytes_written = chunk.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(chunk.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + } + + let bytes_written = self.chunk_iend.save(&mut writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(self.chunk_iend.data_length) as usize + 12 { + return Err(error::DmiError::Generic(format!( + "Failed to save DMI. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + Ok(total_bytes_written) + } +} diff --git a/src/dmi/icon.rs b/src/dmi/icon.rs index a62a67f..c39e637 100644 --- a/src/dmi/icon.rs +++ b/src/dmi/icon.rs @@ -1,397 +1,398 @@ -use super::error; -use super::ztxt; -use super::RawDmi; -use image; -use image::imageops; -use image::GenericImageView; -use std::collections::HashMap; -use std::io::prelude::*; - -#[derive(Clone, Default)] -pub struct Icon { - pub version: String, - pub width: u32, - pub height: u32, - pub states: Vec, -} - -impl Icon { - pub fn load(reader: R) -> Result { - let raw_dmi = RawDmi::load(reader)?; - let chunk_ztxt = match &raw_dmi.chunk_ztxt { - Some(chunk) => chunk.clone(), - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no zTXt chunk found." - ))) - } - }; - let decompressed_text = chunk_ztxt.data.decode()?; - let decompressed_text = String::from_utf8(decompressed_text)?; - let mut decompressed_text = decompressed_text.lines(); - - let current_line = decompressed_text.next(); - if current_line != Some("# BEGIN DMI") { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no DMI header found. Beginning: {:#?}", - current_line - ))); - }; - - let current_line = match decompressed_text.next() { - Some(thing) => thing, - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no version header found." - ))) - } - }; - let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); - if split_version.len() != 2 || split_version[0] != "version" { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper version header found: {:#?}", - split_version - ))); - }; - let version = split_version[1].to_string(); - - let current_line = match decompressed_text.next() { - Some(thing) => thing, - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no width found." - ))) - } - }; - let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); - if split_version.len() != 2 || split_version[0] != "\twidth" { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper width found: {:#?}", - split_version - ))); - }; - let width = split_version[1].parse::()?; - - let current_line = match decompressed_text.next() { - Some(thing) => thing, - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no height found." - ))) - } - }; - let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); - if split_version.len() != 2 || split_version[0] != "\theight" { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper height found: {:#?}", - split_version - ))); - }; - let height = split_version[1].parse::()?; - - if width == 0 || height == 0 { - return Err(error::DmiError::Generic(format!( - "Error loading icon: invalid width ({}) / height ({}) values.", - width, height - ))); - }; - - // Image time. - let mut reader = vec![]; - raw_dmi.save(&mut reader)?; - let base_image = image::load_from_memory_with_format(&reader, image::ImageFormat::Png)?; - - let dimensions = base_image.dimensions(); - let img_width = dimensions.0; - let img_height = dimensions.1; - - if img_width == 0 || img_height == 0 || img_width % width != 0 || img_height % height != 0 { - return Err(error::DmiError::Generic(format!("Error loading icon: invalid image width ({}) / height ({}) values. Missmatch with metadata width ({}) / height ({}).", img_width, img_height, width, height))); - }; - - let width_in_states = img_width / width; - let height_in_states = img_height / height; - let max_possible_states = width_in_states * height_in_states; - - let mut index = 0; - - let mut current_line = match decompressed_text.next() { - Some(thing) => thing, - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no DMI trailer nor states found." - ))) - } - }; - - let mut states = vec![]; - - loop { - if current_line.contains("# END DMI") { - break; - }; - - let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); - if split_version.len() != 2 || split_version[0] != "state" { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper state found: {:#?}", - split_version - ))); - }; - - let name = split_version[1].as_bytes(); - if !name.starts_with(&[b'\"']) || !name.ends_with(&[b'\"']) { - return Err(error::DmiError::Generic(format!("Error loading icon: invalid name icon_state found in metadata, should be preceded and succeeded by double-quotes (\"): {:#?}", name))); - }; - let name = match name.len() { - 0 | 1 => return Err(error::DmiError::Generic(format!("Error loading icon: invalid name icon_state found in metadata, improper size: {:#?}", name))), - 2 => String::new(), //Only the quotes, empty name otherwise. - length @ _ => String::from_utf8(name[1..(length - 1)].to_vec())?, //Hacky way to trim. Blame the cool methods being nightly experimental. - }; - - let mut dirs = None; - let mut frames = None; - let mut delay = None; - let mut loop_flag = None; - let mut rewind = None; - let mut movement = None; - let mut hotspot = None; - let mut unknown_settings = None; - - loop { - current_line = match decompressed_text.next() { - Some(thing) => thing, - None => { - return Err(error::DmiError::Generic(format!( - "Error loading icon: no DMI trailer found." - ))) - } - }; - - if current_line.contains("# END DMI") || current_line.contains("state = \"") { - break; - }; - let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); - if split_version.len() != 2 { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper state found: {:#?}", - split_version - ))); - }; - - match split_version[0] { - "\tdirs" => dirs = Some(split_version[1].parse::()?), - "\tframes" => frames = Some(split_version[1].parse::()?), - "\tdelay" => { - let mut delay_vector = vec![]; - let text_delays = split_version[1].split_terminator(","); - for text_entry in text_delays { - delay_vector.push(text_entry.parse::()?); - } - delay = Some(delay_vector); - } - "\tloop" => loop_flag = Some(split_version[1].parse::()?), - "\trewind" => rewind = Some(split_version[1].parse::()?), - "\tmovement" => movement = Some(split_version[1].parse::()?), - "\thotspot" => { - let text_coordinates: Vec<&str> = - split_version[1].split_terminator(",").collect(); - if text_coordinates.len() != 3 { - return Err(error::DmiError::Generic(format!( - "Error loading icon: improper hotspot found: {:#?}", - split_version - ))); - }; - hotspot = Some([ - text_coordinates[0].parse::()?, - text_coordinates[1].parse::()?, - text_coordinates[2].parse::()?, - ]); - } - _ => { - unknown_settings = match unknown_settings { - None => { - let mut new_map = HashMap::new(); - new_map.insert( - split_version[0].to_string(), - split_version[1].to_string(), - ); - Some(new_map) - } - Some(mut thing) => { - thing.insert( - split_version[0].to_string(), - split_version[1].to_string(), - ); - Some(thing) - } - }; - } - }; - } - - if dirs == None || frames == None { - return Err(error::DmiError::Generic(format!("Error loading icon: state lacks essential settings. dirs: {:#?}. frames: {:#?}.", dirs, frames))); - }; - let dirs = dirs.unwrap(); - let frames = frames.unwrap(); - - if index + (dirs as u32 * frames) > max_possible_states { - return Err(error::DmiError::Generic(format!("Error loading icon: metadata settings exceeded the maximum number of states possible ({}).", max_possible_states))); - }; - - let mut images = vec![]; - - for _frame in 0..frames { - for _dir in 0..dirs { - let x = (index % width_in_states) * width; - //This operation rounds towards zero, truncating any fractional part of the exact result, essentially a floor() function. - let y = (index / width_in_states) * height; - images.push(base_image.crop_imm(x, y, width, height)); - index += 1; - } - } - - states.push(IconState { - name, - dirs, - frames, - images, - delay, - loop_flag, - rewind, - movement, - hotspot, - unknown_settings, - }); - } - - Ok(Icon { - version, - width, - height, - states, - }) - } - - pub fn save(&self, mut writter: &mut W) -> Result { - let mut sprites = vec![]; - let mut signature = format!( - "# BEGIN DMI\nversion = {}\n\twidth = {}\n\theight = {}\n", - self.version, self.width, self.height - ); - - for icon_state in &self.states { - if icon_state.images.len() as u32 != icon_state.dirs as u32 * icon_state.frames { - return Err(error::DmiError::Generic(format!("Error saving Icon: number of images ({}) differs from the stated metadata. Dirs: {}. Frames: {}. Name: \"{}\".", icon_state.images.len(), icon_state.dirs, icon_state.frames, icon_state.name))); - }; - - signature.push_str(&format!( - "state = \"{}\"\n\tdirs = {}\n\tframes = {}\n", - icon_state.name, icon_state.dirs, icon_state.frames - )); - - if icon_state.frames > 1 { - match &icon_state.delay { - Some(delay) => { - if delay.len() as u32 != icon_state.frames { - return Err(error::DmiError::Generic(format!("Error saving Icon: number of frames ({}) differs from the delay entry ({:3?}). Name: \"{}\".", icon_state.frames, delay, icon_state.name))) - }; - let delay: Vec= delay.iter().map(|&c| c.to_string()).collect(); - signature.push_str(&format!("\tdelay = {}\n", delay.join(","))); - }, - None => return Err(error::DmiError::Generic(format!("Error saving Icon: number of frames ({}) larger than one without a delay entry in icon state of name \"{}\".", icon_state.frames, icon_state.name))) - }; - match icon_state.loop_flag { - Some(flag) => signature.push_str(&format!("\tloop = {}\n", flag)), - None => (), - }; - match icon_state.rewind { - Some(flag) => signature.push_str(&format!("\trewind = {}\n", flag)), - None => (), - }; - match icon_state.movement { - Some(flag) => signature.push_str(&format!("\trewind = {}\n", flag)), - None => (), - }; - }; - - match icon_state.hotspot { - Some(array) => signature.push_str(&format!( - "\tarray = {},{},{}\n", - array[0], array[1], array[2] - )), - None => (), - }; - - match &icon_state.unknown_settings { - Some(hashmap) => { - for (setting, value) in hashmap.iter() { - signature.push_str(&format!("\t{} = {}\n", setting, value)); - } - } - None => (), - }; - - sprites.extend(icon_state.images.iter()); - } - - signature.push_str("# END DMI\n"); - - let max_index = (sprites.len() as f64).sqrt().ceil() as u32; - let mut new_png = - image::DynamicImage::new_rgba8(max_index * self.width, max_index * self.height); - - let mut index = 0; - for image in sprites.iter() { - imageops::replace( - &mut new_png, - *image, - self.width * (index % max_index), - self.height * (index / max_index), - ); - index += 1; - } - - let mut new_dmi = vec![]; - new_png.write_to(&mut new_dmi, image::ImageOutputFormat::Png)?; - let mut new_dmi = RawDmi::load(&new_dmi[..])?; - - let new_ztxt = ztxt::create_ztxt_chunk(signature.as_bytes())?; - - new_dmi.chunk_ztxt = Some(new_ztxt); - - Ok(new_dmi.save(&mut writter)?) - } -} - -#[derive(Clone)] -pub struct IconState { - pub name: String, - pub dirs: u8, - pub frames: u32, - pub images: Vec, - pub delay: Option>, - pub loop_flag: Option, - pub rewind: Option, - pub movement: Option, - pub hotspot: Option<[u32; 3]>, - pub unknown_settings: Option>, -} - -impl Default for IconState { - fn default() -> Self { - IconState { - name: String::new(), - dirs: 1, - frames: 1, - images: vec![], - delay: None, - loop_flag: None, - rewind: None, - movement: None, - hotspot: None, - unknown_settings: None, - } - } -} +use super::error; +use super::ztxt; +use super::RawDmi; +use image; +use image::imageops; +use image::GenericImageView; +use std::collections::HashMap; +use std::io::prelude::*; + +#[derive(Clone, Default)] +pub struct Icon { + pub version: String, + pub width: u32, + pub height: u32, + pub states: Vec, +} + +impl Icon { + pub fn load(reader: R) -> Result { + let raw_dmi = RawDmi::load(reader)?; + let chunk_ztxt = match &raw_dmi.chunk_ztxt { + Some(chunk) => chunk.clone(), + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no zTXt chunk found." + ))) + } + }; + let decompressed_text = chunk_ztxt.data.decode()?; + let decompressed_text = String::from_utf8(decompressed_text)?; + let mut decompressed_text = decompressed_text.lines(); + + let current_line = decompressed_text.next(); + if current_line != Some("# BEGIN DMI") { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no DMI header found. Beginning: {:#?}", + current_line + ))); + }; + + let current_line = match decompressed_text.next() { + Some(thing) => thing, + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no version header found." + ))) + } + }; + let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); + if split_version.len() != 2 || split_version[0] != "version" { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper version header found: {:#?}", + split_version + ))); + }; + let version = split_version[1].to_string(); + + let current_line = match decompressed_text.next() { + Some(thing) => thing, + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no width found." + ))) + } + }; + let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); + if split_version.len() != 2 || split_version[0] != "\twidth" { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper width found: {:#?}", + split_version + ))); + }; + let width = split_version[1].parse::()?; + + let current_line = match decompressed_text.next() { + Some(thing) => thing, + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no height found." + ))) + } + }; + let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); + if split_version.len() != 2 || split_version[0] != "\theight" { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper height found: {:#?}", + split_version + ))); + }; + let height = split_version[1].parse::()?; + + if width == 0 || height == 0 { + return Err(error::DmiError::Generic(format!( + "Error loading icon: invalid width ({}) / height ({}) values.", + width, height + ))); + }; + + // Image time. + let mut reader = vec![]; + raw_dmi.save(&mut reader)?; + let base_image = image::load_from_memory_with_format(&reader, image::ImageFormat::Png)?; + + let dimensions = base_image.dimensions(); + let img_width = dimensions.0; + let img_height = dimensions.1; + + if img_width == 0 || img_height == 0 || img_width % width != 0 || img_height % height != 0 { + return Err(error::DmiError::Generic(format!("Error loading icon: invalid image width ({}) / height ({}) values. Missmatch with metadata width ({}) / height ({}).", img_width, img_height, width, height))); + }; + + let width_in_states = img_width / width; + let height_in_states = img_height / height; + let max_possible_states = width_in_states * height_in_states; + + let mut index = 0; + + let mut current_line = match decompressed_text.next() { + Some(thing) => thing, + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no DMI trailer nor states found." + ))) + } + }; + + let mut states = vec![]; + + loop { + if current_line.contains("# END DMI") { + break; + }; + + let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); + if split_version.len() != 2 || split_version[0] != "state" { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper state found: {:#?}", + split_version + ))); + }; + + let name = split_version[1].as_bytes(); + if !name.starts_with(&[b'\"']) || !name.ends_with(&[b'\"']) { + return Err(error::DmiError::Generic(format!("Error loading icon: invalid name icon_state found in metadata, should be preceded and succeeded by double-quotes (\"): {:#?}", name))); + }; + let name = match name.len() { + 0 | 1 => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: invalid name icon_state found in metadata, improper size: {:#?}", + name + ))) + } + 2 => String::new(), //Only the quotes, empty name otherwise. + length @ _ => String::from_utf8(name[1..(length - 1)].to_vec())?, //Hacky way to trim. Blame the cool methods being nightly experimental. + }; + + let mut dirs = None; + let mut frames = None; + let mut delay = None; + let mut loop_flag = None; + let mut rewind = None; + let mut movement = None; + let mut hotspot = None; + let mut unknown_settings = None; + + loop { + current_line = match decompressed_text.next() { + Some(thing) => thing, + None => { + return Err(error::DmiError::Generic(format!( + "Error loading icon: no DMI trailer found." + ))) + } + }; + + if current_line.contains("# END DMI") || current_line.contains("state = \"") { + break; + }; + let split_version: Vec<&str> = current_line.split_terminator(" = ").collect(); + if split_version.len() != 2 { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper state found: {:#?}", + split_version + ))); + }; + + match split_version[0] { + "\tdirs" => dirs = Some(split_version[1].parse::()?), + "\tframes" => frames = Some(split_version[1].parse::()?), + "\tdelay" => { + let mut delay_vector = vec![]; + let text_delays = split_version[1].split_terminator(","); + for text_entry in text_delays { + delay_vector.push(text_entry.parse::()?); + } + delay = Some(delay_vector); + } + "\tloop" => loop_flag = Some(split_version[1].parse::()?), + "\trewind" => rewind = Some(split_version[1].parse::()?), + "\tmovement" => movement = Some(split_version[1].parse::()?), + "\thotspot" => { + let text_coordinates: Vec<&str> = split_version[1].split_terminator(",").collect(); + if text_coordinates.len() != 3 { + return Err(error::DmiError::Generic(format!( + "Error loading icon: improper hotspot found: {:#?}", + split_version + ))); + }; + hotspot = Some([ + text_coordinates[0].parse::()?, + text_coordinates[1].parse::()?, + text_coordinates[2].parse::()?, + ]); + } + _ => { + unknown_settings = match unknown_settings { + None => { + let mut new_map = HashMap::new(); + new_map.insert(split_version[0].to_string(), split_version[1].to_string()); + Some(new_map) + } + Some(mut thing) => { + thing.insert(split_version[0].to_string(), split_version[1].to_string()); + Some(thing) + } + }; + } + }; + } + + if dirs == None || frames == None { + return Err(error::DmiError::Generic(format!( + "Error loading icon: state lacks essential settings. dirs: {:#?}. frames: {:#?}.", + dirs, frames + ))); + }; + let dirs = dirs.unwrap(); + let frames = frames.unwrap(); + + if index + (dirs as u32 * frames) > max_possible_states { + return Err(error::DmiError::Generic(format!("Error loading icon: metadata settings exceeded the maximum number of states possible ({}).", max_possible_states))); + }; + + let mut images = vec![]; + + for _frame in 0..frames { + for _dir in 0..dirs { + let x = (index % width_in_states) * width; + //This operation rounds towards zero, truncating any fractional part of the exact result, essentially a floor() function. + let y = (index / width_in_states) * height; + images.push(base_image.crop_imm(x, y, width, height)); + index += 1; + } + } + + states.push(IconState { + name, + dirs, + frames, + images, + delay, + loop_flag, + rewind, + movement, + hotspot, + unknown_settings, + }); + } + + Ok(Icon { + version, + width, + height, + states, + }) + } + + pub fn save(&self, mut writter: &mut W) -> Result { + let mut sprites = vec![]; + let mut signature = format!( + "# BEGIN DMI\nversion = {}\n\twidth = {}\n\theight = {}\n", + self.version, self.width, self.height + ); + + for icon_state in &self.states { + if icon_state.images.len() as u32 != icon_state.dirs as u32 * icon_state.frames { + return Err(error::DmiError::Generic(format!("Error saving Icon: number of images ({}) differs from the stated metadata. Dirs: {}. Frames: {}. Name: \"{}\".", icon_state.images.len(), icon_state.dirs, icon_state.frames, icon_state.name))); + }; + + signature.push_str(&format!( + "state = \"{}\"\n\tdirs = {}\n\tframes = {}\n", + icon_state.name, icon_state.dirs, icon_state.frames + )); + + if icon_state.frames > 1 { + match &icon_state.delay { + Some(delay) => { + if delay.len() as u32 != icon_state.frames { + return Err(error::DmiError::Generic(format!("Error saving Icon: number of frames ({}) differs from the delay entry ({:3?}). Name: \"{}\".", icon_state.frames, delay, icon_state.name))) + }; + let delay: Vec= delay.iter().map(|&c| c.to_string()).collect(); + signature.push_str(&format!("\tdelay = {}\n", delay.join(","))); + }, + None => return Err(error::DmiError::Generic(format!("Error saving Icon: number of frames ({}) larger than one without a delay entry in icon state of name \"{}\".", icon_state.frames, icon_state.name))) + }; + match icon_state.loop_flag { + Some(flag) => signature.push_str(&format!("\tloop = {}\n", flag)), + None => (), + }; + match icon_state.rewind { + Some(flag) => signature.push_str(&format!("\trewind = {}\n", flag)), + None => (), + }; + match icon_state.movement { + Some(flag) => signature.push_str(&format!("\trewind = {}\n", flag)), + None => (), + }; + }; + + match icon_state.hotspot { + Some(array) => signature.push_str(&format!( + "\tarray = {},{},{}\n", + array[0], array[1], array[2] + )), + None => (), + }; + + match &icon_state.unknown_settings { + Some(hashmap) => { + for (setting, value) in hashmap.iter() { + signature.push_str(&format!("\t{} = {}\n", setting, value)); + } + } + None => (), + }; + + sprites.extend(icon_state.images.iter()); + } + + signature.push_str("# END DMI\n"); + + let max_index = (sprites.len() as f64).sqrt().ceil() as u32; + let mut new_png = + image::DynamicImage::new_rgba8(max_index * self.width, max_index * self.height); + + let mut index = 0; + for image in sprites.iter() { + imageops::replace( + &mut new_png, + *image, + self.width * (index % max_index), + self.height * (index / max_index), + ); + index += 1; + } + + let mut new_dmi = vec![]; + new_png.write_to(&mut new_dmi, image::ImageOutputFormat::Png)?; + let mut new_dmi = RawDmi::load(&new_dmi[..])?; + + let new_ztxt = ztxt::create_ztxt_chunk(signature.as_bytes())?; + + new_dmi.chunk_ztxt = Some(new_ztxt); + + Ok(new_dmi.save(&mut writter)?) + } +} + +#[derive(Clone)] +pub struct IconState { + pub name: String, + pub dirs: u8, + pub frames: u32, + pub images: Vec, + pub delay: Option>, + pub loop_flag: Option, + pub rewind: Option, + pub movement: Option, + pub hotspot: Option<[u32; 3]>, + pub unknown_settings: Option>, +} + +impl Default for IconState { + fn default() -> Self { + IconState { + name: String::new(), + dirs: 1, + frames: 1, + images: vec![], + delay: None, + loop_flag: None, + rewind: None, + movement: None, + hotspot: None, + unknown_settings: None, + } + } +} diff --git a/src/dmi/iend.rs b/src/dmi/iend.rs index 3f69080..87ef098 100644 --- a/src/dmi/iend.rs +++ b/src/dmi/iend.rs @@ -1,136 +1,152 @@ -use super::chunk; -use super::error; -use std::convert::TryFrom; -use std::io::prelude::*; - -pub const IEND_TYPE: [u8; 4] = [b'I', b'E', b'N', b'D']; - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct RawIendChunk { - pub data_length: [u8; 4], - pub chunk_type: [u8; 4], - pub crc: [u8; 4], -} - -impl RawIendChunk { - pub fn new() -> RawIendChunk { - RawIendChunk { - ..Default::default() - } - } - - pub fn length(&self) -> usize { - self.data_length.len() + self.chunk_type.len() + self.crc.len() - } - - pub fn load(reader: &mut R) -> Result { - let default_iend_chunk = RawIendChunk::new(); - - let mut raw_chunk_bytes = Vec::new(); - reader.read_to_end(&mut raw_chunk_bytes)?; - - let total_bytes_length = raw_chunk_bytes.len(); - if total_bytes_length != default_iend_chunk.length() { - return Err(error::DmiError::Generic(format!( - "Failed to load RawIendChunk from reader. Size: {}. Expected: {}.", - raw_chunk_bytes.len(), default_iend_chunk.length() - ))); - } - - let data_length = [ - raw_chunk_bytes[0], - raw_chunk_bytes[1], - raw_chunk_bytes[2], - raw_chunk_bytes[3], - ]; - if data_length != default_iend_chunk.data_length { - return Err(error::DmiError::Generic(format!("Failed to load RawIendChunk from reader. Lengh field value: {:#?}. Expected: {:#?}.", data_length, default_iend_chunk.data_length))); - } - - let chunk_type = [ - raw_chunk_bytes[4], - raw_chunk_bytes[5], - raw_chunk_bytes[6], - raw_chunk_bytes[7], - ]; - if chunk_type != default_iend_chunk.chunk_type { - return Err(error::DmiError::Generic(format!("Failed to load RawIendChunk from reader. Chunk type: {:#?}. Expected {:#?}.", chunk_type, default_iend_chunk.chunk_type))); - } - - let crc = [ - raw_chunk_bytes[total_bytes_length - 4], - raw_chunk_bytes[total_bytes_length - 3], - raw_chunk_bytes[total_bytes_length - 2], - raw_chunk_bytes[total_bytes_length - 1], - ]; - if crc != default_iend_chunk.crc { - return Err(error::DmiError::Generic(format!("Failed to load RawIendChunk from reader. CRC: {:#?}. Expected {:#?}.", crc, default_iend_chunk.crc))); - } - - Ok(default_iend_chunk) - } - - pub fn save(&self, writter: &mut W) -> Result { - let bytes_written = writter.write(&self.data_length)?; - let mut total_bytes_written = bytes_written; - if bytes_written < self.data_length.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.chunk_type)?; - total_bytes_written += bytes_written; - if bytes_written < self.chunk_type.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.crc)?; - total_bytes_written += bytes_written; - if bytes_written < self.crc.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - Ok(total_bytes_written) - } -} - -impl Default for RawIendChunk { - fn default() -> Self { - let data_length = [0, 0, 0, 0]; - let chunk_type = IEND_TYPE; - let crc = [174, 66, 96, 130]; - RawIendChunk { - data_length, - chunk_type, - crc, - } - } -} - -impl TryFrom for RawIendChunk { - type Error = error::DmiError; - fn try_from(raw_generic_chunk: chunk::RawGenericChunk) -> Result { - if raw_generic_chunk.data.len() > 0 { - return Err(error::DmiError::Generic(format!("Failed to convert RawGenericChunk into RawIendChunk. Non-empty data field. Chunk: {:#?}.", raw_generic_chunk))); - }; - - let default_iend_chunk = RawIendChunk::new(); - - if raw_generic_chunk.chunk_type != default_iend_chunk.chunk_type { - return Err(error::DmiError::Generic(format!("Failed to convert RawGenericChunk into RawIendChunk. Wrong type: {:#?}. Expected: {:#?}.", raw_generic_chunk.chunk_type, default_iend_chunk.chunk_type))); - }; - if raw_generic_chunk.crc != default_iend_chunk.crc { - return Err(error::DmiError::Generic(format!("Failed to convert RawGenericChunk into RawIendChunk. Mismatching CRC: {:#?}. Expected: {:#?}.", raw_generic_chunk.crc, default_iend_chunk.crc))); - } - - Ok(default_iend_chunk) - } -} +use super::chunk; +use super::error; +use std::convert::TryFrom; +use std::io::prelude::*; + +pub const IEND_TYPE: [u8; 4] = [b'I', b'E', b'N', b'D']; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RawIendChunk { + pub data_length: [u8; 4], + pub chunk_type: [u8; 4], + pub crc: [u8; 4], +} + +impl RawIendChunk { + pub fn new() -> RawIendChunk { + RawIendChunk { + ..Default::default() + } + } + + pub fn length(&self) -> usize { + self.data_length.len() + self.chunk_type.len() + self.crc.len() + } + + pub fn load(reader: &mut R) -> Result { + let default_iend_chunk = RawIendChunk::new(); + + let mut raw_chunk_bytes = Vec::new(); + reader.read_to_end(&mut raw_chunk_bytes)?; + + let total_bytes_length = raw_chunk_bytes.len(); + if total_bytes_length != default_iend_chunk.length() { + return Err(error::DmiError::Generic(format!( + "Failed to load RawIendChunk from reader. Size: {}. Expected: {}.", + raw_chunk_bytes.len(), + default_iend_chunk.length() + ))); + } + + let data_length = [ + raw_chunk_bytes[0], + raw_chunk_bytes[1], + raw_chunk_bytes[2], + raw_chunk_bytes[3], + ]; + if data_length != default_iend_chunk.data_length { + return Err(error::DmiError::Generic(format!( + "Failed to load RawIendChunk from reader. Lengh field value: {:#?}. Expected: {:#?}.", + data_length, default_iend_chunk.data_length + ))); + } + + let chunk_type = [ + raw_chunk_bytes[4], + raw_chunk_bytes[5], + raw_chunk_bytes[6], + raw_chunk_bytes[7], + ]; + if chunk_type != default_iend_chunk.chunk_type { + return Err(error::DmiError::Generic(format!( + "Failed to load RawIendChunk from reader. Chunk type: {:#?}. Expected {:#?}.", + chunk_type, default_iend_chunk.chunk_type + ))); + } + + let crc = [ + raw_chunk_bytes[total_bytes_length - 4], + raw_chunk_bytes[total_bytes_length - 3], + raw_chunk_bytes[total_bytes_length - 2], + raw_chunk_bytes[total_bytes_length - 1], + ]; + if crc != default_iend_chunk.crc { + return Err(error::DmiError::Generic(format!( + "Failed to load RawIendChunk from reader. CRC: {:#?}. Expected {:#?}.", + crc, default_iend_chunk.crc + ))); + } + + Ok(default_iend_chunk) + } + + pub fn save(&self, writter: &mut W) -> Result { + let bytes_written = writter.write(&self.data_length)?; + let mut total_bytes_written = bytes_written; + if bytes_written < self.data_length.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.chunk_type)?; + total_bytes_written += bytes_written; + if bytes_written < self.chunk_type.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.crc)?; + total_bytes_written += bytes_written; + if bytes_written < self.crc.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save IEND chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + Ok(total_bytes_written) + } +} + +impl Default for RawIendChunk { + fn default() -> Self { + let data_length = [0, 0, 0, 0]; + let chunk_type = IEND_TYPE; + let crc = [174, 66, 96, 130]; + RawIendChunk { + data_length, + chunk_type, + crc, + } + } +} + +impl TryFrom for RawIendChunk { + type Error = error::DmiError; + fn try_from(raw_generic_chunk: chunk::RawGenericChunk) -> Result { + if raw_generic_chunk.data.len() > 0 { + return Err(error::DmiError::Generic(format!( + "Failed to convert RawGenericChunk into RawIendChunk. Non-empty data field. Chunk: {:#?}.", + raw_generic_chunk + ))); + }; + + let default_iend_chunk = RawIendChunk::new(); + + if raw_generic_chunk.chunk_type != default_iend_chunk.chunk_type { + return Err(error::DmiError::Generic(format!( + "Failed to convert RawGenericChunk into RawIendChunk. Wrong type: {:#?}. Expected: {:#?}.", + raw_generic_chunk.chunk_type, default_iend_chunk.chunk_type + ))); + }; + if raw_generic_chunk.crc != default_iend_chunk.crc { + return Err(error::DmiError::Generic(format!("Failed to convert RawGenericChunk into RawIendChunk. Mismatching CRC: {:#?}. Expected: {:#?}.", raw_generic_chunk.crc, default_iend_chunk.crc))); + } + + Ok(default_iend_chunk) + } +} diff --git a/src/dmi/ztxt.rs b/src/dmi/ztxt.rs index e0c65b2..e5fa2eb 100644 --- a/src/dmi/ztxt.rs +++ b/src/dmi/ztxt.rs @@ -1,325 +1,336 @@ -use super::chunk; -use super::crc; -use super::error; -use deflate; -use inflate; -use std::convert::TryFrom; -use std::fmt; -use std::io::prelude::*; - -pub const ZTXT_TYPE: [u8; 4] = [b'z', b'T', b'X', b't']; - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct RawZtxtChunk { - pub data_length: [u8; 4], - pub chunk_type: [u8; 4], - pub data: RawZtxtData, - pub crc: [u8; 4], -} - -pub fn create_ztxt_chunk(dmi_signature: &[u8]) -> Result { - let compressed_text = encode(dmi_signature); - let data = RawZtxtData { - compressed_text, - ..Default::default() - }; - let mut data_bytes = vec![]; - data.save(&mut data_bytes)?; - let data_length = (data_bytes.len() as u32).to_be_bytes(); - let chunk_type = ZTXT_TYPE; - let crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())).to_be_bytes(); - Ok(RawZtxtChunk { - data_length, - chunk_type, - data, - crc, - }) -} - -impl RawZtxtChunk { - pub fn load(reader: &mut R) -> Result { - let mut raw_chunk_bytes = Vec::new(); - reader.read_to_end(&mut raw_chunk_bytes)?; - let total_bytes_length = raw_chunk_bytes.len(); - if total_bytes_length < 12 { - return Err(error::DmiError::Generic(format!( - "Failed to load RawZtxtChunk from reader. Size: {}. Minimum necessary is 12.", - raw_chunk_bytes.len() - ))); - } - let data_length = [ - raw_chunk_bytes[0], - raw_chunk_bytes[1], - raw_chunk_bytes[2], - raw_chunk_bytes[3], - ]; - if u32::from_be_bytes(data_length) != total_bytes_length as u32 - 12 { - return Err(error::DmiError::Generic(format!("Failed to load RawZtxtChunk from reader. Lengh field value ({}) does not match the actual data field size ({}).", u32::from_be_bytes(data_length), total_bytes_length -12))); - } - let chunk_type = [ - raw_chunk_bytes[4], - raw_chunk_bytes[5], - raw_chunk_bytes[6], - raw_chunk_bytes[7], - ]; - if chunk_type != ZTXT_TYPE { - return Err(error::DmiError::Generic(format!("Failed to load RawZtxtChunk from reader. Chunk type is not zTXt: {:#?}. Should be {:#?}.", chunk_type, ZTXT_TYPE))); - } - let data_bytes = &raw_chunk_bytes[8..(total_bytes_length - 4)].to_vec(); - let data = RawZtxtData::load(&mut &**data_bytes)?; - let crc = [ - raw_chunk_bytes[total_bytes_length - 4], - raw_chunk_bytes[total_bytes_length - 3], - raw_chunk_bytes[total_bytes_length - 2], - raw_chunk_bytes[total_bytes_length - 1], - ]; - let calculated_crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())); - if u32::from_be_bytes(crc) != calculated_crc { - return Err(error::DmiError::Generic(format!("Failed to load RawZtxtChunk from reader. Given CRC ({}) does not match the calculated one ({}).", u32::from_be_bytes(crc), calculated_crc))); - } - Ok(RawZtxtChunk { - data_length, - chunk_type, - data, - crc, - }) - } - - pub fn save(&self, writter: &mut W) -> Result { - let bytes_written = writter.write(&self.data_length)?; - let mut total_bytes_written = bytes_written; - if bytes_written < self.data_length.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.chunk_type)?; - total_bytes_written += bytes_written; - if bytes_written < self.chunk_type.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = self.data.save(&mut *writter)?; - total_bytes_written += bytes_written; - if bytes_written < u32::from_be_bytes(self.data_length) as usize { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.crc)?; - total_bytes_written += bytes_written; - if bytes_written < self.crc.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - Ok(total_bytes_written) - } - - pub fn set_data(&self, data: RawZtxtData) -> Result { - let mut data_bytes = vec![]; - data.save(&mut data_bytes)?; - let data_length = (data_bytes.len() as u32).to_be_bytes(); - let chunk_type = ZTXT_TYPE; - let crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())).to_be_bytes(); - Ok(RawZtxtChunk { - data_length, - chunk_type, - data, - crc, - }) - } -} - -impl Default for RawZtxtChunk { - fn default() -> Self { - let data: RawZtxtData = Default::default(); - let data_length = (data.length() as u32).to_be_bytes(); - let chunk_type = ZTXT_TYPE; - let crc = data.crc().to_be_bytes(); - RawZtxtChunk { - data_length, - chunk_type, - data, - crc, - } - } -} - -impl TryFrom for RawZtxtChunk { - type Error = error::DmiError; - fn try_from(raw_generic_chunk: chunk::RawGenericChunk) -> Result { - let data_length = raw_generic_chunk.data_length; - let chunk_type = raw_generic_chunk.chunk_type; - if chunk_type != ZTXT_TYPE { - return Err(error::DmiError::Generic(format!("Failed to convert RawGenericChunk into RawZtxtChunk. Wrong type: {:#?}. Expected: {:#?}.", chunk_type, ZTXT_TYPE))); - }; - let chunk_data = &raw_generic_chunk.data; - let data = RawZtxtData::load(&mut &**chunk_data)?; - let crc = raw_generic_chunk.crc; - Ok(RawZtxtChunk { - data_length, - chunk_type, - data, - crc, - }) - } -} - -/* -impl TryFrom> for RawZtxtChunk { - type Error = anyhow::Error; - fn try_from(raw_chunk_bytes: Vec) -> Result { - let total_bytes_length = raw_chunk_bytes.len(); - if total_bytes_length < 12 { - bail!("Failed to convert Vec into RawZtxtChunk. Size: {}. Minimum necessary is 12.", raw_chunk_bytes.len()) - } - let length = [raw_chunk_bytes[0], raw_chunk_bytes[1], raw_chunk_bytes[2], raw_chunk_bytes[3]]; - if u32::from_be_bytes(length) != total_bytes_length as u32 - 12 { - bail!("Failed to convert Vec into RawZtxtChunk. Lengh field value ({}) does not match the actual data field size ({}).", u32::from_be_bytes(length), total_bytes_length -12) - } - let chunk_type = [raw_chunk_bytes[4], raw_chunk_bytes[5], raw_chunk_bytes[6], raw_chunk_bytes[7]]; - if chunk_type != ZTXT_TYPE { - bail!("Failed to convert Vec into RawZtxtChunk. Chunk type is not zTXt: {:#?}. Should be {:#?}.", chunk_type, ZTXT_TYPE) - } - let data_bytes = &raw_chunk_bytes[8..(total_bytes_length - 4)]; - let data = RawZtxtData::load(data_bytes)?; - let crc = [raw_chunk_bytes[total_bytes_length - 4], raw_chunk_bytes[total_bytes_length - 3], raw_chunk_bytes[total_bytes_length - 2], raw_chunk_bytes[total_bytes_length - 1]]; - let calculated_crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())); - if u32::from_be_bytes(crc) != calculated_crc { - bail!("Failed to convert Vec into RawZtxtChunk. Given CRC ({}) does not match the calculated one ({}).", u32::from_be_bytes(crc), calculated_crc) - } - Ok(RawZtxtChunk { - length, - chunk_type, - data, - crc, - }) - } -} -*/ - -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct RawZtxtData { - pub keyword: Vec, - pub null_separator: u8, - pub compression_method: u8, - pub compressed_text: Vec, -} - -impl RawZtxtData { - pub fn load(reader: &mut R) -> Result { - let mut data_bytes = Vec::new(); - reader.read_to_end(&mut data_bytes)?; - let mut data_bytes_iter = data_bytes.iter().cloned(); - let keyword = data_bytes_iter.by_ref().take_while(|x| *x != 0).collect(); - let null_separator = 0; - let compression_method = data_bytes_iter.next().ok_or_else(|| error::DmiError::Generic(format!("Failed to load RawZtxtData from reader, during compression method reading.\nVector: {:#?}", data_bytes)))?; - //let compressed_text = RawCompressedText::try_from(back_to_vector)?; - let compressed_text = data_bytes_iter.collect(); - //let compressed_text = RawCompressedText::load(&back_to_vector[..])?; - - Ok(RawZtxtData { - keyword, - null_separator, - compression_method, - compressed_text, - }) - } - - pub fn save(&self, writter: &mut W) -> Result { - let bytes_written = writter.write(&self.keyword)?; - let mut total_bytes_written = bytes_written; - if bytes_written < self.keyword.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&[self.null_separator])?; - total_bytes_written += bytes_written; - if bytes_written < 1 { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&[self.compression_method])?; - total_bytes_written += bytes_written; - if bytes_written < 1 { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - let bytes_written = writter.write(&self.compressed_text)?; - total_bytes_written += bytes_written; - if bytes_written < self.compressed_text.len() { - return Err(error::DmiError::Generic(format!( - "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", - total_bytes_written - ))); - }; - - Ok(total_bytes_written) - } - - pub fn decode(&self) -> Result, error::DmiError> { - match inflate::inflate_bytes_zlib(&self.compressed_text) { - Ok(decompressed_text) => Ok(decompressed_text), - Err(text) => { - return Err(error::DmiError::Generic(format!( - "Failed to read compressed text. Error: {}", - text - ))) - } - } - } - - fn length(&self) -> usize { - self.keyword.len() + 2 + self.compressed_text.len() - } - - fn crc(&self) -> u32 { - crc::calculate_crc( - ZTXT_TYPE - .iter() - .chain(self.keyword.iter()) - .chain([self.null_separator, self.compression_method].iter()) - .chain(self.compressed_text.iter()), - ) - } -} - -pub fn encode(text_to_compress: &[u8]) -> Vec { - deflate::deflate_bytes_zlib(text_to_compress) -} - -impl Default for RawZtxtData { - fn default() -> Self { - RawZtxtData { - keyword: "Description".as_bytes().to_vec(), - null_separator: 0, - compression_method: 0, - compressed_text: vec![], - } - } -} - -impl fmt::Display for RawZtxtData { - fn fmt(&self, feedback: &mut fmt::Formatter) -> fmt::Result { - write!(feedback, "RawZtxtData chunk error.\nkeyword: {:#?}\nnull_separator: {:#?}\ncompression_method: {:#?}\ncompressed_text: {:#?}", self.keyword, self.null_separator, self.compression_method, self.compressed_text) - } -} +use super::chunk; +use super::crc; +use super::error; +use deflate; +use inflate; +use std::convert::TryFrom; +use std::fmt; +use std::io::prelude::*; + +pub const ZTXT_TYPE: [u8; 4] = [b'z', b'T', b'X', b't']; + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RawZtxtChunk { + pub data_length: [u8; 4], + pub chunk_type: [u8; 4], + pub data: RawZtxtData, + pub crc: [u8; 4], +} + +pub fn create_ztxt_chunk(dmi_signature: &[u8]) -> Result { + let compressed_text = encode(dmi_signature); + let data = RawZtxtData { + compressed_text, + ..Default::default() + }; + let mut data_bytes = vec![]; + data.save(&mut data_bytes)?; + let data_length = (data_bytes.len() as u32).to_be_bytes(); + let chunk_type = ZTXT_TYPE; + let crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())).to_be_bytes(); + Ok(RawZtxtChunk { + data_length, + chunk_type, + data, + crc, + }) +} + +impl RawZtxtChunk { + pub fn load(reader: &mut R) -> Result { + let mut raw_chunk_bytes = Vec::new(); + reader.read_to_end(&mut raw_chunk_bytes)?; + let total_bytes_length = raw_chunk_bytes.len(); + if total_bytes_length < 12 { + return Err(error::DmiError::Generic(format!( + "Failed to load RawZtxtChunk from reader. Size: {}. Minimum necessary is 12.", + raw_chunk_bytes.len() + ))); + } + let data_length = [ + raw_chunk_bytes[0], + raw_chunk_bytes[1], + raw_chunk_bytes[2], + raw_chunk_bytes[3], + ]; + if u32::from_be_bytes(data_length) != total_bytes_length as u32 - 12 { + return Err(error::DmiError::Generic(format!("Failed to load RawZtxtChunk from reader. Lengh field value ({}) does not match the actual data field size ({}).", u32::from_be_bytes(data_length), total_bytes_length -12))); + } + let chunk_type = [ + raw_chunk_bytes[4], + raw_chunk_bytes[5], + raw_chunk_bytes[6], + raw_chunk_bytes[7], + ]; + if chunk_type != ZTXT_TYPE { + return Err(error::DmiError::Generic(format!( + "Failed to load RawZtxtChunk from reader. Chunk type is not zTXt: {:#?}. Should be {:#?}.", + chunk_type, ZTXT_TYPE + ))); + } + let data_bytes = &raw_chunk_bytes[8..(total_bytes_length - 4)].to_vec(); + let data = RawZtxtData::load(&mut &**data_bytes)?; + let crc = [ + raw_chunk_bytes[total_bytes_length - 4], + raw_chunk_bytes[total_bytes_length - 3], + raw_chunk_bytes[total_bytes_length - 2], + raw_chunk_bytes[total_bytes_length - 1], + ]; + let calculated_crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())); + if u32::from_be_bytes(crc) != calculated_crc { + return Err(error::DmiError::Generic(format!("Failed to load RawZtxtChunk from reader. Given CRC ({}) does not match the calculated one ({}).", u32::from_be_bytes(crc), calculated_crc))); + } + Ok(RawZtxtChunk { + data_length, + chunk_type, + data, + crc, + }) + } + + pub fn save(&self, writter: &mut W) -> Result { + let bytes_written = writter.write(&self.data_length)?; + let mut total_bytes_written = bytes_written; + if bytes_written < self.data_length.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.chunk_type)?; + total_bytes_written += bytes_written; + if bytes_written < self.chunk_type.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = self.data.save(&mut *writter)?; + total_bytes_written += bytes_written; + if bytes_written < u32::from_be_bytes(self.data_length) as usize { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.crc)?; + total_bytes_written += bytes_written; + if bytes_written < self.crc.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt chunk. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + Ok(total_bytes_written) + } + + pub fn set_data(&self, data: RawZtxtData) -> Result { + let mut data_bytes = vec![]; + data.save(&mut data_bytes)?; + let data_length = (data_bytes.len() as u32).to_be_bytes(); + let chunk_type = ZTXT_TYPE; + let crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())).to_be_bytes(); + Ok(RawZtxtChunk { + data_length, + chunk_type, + data, + crc, + }) + } +} + +impl Default for RawZtxtChunk { + fn default() -> Self { + let data: RawZtxtData = Default::default(); + let data_length = (data.length() as u32).to_be_bytes(); + let chunk_type = ZTXT_TYPE; + let crc = data.crc().to_be_bytes(); + RawZtxtChunk { + data_length, + chunk_type, + data, + crc, + } + } +} + +impl TryFrom for RawZtxtChunk { + type Error = error::DmiError; + fn try_from(raw_generic_chunk: chunk::RawGenericChunk) -> Result { + let data_length = raw_generic_chunk.data_length; + let chunk_type = raw_generic_chunk.chunk_type; + if chunk_type != ZTXT_TYPE { + return Err(error::DmiError::Generic(format!( + "Failed to convert RawGenericChunk into RawZtxtChunk. Wrong type: {:#?}. Expected: {:#?}.", + chunk_type, ZTXT_TYPE + ))); + }; + let chunk_data = &raw_generic_chunk.data; + let data = RawZtxtData::load(&mut &**chunk_data)?; + let crc = raw_generic_chunk.crc; + Ok(RawZtxtChunk { + data_length, + chunk_type, + data, + crc, + }) + } +} + +/* +impl TryFrom> for RawZtxtChunk { + type Error = anyhow::Error; + fn try_from(raw_chunk_bytes: Vec) -> Result { + let total_bytes_length = raw_chunk_bytes.len(); + if total_bytes_length < 12 { + bail!("Failed to convert Vec into RawZtxtChunk. Size: {}. Minimum necessary is 12.", raw_chunk_bytes.len()) + } + let length = [raw_chunk_bytes[0], raw_chunk_bytes[1], raw_chunk_bytes[2], raw_chunk_bytes[3]]; + if u32::from_be_bytes(length) != total_bytes_length as u32 - 12 { + bail!("Failed to convert Vec into RawZtxtChunk. Lengh field value ({}) does not match the actual data field size ({}).", u32::from_be_bytes(length), total_bytes_length -12) + } + let chunk_type = [raw_chunk_bytes[4], raw_chunk_bytes[5], raw_chunk_bytes[6], raw_chunk_bytes[7]]; + if chunk_type != ZTXT_TYPE { + bail!("Failed to convert Vec into RawZtxtChunk. Chunk type is not zTXt: {:#?}. Should be {:#?}.", chunk_type, ZTXT_TYPE) + } + let data_bytes = &raw_chunk_bytes[8..(total_bytes_length - 4)]; + let data = RawZtxtData::load(data_bytes)?; + let crc = [raw_chunk_bytes[total_bytes_length - 4], raw_chunk_bytes[total_bytes_length - 3], raw_chunk_bytes[total_bytes_length - 2], raw_chunk_bytes[total_bytes_length - 1]]; + let calculated_crc = crc::calculate_crc(chunk_type.iter().chain(data_bytes.iter())); + if u32::from_be_bytes(crc) != calculated_crc { + bail!("Failed to convert Vec into RawZtxtChunk. Given CRC ({}) does not match the calculated one ({}).", u32::from_be_bytes(crc), calculated_crc) + } + Ok(RawZtxtChunk { + length, + chunk_type, + data, + crc, + }) + } +} +*/ + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RawZtxtData { + pub keyword: Vec, + pub null_separator: u8, + pub compression_method: u8, + pub compressed_text: Vec, +} + +impl RawZtxtData { + pub fn load(reader: &mut R) -> Result { + let mut data_bytes = Vec::new(); + reader.read_to_end(&mut data_bytes)?; + let mut data_bytes_iter = data_bytes.iter().cloned(); + let keyword = data_bytes_iter.by_ref().take_while(|x| *x != 0).collect(); + let null_separator = 0; + let compression_method = data_bytes_iter.next().ok_or_else(|| { + error::DmiError::Generic(format!( + "Failed to load RawZtxtData from reader, during compression method reading.\nVector: {:#?}", + data_bytes + )) + })?; + //let compressed_text = RawCompressedText::try_from(back_to_vector)?; + let compressed_text = data_bytes_iter.collect(); + //let compressed_text = RawCompressedText::load(&back_to_vector[..])?; + + Ok(RawZtxtData { + keyword, + null_separator, + compression_method, + compressed_text, + }) + } + + pub fn save(&self, writter: &mut W) -> Result { + let bytes_written = writter.write(&self.keyword)?; + let mut total_bytes_written = bytes_written; + if bytes_written < self.keyword.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&[self.null_separator])?; + total_bytes_written += bytes_written; + if bytes_written < 1 { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&[self.compression_method])?; + total_bytes_written += bytes_written; + if bytes_written < 1 { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + let bytes_written = writter.write(&self.compressed_text)?; + total_bytes_written += bytes_written; + if bytes_written < self.compressed_text.len() { + return Err(error::DmiError::Generic(format!( + "Failed to save zTXt data. Buffer unable to hold the data, only {} bytes written.", + total_bytes_written + ))); + }; + + Ok(total_bytes_written) + } + + pub fn decode(&self) -> Result, error::DmiError> { + match inflate::inflate_bytes_zlib(&self.compressed_text) { + Ok(decompressed_text) => Ok(decompressed_text), + Err(text) => { + return Err(error::DmiError::Generic(format!( + "Failed to read compressed text. Error: {}", + text + ))) + } + } + } + + fn length(&self) -> usize { + self.keyword.len() + 2 + self.compressed_text.len() + } + + fn crc(&self) -> u32 { + crc::calculate_crc( + ZTXT_TYPE + .iter() + .chain(self.keyword.iter()) + .chain([self.null_separator, self.compression_method].iter()) + .chain(self.compressed_text.iter()), + ) + } +} + +pub fn encode(text_to_compress: &[u8]) -> Vec { + deflate::deflate_bytes_zlib(text_to_compress) +} + +impl Default for RawZtxtData { + fn default() -> Self { + RawZtxtData { + keyword: "Description".as_bytes().to_vec(), + null_separator: 0, + compression_method: 0, + compressed_text: vec![], + } + } +} + +impl fmt::Display for RawZtxtData { + fn fmt(&self, feedback: &mut fmt::Formatter) -> fmt::Result { + write!(feedback, "RawZtxtData chunk error.\nkeyword: {:#?}\nnull_separator: {:#?}\ncompression_method: {:#?}\ncompressed_text: {:#?}", self.keyword, self.null_separator, self.compression_method, self.compressed_text) + } +} diff --git a/src/tests.rs b/src/tests.rs index 1afa005..0be5e9e 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,13 +1,13 @@ -use std::path::Path; -use std::path::PathBuf; -use std::fs::File; -use super::icon; - -#[test] -fn load_dmi() { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("tests/load_test.dmi"); - let path = Path::new(&path); - let file = File::open(&path).expect(&format!("No lights dmi: {:?}", path)); - let _lights_icon = icon::Icon::load(&file).expect("Unable to load lights dmi"); -} +use super::icon; +use std::fs::File; +use std::path::Path; +use std::path::PathBuf; + +#[test] +fn load_dmi() { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/load_test.dmi"); + let path = Path::new(&path); + let file = File::open(&path).expect(&format!("No lights dmi: {:?}", path)); + let _lights_icon = icon::Icon::load(&file).expect("Unable to load lights dmi"); +}