use core::cell::RefCell;
use std::sync::Mutex;
use std::fs::File;
use std::io::{self, Read};
use std::fmt;
use std::path::Path;
use std::str::FromStr;
use zip::ZipArchive;
use minidom::Element;
use crate::paint_layer::PaintLayer;
#[derive(Debug, Clone, Copy)]
pub enum NodeType {
ShapeLayer,
PaintLayer,
}
impl FromStr for NodeType {
type Err = String;
fn from_str(s: &str) -> Result<NodeType, Self::Err> {
Ok(match s {
"shapelayer" => NodeType::ShapeLayer,
"paintlayer" => NodeType::PaintLayer,
other => return Err(String::from(other)),
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum CompositeOp {
Normal,
}
impl FromStr for CompositeOp {
type Err = String;
fn from_str(s: &str) -> Result<CompositeOp, Self::Err> {
Ok(match s {
"normal" => CompositeOp::Normal,
other => return Err(String::from(other)),
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum Colorspace {
Rgba,
}
impl FromStr for Colorspace {
type Err = String;
fn from_str(s: &str) -> Result<Colorspace, Self::Err> {
Ok(match s {
"RGBA" => Colorspace::Rgba,
other => return Err(String::from(other)),
})
}
}
impl fmt::Display for Colorspace {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", match self {
Colorspace::Rgba => "RGBA",
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParsedLayer {
PaintLayer([u8; 4], PaintLayer),
ShapeLayer(String),
}
#[derive(Debug, Clone)]
pub struct Layer {
name: String,
filename: String,
uuid: String,
colorspace: Option<Colorspace>,
channelflags: String,
channellockflags: Option<String>,
nodetype: NodeType,
compositeop: CompositeOp,
opacity: u8,
colorlabel: bool,
selected: bool,
intimeline: bool,
locked: bool,
visible: bool,
collapsed: bool,
onionskin: Option<u8>,
x: usize,
y: usize,
}
impl Layer {
pub fn get_name(&self) -> &str {
&self.name
}
pub fn get_position(&self) -> (usize, usize) {
(self.x, self.y)
}
}
#[derive(Debug, Clone)]
struct Image {
name: String,
description: String,
profile: String,
colorspace: Colorspace,
width: usize,
height: usize,
x_res: usize,
y_res: usize,
layers: Vec<Layer>,
}
#[derive(Debug)]
pub struct ParsedKra {
zip: Mutex<RefCell<ZipArchive<File>>>,
image: Image,
}
#[derive(Debug, Clone)]
pub struct Error {
}
impl ParsedKra {
pub fn get_name(&self) -> &str {
&self.image.name
}
pub fn get_description(&self) -> &str {
&self.image.description
}
pub fn get_profile(&self) -> &str {
&self.image.profile
}
pub fn get_colorspace(&self) -> Colorspace {
self.image.colorspace
}
pub fn get_dimensions(&self) -> (usize, usize) {
(self.image.width, self.image.height)
}
pub fn get_res(&self) -> (usize, usize) {
(self.image.x_res, self.image.y_res)
}
fn get_paint_layer(layer: &Layer, zip: &mut ZipArchive<File>, name: &str) -> io::Result<ParsedLayer> {
let default_pixel = {
let mut file = zip.by_name(&format!("{}/layers/{}.defaultpixel", name, layer.filename))?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
match content.as_slice() {
[r, g, b, a] => [r.clone(), g.clone(), b.clone(), a.clone()],
_ => return Err(io::Error::new(io::ErrorKind::Other, "wrong defaultpixel")),
}
};
let paint_layer = {
let mut file = zip.by_name(&format!("{}/layers/{}", name, layer.filename))?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
PaintLayer::parse(&content).unwrap().1
};
Ok(ParsedLayer::PaintLayer(default_pixel, paint_layer))
}
fn get_shape_layer(layer: &Layer, zip: &mut ZipArchive<File>, name: &str) -> io::Result<ParsedLayer> {
let mut file = zip.by_name(&format!("{}/layers/{}.shapelayer/content.svg", name, layer.filename))?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(ParsedLayer::ShapeLayer(content))
}
pub fn parse_layer(&self, layer: &Layer) -> io::Result<ParsedLayer> {
let zip = self.zip.lock().unwrap();
let mut zip = zip.borrow_mut();
Ok(match layer.nodetype {
NodeType::PaintLayer => ParsedKra::get_paint_layer(layer, &mut zip, &self.image.name)?,
NodeType::ShapeLayer => ParsedKra::get_shape_layer(layer, &mut zip, &self.image.name)?,
})
}
pub fn len(&self) -> usize {
self.image.layers.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Layer> {
self.image.layers.iter().rev()
}
}
#[derive(Debug)]
pub struct Kra {
zip: ZipArchive<File>,
}
impl Kra {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Kra> {
let file = File::open(path)?;
let mut zip = ZipArchive::new(file)?;
{
let mut file = zip.by_name("mimetype")?;
let mut mimetype = String::new();
file.read_to_string(&mut mimetype)?;
if mimetype != "application/x-krita" {
return Err(io::Error::new(io::ErrorKind::Other, "not a Krita file"));
}
}
Ok(Kra {
zip,
})
}
pub fn read_maindoc(mut self) -> io::Result<ParsedKra> {
let mut string = String::new();
{
let mut file = self.zip.by_name("maindoc.xml")?;
file.read_to_string(&mut string)?;
}
let doc: Element = string.parse().unwrap();
let image = Kra::parse_maindoc(&doc)?;
Ok(ParsedKra {
zip: Mutex::new(RefCell::new(self.zip)),
image,
})
}
fn parse_maindoc(doc: &Element) -> io::Result<Image> {
assert!(doc.is("DOC", "http://www.calligra.org/DTD/krita"));
assert_eq!(doc.attr("syntaxVersion"), Some("2"));
let image = doc.get_child("IMAGE", "http://www.calligra.org/DTD/krita").unwrap();
Ok(Kra::parse_image(image)?)
}
fn parse_image(image: &Element) -> io::Result<Image> {
assert!(image.is("IMAGE", "http://www.calligra.org/DTD/krita"));
assert_eq!(image.attr("mime").unwrap(), "application/x-kra");
let name = image.attr("name").unwrap().to_owned();
let description = image.attr("description").unwrap().to_owned();
let profile = image.attr("profile").unwrap().to_owned();
let colorspace = image.attr("colorspacename").map(|x| x.parse().unwrap()).unwrap();
let width = image.attr("width").map(|x| x.parse().unwrap()).unwrap();
let height = image.attr("height").map(|x| x.parse().unwrap()).unwrap();
let x_res = image.attr("x-res").map(|x| x.parse().unwrap()).unwrap();
let y_res = image.attr("y-res").map(|x| x.parse().unwrap()).unwrap();
Ok(Image {
name,
description,
profile,
colorspace,
width,
height,
x_res,
y_res,
layers: image
.get_child("layers", "http://www.calligra.org/DTD/krita")
.unwrap()
.children()
.map(Kra::parse_layer)
.collect::<Result<_, _>>()?,
})
}
fn parse_layer(layer: &Element) -> io::Result<Layer> {
assert!(layer.is("layer", "http://www.calligra.org/DTD/krita"));
let name = layer.attr("name").unwrap().to_owned();
let filename = layer.attr("filename").unwrap().to_owned();
let uuid = layer.attr("uuid").unwrap().to_owned();
let colorspace = layer.attr("colorspacename").map(|x| x.parse().unwrap());
let channelflags = layer.attr("channelflags").unwrap().to_owned();
let channellockflags = layer.attr("channellockflags").map(str::to_owned);
let nodetype = layer.attr("nodetype").map(|x| x.parse().unwrap()).unwrap();
let compositeop = layer.attr("compositeop").map(|x| x.parse().unwrap()).unwrap();
let opacity = layer.attr("opacity").map(|x| x.parse().unwrap()).unwrap();
let colorlabel = layer.attr("colorlabel").map(|x| x.parse::<u8>().unwrap()).unwrap() != 0;
let selected = layer.attr("selected") == Some("true");
let intimeline = layer.attr("intimeline").map(|x| x.parse::<u8>().unwrap()).unwrap() != 0;
let locked = layer.attr("locked").map(|x| x.parse::<u8>().unwrap()).unwrap() != 0;
let visible = layer.attr("visible").map(|x| x.parse::<u8>().unwrap()).unwrap() != 0;
let collapsed = layer.attr("collapsed").map(|x| x.parse::<u8>().unwrap()).unwrap() != 0;
let onionskin = layer.attr("onionskin").map(|x| x.parse::<u8>().unwrap());
let x = layer.attr("x").map(|x| x.parse().unwrap()).unwrap();
let y = layer.attr("y").map(|x| x.parse().unwrap()).unwrap();
Ok(Layer {
name,
filename,
uuid,
colorspace,
channelflags,
channellockflags,
nodetype,
compositeop,
opacity,
colorlabel,
selected,
intimeline,
locked,
visible,
collapsed,
onionskin,
x,
y,
})
}
}