use failure::Error; use image; use serde_json; use std::io::{Read, Write, Seek}; use zip::{ZipArchive, ZipWriter}; /// This value represents both the current document in-memory as well /// as the entirety of the values that we will want to both save and /// restore. pub struct Document { pub tilesheet: image::DynamicImage, pub metadata: Metadata, pub rules: (), } /// This should be renamed probably, but it's the configuration-level /// info about a document (e.g. the tile size) #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Metadata { pub tile_width: u16, pub tile_height: u16, pub config_loop_forever: bool, } impl Document { /// Attempt to read a `Document` from anything which implements /// `Read` and `Seek`. The file format for Palladio files is /// documented externally. pub fn open_from_file(r: &mut R) -> Result { let mut archive = ZipArchive::new(r)?; let tilesheet = { let mut buf = Vec::new(); archive.by_name("tilesheet.png")?.read_to_end(&mut buf)?; image::load_from_memory(&buf)? }; let metadata = { let file = archive.by_name("metadata.json")?; serde_json::from_reader(file)? }; let rules = { let file = archive.by_name("rules.json")?; serde_json::from_reader(file)? }; Ok(Document{ tilesheet, metadata, rules }) } /// Attempt to write a `Document` from anything which implements /// `Write` and `Seek`. The file format for Palladio files is /// documented externally. pub fn save_to_file(&self, w: &mut W) -> Result<(), Error> { use zip::write::FileOptions; let mut zip = ZipWriter::new(w); zip.start_file("tilesheet.png", FileOptions::default())?; self.tilesheet.write_to(&mut zip, image::ImageOutputFormat::PNG)?; zip.start_file("metadata.json", FileOptions::default())?; serde_json::to_writer(&mut zip, &self.metadata)?; zip.start_file("rules.json", FileOptions::default())?; serde_json::to_writer(&mut zip, &self.rules)?; zip.start_file("info.txt", FileOptions::default())?; writeln!( &mut zip, "Created by {} v{}", ::constants::PROGRAM_NAME, ::constants::PROGRAM_VERSION, )?; zip.finish()?; Ok(()) } /// Create a new fresh document with an empty 32x32 image, a /// configured tile size of 16x16, and no rules pub fn default() -> Document { Document { tilesheet: image::DynamicImage::new_rgb8(32, 32), metadata: Metadata { tile_width: 16, tile_height: 16, config_loop_forever: false, }, rules: (), } } } #[cfg(test)] mod tests { use super::Document; use std::io::{BufReader, Cursor}; #[test] fn round_trip() { // First, save our dummy `Document` to an in-memory buffer let mut buf = Cursor::new(Vec::new()); let doc1 = Document::default(); doc1.save_to_file(&mut buf).unwrap(); // then take that buffer back, and reparse it let buf = buf.into_inner(); let doc2 = Document::open_from_file(&mut BufReader::new(Cursor::new(buf))).unwrap(); // we can't assert equality over the image itself, so let's // just assert that the other parts are equal assert!(doc1.metadata == doc2.metadata); assert!(doc1.rules == doc2.rules); } }