use failure::Error;
use image;
use serde_json;
use std::io::{Read, Seek, Write};
use zip::{ZipArchive, ZipWriter};
use crate::constants;
/// 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: Vec<RuleSpec>,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuleSpec {
pub rule_name: String,
pub lhs: Vec<((u16, u16), (u16, u16))>,
pub rhs: Vec<((u16, u16), (u16, u16))>,
}
/// 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: Read + Seek>(r: &mut R) -> Result<Document, Error> {
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<W: Write + Seek>(&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: Vec::new(),
}
}
}
#[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);
}
}