nze_tiled 0.1.0

Loads maps made with the map editor Tiled
Documentation

use std::path::{Path, PathBuf};

use super::{helper::*, LayerData, Colour};
use super::error::TiledError;
use super::Properties;
use nze_geometry::*;

use quick_xml::events::attributes::Attribute;
use quick_xml::events::{BytesStart, BytesText};
use quick_xml::reader::Reader;

pub struct ObjData {
    pub id: u32,
    pub name: String,
    pub type_name: String,
    pub visible: bool,
}

pub struct Obj {
    pub props : Properties,
    pub rect : Rect,
    pub rotation: f64,
    pub info: ObjData,
    poly : Option<Box<Poly>>,
    text : Option<Box<Text>>,
    point: bool,
    ellipse: bool,
    pub template: Option<PathBuf>,
}

pub struct Poly {
    pub points : Vec<Vec2>,
    pub obj : Obj,
    pub closed : bool,
}

pub type Point = Obj;
pub type Ellipse = Obj;

#[derive(Eq, PartialEq)]
pub enum TextHorizontalAlign {
    Left,
    Center,
    Right,
    Justify
}

#[derive(Eq, PartialEq)]
pub enum TextVerticalAlign {
    Top,
    Center,
    Bottom
}

pub struct Text {
    pub obj: Obj,
    pub text: String,
    pub font_family: String,
    pub pixel_size: u32,
    pub wrap: bool,
    pub bold: bool,
    pub italic: bool,
    pub horizontal_align : TextHorizontalAlign,
    pub vertical_align : TextVerticalAlign,
    pub colour: Colour,
}

pub struct ObjGroup {
    pub props : Properties,
    pub objs  : Vec<Obj>,
    pub polys : Vec<Poly>,
    pub points: Vec<Point>,
    pub ellipse: Vec<Ellipse>,
    pub text: Vec<Text>,
    pub info: LayerData,
    path: PathBuf,
}

impl ObjData {
    pub(crate) fn blank() -> ObjData {
        ObjData {
            id: 0,
            name: String::new(),
            type_name: String::new(),
            visible: true,
        }
    }
}

impl Obj {
    fn blank() -> Obj {
        Obj { props: Properties::blank(), rect: Rect::new(0.0, 0.0, 0.0, 0.0), info: ObjData::blank(), poly: None, text: None, rotation: 0.0, point: false, ellipse: false, template: None}
    }
    
    pub(crate) fn new(attribs : Vec<Attribute>, reader: Option<&mut Reader<&[u8]>>, path: &Path) -> Result<Obj, TiledError> {
        let mut obj = Obj::blank();
        obj.parse_attribs(attribs)?;
        if let Some(template) = &obj.template {
            let path: PathBuf = [path, template].iter().collect();
            let file = &read_file_to_string(&path)?;
            let mut reader = Reader::from_str(&file);
            parse_xml(&mut obj, &mut reader)?;
        }
        if let Some(reader) = reader {
            parse_xml(&mut obj, reader)?;
        }
        Ok(obj)
    }
    
    fn parse_attribs(&mut self, attribs : Vec<Attribute>) -> Result<(), TiledError> {
        for a in attribs {
            match a.key.as_ref() {
                b"x" => self.rect.x = get_value(&a.value)?,
                b"y" => self.rect.y = get_value(&a.value)?,
                b"width" => self.rect.w = get_value(&a.value)?,
                b"height" => self.rect.h = get_value(&a.value)?,
                b"id" => self.info.id = get_value(&a.value)?,
                b"name" => self.info.name = get_string(&a.value)?.to_string(),
                b"type" => self.info.type_name = get_string(&a.value)?.to_string(),
                b"visible" => self.info.visible = get_string(&a.value)? == "1",
                b"rotation" => self.rotation = get_value(&a.value)?,
                b"template" => self.template = Some(PathBuf::from(get_string(&a.value)?)),
                _ => println!("warning: unrecognized atrribute {:?}", a.key),
            }
        }
        Ok(())
    }
}

impl HandleXml for Obj {
    fn start(&mut self, e : &BytesStart, reader: &mut Reader<&[u8]>) -> Result<(), TiledError> {
        match e.name().as_ref() {
            b"template" => (),
            b"object" => self.parse_attribs(collect_attribs(&e)?)?,
            b"properties" => parse_xml(&mut self.props, reader)?,
            b"text" => self.text = Some(Box::new(Text::new(collect_attribs(&e)?, reader)?)),
            _ => println!("unrecognized tag {:?}", e.name()),
        }
        Ok(())
    }
    fn empty(&mut self, e : &BytesStart) -> Result<(), TiledError> {
        match e.name().as_ref() {
            b"object" => self.parse_attribs(collect_attribs(&e)?)?,
            b"polyline" => self.poly = Some(Box::new(Poly::new(collect_attribs(&e)?, false)?)),
            b"polygon" => self.poly = Some(Box::new(Poly::new(collect_attribs(&e)?, true)?)),
            b"ellipse" => self.ellipse = true,
            b"point" => self.point = true,
            _ => println!("unrecognized empty tag {:?}", e.name()),
        }
        Ok(())
    }
    fn self_tag() -> &'static str {
        "object"
    }
}

impl Poly {
    fn blank() -> Poly {
        Poly { points: Vec::new(), obj: Obj::blank(), closed: false }
    }
    fn parse_attribs(&mut self, attribs : Vec<Attribute>, closed : bool) -> Result<(), TiledError> {
        self.closed = closed;
        for a in attribs {
            match a.key.as_ref() {
                b"points" => {
                    let points = get_string(&a.value)?;
                    for pair in points.split(' ') {
                        let (x, y) = match pair.split_once(',') {
                            Some((x, y)) => match (x.parse(), y.parse()) {
                                (Ok(x), Ok(y)) => (x, y),
                                _ => { return Err(TiledError::ParseError(String::from("failed to parse poly points to floats"))); },
                            },
                            _ => { return Err(TiledError::MissingPoint()); },
                        };
                        self.points.push(Vec2::new(x, y));
                    }
                }
                _ => println!("warning: unrecognized atrribute {:?}", a.key),
            }
        }
        Ok(())
    }
    pub(crate) fn new(attribs : Vec<Attribute>, closed : bool) -> Result<Poly, TiledError> {
        let mut poly = Poly::blank();
        poly.parse_attribs(attribs, closed)?;
        Ok(poly)
    }
}

impl Text {
    fn blank() -> Text {
        Text {
            obj: Obj::blank(),
            text: String::new(),
            font_family: String::from("sans-serif"),
            pixel_size: 16,
            wrap: false, bold: false, italic: false,
            horizontal_align: TextHorizontalAlign::Left, vertical_align: TextVerticalAlign::Top,
            colour : Colour { r: 0, g: 0, b: 0, a:255 }
        }
    }
    fn parse_attribs(&mut self, attribs : Vec<Attribute>) -> Result<(), TiledError> {
        for a in attribs {
            match a.key.as_ref() {
                b"fontfamily" => self.font_family = get_string(&a.value)?.to_string(),
                b"pixelsize" => self.pixel_size = get_value(&a.value)?,
                b"wrap" => self.wrap = get_value::<i32>(&a.value)? == 1,
                b"bold" => self.bold = get_value::<i32>(&a.value)? == 1,
                b"italic" => self.italic = get_value::<i32>(&a.value)? == 1,
                b"halign" => self.horizontal_align = match get_string(&a.value)? {
                    "justify" => TextHorizontalAlign::Justify,
                    "right" => TextHorizontalAlign::Right,
                    "center" => TextHorizontalAlign::Center,
                    "left" => TextHorizontalAlign::Left,
                    _ => {
                        return Err(TiledError::ParseError(String::from("text alignment not recognized")));
                    },
                },
                b"valign" => self.vertical_align = match get_string(&a.value)? {
                    "top" => TextVerticalAlign::Top,
                    "bottom" => TextVerticalAlign::Bottom,
                    "center" => TextVerticalAlign::Center,
                    _ => {
                        return Err(TiledError::ParseError(String::from("text alignment not recognized")));
                    },
                },
                b"color" => self.colour = get_colour(&a.value)?,
                _ => println!("warning: unrecognized atrribute {:?}", a.key),
            }
        }
        Ok(())
    }
    pub(crate) fn new(attribs : Vec<Attribute>, reader: &mut Reader<&[u8]>) -> Result<Text, TiledError> {
        let mut text = Text::blank();
        text.parse_attribs(attribs)?;
        parse_xml(&mut text, reader)?;
        Ok(text)
    }
}

impl HandleXml for Text {
    fn text(&mut self, e : &BytesText) -> Result<(), TiledError> {
        let data = match e.unescape() {
            Ok(s) => s,
            Err(_) => { return Err(TiledError::ParseError(String::from("text data could not be retrieved"))); },
        };
        self.text = data.to_string();
        Ok(())
    }
    fn self_tag() -> &'static str {
        "text"
    }
}

impl ObjGroup {    
    fn blank() -> ObjGroup {
        ObjGroup {
            props: Properties::blank(),
            objs: Vec::new(),
            polys: Vec::new(),
            points: Vec::new(),
            ellipse: Vec::new(),
            text: Vec::new(),
            info: LayerData::new(),
            path: PathBuf::new(),
        }
    }
    fn parse_attribs(&mut self, attribs : Vec<Attribute>) -> Result<(), TiledError> {
        for a in attribs {
            if let Some(()) = self.info.handle_attrib(&a)? {
                println!("warning: unrecognized atrribute {:?}", a.key);
            }
        }
        Ok(())
    }
    pub(crate) fn new(attribs : Vec<Attribute>, reader: &mut Reader<&[u8]>, path: &Path, layer_index: u32) -> Result<ObjGroup, TiledError> {
        let mut og = ObjGroup::blank();
        og.path = path.to_path_buf();
        og.parse_attribs(attribs)?;
        parse_xml(&mut og, reader)?;
        let mut obj_list : Vec::<Obj> = Vec::new();
        while og.objs.len() > 0{
            let mut obj = og.objs.pop().unwrap();
            if let Some(mut poly) = obj.poly.take() {
                poly.obj = obj;
                og.polys.push(*poly);
            } else {
                if let Some(mut text) = obj.text.take() {
                    text.obj = obj;
                    og.text.push(*text);
                } else {
                    if obj.ellipse {
                        og.ellipse.push(obj);
                    } else if obj.point {
                        og.points.push(obj);
                    } else {
                        obj_list.push(obj);
                    }
                }
            }
        }
        og.objs = obj_list;
        og.info.layer_position = layer_index;
        Ok(og)
    }
}

impl HandleXml for ObjGroup {
    fn start(&mut self, e : &BytesStart, reader: &mut Reader<&[u8]>) -> Result<(), TiledError> {
        match e.name().as_ref() {
            b"properties" => parse_xml(&mut self.props, reader)?,
            b"object" => self.objs.push(Obj::new(collect_attribs(&e)?, Some(reader), &self.path)?),
            _ => println!("unrecognized tag {:?}", e.name()),
        }
        Ok(())
    }
    fn empty(&mut self, e : &BytesStart) -> Result<(), TiledError> {
        match e.name().as_ref() {
             b"object" => self.objs.push(Obj::new(collect_attribs(&e)?, None, &self.path)?),
            _ => println!("unrecognized empty tag {:?}", e.name()),
        }
        Ok(())
    }
    fn self_tag() -> &'static str {
        "objectgroup"
    }
}