diff --git a/.gitignore b/.gitignore index cc6f63bf21caf612dc88be30e057ca23d338cdcd..6cd496af8ddd31e1e7419371d9ff55647c49eb5f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target Cargo.lock *.swp *.dll +.vscode \ No newline at end of file diff --git a/src/animation.rs b/src/animation.rs new file mode 100644 index 0000000000000000000000000000000000000000..1ffe8802ac24a87e8a05f30c2bb30c4e587bf796 --- /dev/null +++ b/src/animation.rs @@ -0,0 +1,27 @@ +use xml::attribute::OwnedAttribute; + +use crate::{error::TiledError, util::get_attrs}; + +#[derive(Debug, PartialEq, Clone)] +pub struct Frame { + pub tile_id: u32, + pub duration: u32, +} + +impl Frame { + pub(crate) fn new(attrs: Vec<OwnedAttribute>) -> Result<Frame, TiledError> { + let ((), (tile_id, duration)) = get_attrs!( + attrs, + optionals: [], + required: [ + ("tileid", tile_id, |v:String| v.parse().ok()), + ("duration", duration, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("A frame must have tileid and duration".to_string()) + ); + Ok(Frame { + tile_id: tile_id, + duration: duration, + }) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000000000000000000000000000000000000..6a502c21c01edaa186ad2a9cca205ee7c963e74f --- /dev/null +++ b/src/error.rs @@ -0,0 +1,49 @@ +use std::fmt; + +#[derive(Debug, Copy, Clone)] +pub enum ParseTileError { + ColourError, + OrientationError, +} + +/// Errors which occured when parsing the file +#[derive(Debug)] +pub enum TiledError { + /// A attribute was missing, had the wrong type of wasn't formated + /// correctly. + MalformedAttributes(String), + /// An error occured when decompressing using the + /// [flate2](https://github.com/alexcrichton/flate2-rs) crate. + DecompressingError(std::io::Error), + Base64DecodingError(base64::DecodeError), + XmlDecodingError(xml::reader::Error), + PrematureEnd(String), + Other(String), +} + +impl fmt::Display for TiledError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + TiledError::MalformedAttributes(ref s) => write!(fmt, "{}", s), + TiledError::DecompressingError(ref e) => write!(fmt, "{}", e), + TiledError::Base64DecodingError(ref e) => write!(fmt, "{}", e), + TiledError::XmlDecodingError(ref e) => write!(fmt, "{}", e), + TiledError::PrematureEnd(ref e) => write!(fmt, "{}", e), + TiledError::Other(ref s) => write!(fmt, "{}", s), + } + } +} + +// This is a skeleton implementation, which should probably be extended in the future. +impl std::error::Error for TiledError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + TiledError::MalformedAttributes(_) => None, + TiledError::DecompressingError(ref e) => Some(e as &dyn std::error::Error), + TiledError::Base64DecodingError(ref e) => Some(e as &dyn std::error::Error), + TiledError::XmlDecodingError(ref e) => Some(e as &dyn std::error::Error), + TiledError::PrematureEnd(_) => None, + TiledError::Other(_) => None, + } + } +} diff --git a/src/image.rs b/src/image.rs new file mode 100644 index 0000000000000000000000000000000000000000..351cc5bf61ebf59f25543a9ed454780bb4fecc0b --- /dev/null +++ b/src/image.rs @@ -0,0 +1,42 @@ +use std::io::Read; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{error::TiledError, properties::Colour, util::*}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Image { + /// The filepath of the image + pub source: String, + pub width: i32, + pub height: i32, + pub transparent_colour: Option<Colour>, +} + +impl Image { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + ) -> Result<Image, TiledError> { + let (c, (s, w, h)) = get_attrs!( + attrs, + optionals: [ + ("trans", trans, |v:String| v.parse().ok()), + ], + required: [ + ("source", source, |v| Some(v)), + ("width", width, |v:String| v.parse().ok()), + ("height", height, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("image must have a source, width and height with correct types".to_string()) + ); + + parse_tag!(parser, "image", { "" => |_| Ok(()) }); + Ok(Image { + source: s, + width: w, + height: h, + transparent_colour: c, + }) + } +} diff --git a/src/layers.rs b/src/layers.rs new file mode 100644 index 0000000000000000000000000000000000000000..b802c569add9753496fd1fb37a210bf5ce313eb5 --- /dev/null +++ b/src/layers.rs @@ -0,0 +1,208 @@ +use std::{collections::HashMap, io::Read}; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{ + error::TiledError, + image::Image, + properties::{parse_properties, Properties}, + util::*, +}; + +/// Stores the proper tile gid, along with how it is flipped. +// Maybe PartialEq and Eq should be custom, so that it ignores tile-flipping? +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LayerTile { + pub gid: u32, + pub flip_h: bool, + pub flip_v: bool, + pub flip_d: bool, +} + +const FLIPPED_HORIZONTALLY_FLAG: u32 = 0x80000000; +const FLIPPED_VERTICALLY_FLAG: u32 = 0x40000000; +const FLIPPED_DIAGONALLY_FLAG: u32 = 0x20000000; +const ALL_FLIP_FLAGS: u32 = + FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG; + +impl LayerTile { + pub fn new(id: u32) -> LayerTile { + let flags = id & ALL_FLIP_FLAGS; + let gid = id & !ALL_FLIP_FLAGS; + let flip_d = flags & FLIPPED_DIAGONALLY_FLAG == FLIPPED_DIAGONALLY_FLAG; // Swap x and y axis (anti-diagonally) [flips over y = -x line] + let flip_h = flags & FLIPPED_HORIZONTALLY_FLAG == FLIPPED_HORIZONTALLY_FLAG; // Flip tile over y axis + let flip_v = flags & FLIPPED_VERTICALLY_FLAG == FLIPPED_VERTICALLY_FLAG; // Flip tile over x axis + + LayerTile { + gid, + flip_h, + flip_v, + flip_d, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Layer { + pub name: String, + pub opacity: f32, + pub visible: bool, + pub offset_x: f32, + pub offset_y: f32, + /// The tiles are arranged in rows. Each tile is a number which can be used + /// to find which tileset it belongs to and can then be rendered. + pub tiles: LayerData, + pub properties: Properties, + pub layer_index: u32, +} + +impl Layer { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + width: u32, + layer_index: u32, + infinite: bool, + ) -> Result<Layer, TiledError> { + let ((o, v, ox, oy), n) = get_attrs!( + attrs, + optionals: [ + ("opacity", opacity, |v:String| v.parse().ok()), + ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), + ("offsetx", offset_x, |v:String| v.parse().ok()), + ("offsety", offset_y, |v:String| v.parse().ok()), + ], + required: [ + ("name", name, |v| Some(v)), + ], + TiledError::MalformedAttributes("layer must have a name".to_string()) + ); + let mut tiles: LayerData = LayerData::Finite(Default::default()); + let mut properties = HashMap::new(); + parse_tag!(parser, "layer", { + "data" => |attrs| { + if infinite { + tiles = parse_infinite_data(parser, attrs, width)?; + } else { + tiles = parse_data(parser, attrs, width)?; + } + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + + Ok(Layer { + name: n, + opacity: o.unwrap_or(1.0), + visible: v.unwrap_or(true), + offset_x: ox.unwrap_or(0.0), + offset_y: oy.unwrap_or(0.0), + tiles: tiles, + properties: properties, + layer_index, + }) + } +} +#[derive(Debug, PartialEq, Clone)] +pub enum LayerData { + Finite(Vec<Vec<LayerTile>>), + Infinite(HashMap<(i32, i32), Chunk>), +} + +#[derive(Debug, PartialEq, Clone)] +pub struct ImageLayer { + pub name: String, + pub opacity: f32, + pub visible: bool, + pub offset_x: f32, + pub offset_y: f32, + pub image: Option<Image>, + pub properties: Properties, + pub layer_index: u32, +} + +impl ImageLayer { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + layer_index: u32, + ) -> Result<ImageLayer, TiledError> { + let ((o, v, ox, oy), n) = get_attrs!( + attrs, + optionals: [ + ("opacity", opacity, |v:String| v.parse().ok()), + ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), + ("offsetx", offset_x, |v:String| v.parse().ok()), + ("offsety", offset_y, |v:String| v.parse().ok()), + ], + required: [ + ("name", name, |v| Some(v)), + ], + TiledError::MalformedAttributes("layer must have a name".to_string())); + let mut properties = HashMap::new(); + let mut image: Option<Image> = None; + parse_tag!(parser, "imagelayer", { + "image" => |attrs| { + image = Some(Image::new(parser, attrs)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + Ok(ImageLayer { + name: n, + opacity: o.unwrap_or(1.0), + visible: v.unwrap_or(true), + offset_x: ox.unwrap_or(0.0), + offset_y: oy.unwrap_or(0.0), + image, + properties, + layer_index, + }) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Chunk { + pub x: i32, + pub y: i32, + pub width: u32, + pub height: u32, + pub tiles: Vec<Vec<LayerTile>>, +} + +impl Chunk { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + encoding: Option<String>, + compression: Option<String>, + ) -> Result<Chunk, TiledError> { + let ((), (x, y, width, height)) = get_attrs!( + attrs, + optionals: [], + required: [ + ("x", x, |v: String| v.parse().ok()), + ("y", y, |v: String| v.parse().ok()), + ("width", width, |v: String| v.parse().ok()), + ("height", height, |v: String| v.parse().ok()), + ], + TiledError::MalformedAttributes("layer must have a name".to_string()) + ); + + let tiles = parse_data_line(encoding, compression, parser, width)?; + + Ok(Chunk { + x, + y, + width, + height, + tiles, + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 33364a369526de0613d013ed1943304d61590235..06805e70b8daa4ce9e7102a1f65b57bc57d54e91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,1295 +1,34 @@ +pub mod animation; +pub mod error; +pub mod image; +pub mod layers; +pub mod map; +pub mod objects; +pub mod properties; +pub mod tile; +pub mod tileset; +mod util; + use base64; +use error::*; +use image::*; +use layers::*; +use map::*; use std::collections::HashMap; use std::fmt; use std::fs::File; -use std::io::{BufReader, Error, Read}; -use std::path::Path; +use std::io::Read; +use std::path::{Path, PathBuf}; use std::str::FromStr; +use tile::*; +use tileset::*; +use util::*; use xml::attribute::OwnedAttribute; use xml::reader::XmlEvent; use xml::reader::{Error as XmlError, EventReader}; -#[derive(Debug, Copy, Clone)] -pub enum ParseTileError { - ColourError, - OrientationError, -} - -// Loops through the attributes once and pulls out the ones we ask it to. It -// will check that the required ones are there. This could have been done with -// attrs.find but that would be inefficient. -// -// This is probably a really terrible way to do this. It does cut down on lines -// though which is nice. -macro_rules! get_attrs { - ($attrs:expr, optionals: [$(($oName:pat, $oVar:ident, $oMethod:expr)),* $(,)*], - required: [$(($name:pat, $var:ident, $method:expr)),* $(,)*], $err:expr) => { - { - $(let mut $oVar = None;)* - $(let mut $var = None;)* - for attr in $attrs.iter() { - match attr.name.local_name.as_ref() { - $($oName => $oVar = $oMethod(attr.value.clone()),)* - $($name => $var = $method(attr.value.clone()),)* - _ => {} - } - } - if !(true $(&& $var.is_some())*) { - return Err($err); - } - (($($oVar),*), ($($var.unwrap()),*)) - } - } -} - -// Goes through the children of the tag and will call the correct function for -// that child. Closes the tag -// -// Not quite as bad. -macro_rules! parse_tag { - ($parser:expr, $close_tag:expr, {$($open_tag:expr => $open_method:expr),* $(,)*}) => { - loop { - match $parser.next().map_err(TiledError::XmlDecodingError)? { - XmlEvent::StartElement {name, attributes, ..} => { - if false {} - $(else if name.local_name == $open_tag { - match $open_method(attributes) { - Ok(()) => {}, - Err(e) => return Err(e) - }; - })* - } - XmlEvent::EndElement {name, ..} => { - if name.local_name == $close_tag { - break; - } - } - XmlEvent::EndDocument => return Err(TiledError::PrematureEnd("Document ended before we expected.".to_string())), - _ => {} - } - } - } -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct Colour { - pub red: u8, - pub green: u8, - pub blue: u8, -} - -impl FromStr for Colour { - type Err = ParseTileError; - - fn from_str(s: &str) -> Result<Colour, ParseTileError> { - let s = if s.starts_with("#") { &s[1..] } else { s }; - if s.len() != 6 { - return Err(ParseTileError::ColourError); - } - let r = u8::from_str_radix(&s[0..2], 16); - let g = u8::from_str_radix(&s[2..4], 16); - let b = u8::from_str_radix(&s[4..6], 16); - if r.is_ok() && g.is_ok() && b.is_ok() { - return Ok(Colour { - red: r.unwrap(), - green: g.unwrap(), - blue: b.unwrap(), - }); - } - Err(ParseTileError::ColourError) - } -} - -/// Errors which occured when parsing the file -#[derive(Debug)] -pub enum TiledError { - /// A attribute was missing, had the wrong type of wasn't formated - /// correctly. - MalformedAttributes(String), - /// An error occured when decompressing using the - /// [flate2](https://github.com/alexcrichton/flate2-rs) crate. - DecompressingError(Error), - Base64DecodingError(base64::DecodeError), - XmlDecodingError(XmlError), - PrematureEnd(String), - Other(String), -} - -impl fmt::Display for TiledError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match *self { - TiledError::MalformedAttributes(ref s) => write!(fmt, "{}", s), - TiledError::DecompressingError(ref e) => write!(fmt, "{}", e), - TiledError::Base64DecodingError(ref e) => write!(fmt, "{}", e), - TiledError::XmlDecodingError(ref e) => write!(fmt, "{}", e), - TiledError::PrematureEnd(ref e) => write!(fmt, "{}", e), - TiledError::Other(ref s) => write!(fmt, "{}", s), - } - } -} - -// This is a skeleton implementation, which should probably be extended in the future. -impl std::error::Error for TiledError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match *self { - TiledError::MalformedAttributes(_) => None, - TiledError::DecompressingError(ref e) => Some(e as &dyn std::error::Error), - TiledError::Base64DecodingError(ref e) => Some(e as &dyn std::error::Error), - TiledError::XmlDecodingError(ref e) => Some(e as &dyn std::error::Error), - TiledError::PrematureEnd(_) => None, - TiledError::Other(_) => None, - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum PropertyValue { - BoolValue(bool), - FloatValue(f32), - IntValue(i32), - ColorValue(u32), - StringValue(String), - /// Holds the path relative to the map or tileset - FileValue(String), -} - -impl PropertyValue { - fn new(property_type: String, value: String) -> Result<PropertyValue, TiledError> { - // Check the property type against the value. - match property_type.as_str() { - "bool" => match value.parse() { - Ok(val) => Ok(PropertyValue::BoolValue(val)), - Err(err) => Err(TiledError::Other(err.to_string())), - }, - "float" => match value.parse() { - Ok(val) => Ok(PropertyValue::FloatValue(val)), - Err(err) => Err(TiledError::Other(err.to_string())), - }, - "int" => match value.parse() { - Ok(val) => Ok(PropertyValue::IntValue(val)), - Err(err) => Err(TiledError::Other(err.to_string())), - }, - "color" if value.len() > 1 => match u32::from_str_radix(&value[1..], 16) { - Ok(color) => Ok(PropertyValue::ColorValue(color)), - Err(_) => Err(TiledError::Other(format!( - "Improperly formatted color property" - ))), - }, - "string" => Ok(PropertyValue::StringValue(value)), - "file" => Ok(PropertyValue::FileValue(value)), - _ => Err(TiledError::Other(format!( - "Unknown property type \"{}\"", - property_type - ))), - } - } -} - -pub type Properties = HashMap<String, PropertyValue>; - -fn parse_properties<R: Read>(parser: &mut EventReader<R>) -> Result<Properties, TiledError> { - let mut p = HashMap::new(); - parse_tag!(parser, "properties", { - "property" => |attrs:Vec<OwnedAttribute>| { - let (t, (k, v)) = get_attrs!( - attrs, - optionals: [ - ("type", property_type, |v| Some(v)), - ], - required: [ - ("name", key, |v| Some(v)), - ("value", value, |v| Some(v)), - ], - TiledError::MalformedAttributes("property must have a name and a value".to_string()) - ); - let t = t.unwrap_or("string".into()); - - p.insert(k, PropertyValue::new(t, v)?); - Ok(()) - }, - }); - Ok(p) -} - -/// All Tiled files will be parsed into this. Holds all the layers and tilesets -#[derive(Debug, PartialEq, Clone)] -pub struct Map { - pub version: String, - pub orientation: Orientation, - /// Width of the map, in tiles - pub width: u32, - /// Height of the map, in tiles - pub height: u32, - pub tile_width: u32, - pub tile_height: u32, - pub tilesets: Vec<Tileset>, - pub layers: Vec<Layer>, - pub image_layers: Vec<ImageLayer>, - pub object_groups: Vec<ObjectGroup>, - pub properties: Properties, - pub background_colour: Option<Colour>, - pub infinite: bool, -} - -impl Map { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - map_path: Option<&Path>, - ) -> Result<Map, TiledError> { - let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!( - attrs, - optionals: [ - ("backgroundcolor", colour, |v:String| v.parse().ok()), - ("infinite", infinite, |v:String| Some(v == "1")), - ], - required: [ - ("version", version, |v| Some(v)), - ("orientation", orientation, |v:String| v.parse().ok()), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ("tilewidth", tile_width, |v:String| v.parse().ok()), - ("tileheight", tile_height, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("map must have a version, width and height with correct types".to_string()) - ); - - let mut tilesets = Vec::new(); - let mut layers = Vec::new(); - let mut image_layers = Vec::new(); - let mut properties = HashMap::new(); - let mut object_groups = Vec::new(); - let mut layer_index = 0; - parse_tag!(parser, "map", { - "tileset" => | attrs| { - tilesets.push(Tileset::new(parser, attrs, map_path)?); - Ok(()) - }, - "layer" => |attrs| { - layers.push(Layer::new(parser, attrs, w, layer_index, infinite.unwrap_or(false))?); - layer_index += 1; - Ok(()) - }, - "imagelayer" => |attrs| { - image_layers.push(ImageLayer::new(parser, attrs, layer_index)?); - layer_index += 1; - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - "objectgroup" => |attrs| { - object_groups.push(ObjectGroup::new(parser, attrs, Some(layer_index))?); - layer_index += 1; - Ok(()) - }, - }); - Ok(Map { - version: v, - orientation: o, - width: w, - height: h, - tile_width: tw, - tile_height: th, - tilesets, - layers, - image_layers, - object_groups, - properties, - background_colour: c, - infinite: infinite.unwrap_or(false), - }) - } - - /// This function will return the correct Tileset given a GID. - pub fn get_tileset_by_gid(&self, gid: u32) -> Option<&Tileset> { - let mut maximum_gid: i32 = -1; - let mut maximum_ts = None; - for tileset in self.tilesets.iter() { - if tileset.first_gid as i32 > maximum_gid && tileset.first_gid <= gid { - maximum_gid = tileset.first_gid as i32; - maximum_ts = Some(tileset); - } - } - maximum_ts - } -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub enum Orientation { - Orthogonal, - Isometric, - Staggered, - Hexagonal, -} - -impl FromStr for Orientation { - type Err = ParseTileError; - - fn from_str(s: &str) -> Result<Orientation, ParseTileError> { - match s { - "orthogonal" => Ok(Orientation::Orthogonal), - "isometric" => Ok(Orientation::Isometric), - "staggered" => Ok(Orientation::Staggered), - "hexagonal" => Ok(Orientation::Hexagonal), - _ => Err(ParseTileError::OrientationError), - } - } -} - -impl fmt::Display for Orientation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Orientation::Orthogonal => write!(f, "orthogonal"), - Orientation::Isometric => write!(f, "isometric"), - Orientation::Staggered => write!(f, "staggered"), - Orientation::Hexagonal => write!(f, "hexagonal"), - } - } -} - -/// A tileset, usually the tilesheet image. -#[derive(Debug, PartialEq, Clone)] -pub struct Tileset { - /// The GID of the first tile stored - pub first_gid: u32, - pub name: String, - pub tile_width: u32, - pub tile_height: u32, - pub spacing: u32, - pub margin: u32, - pub tilecount: Option<u32>, - /// The Tiled spec says that a tileset can have mutliple images so a `Vec` - /// is used. Usually you will only use one. - pub images: Vec<Image>, - pub tiles: Vec<Tile>, - pub properties: Properties, -} - -impl Tileset { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - map_path: Option<&Path>, - ) -> Result<Tileset, TiledError> { - Tileset::new_internal(parser, &attrs).or_else(|_| Tileset::new_reference(&attrs, map_path)) - } - - fn new_internal<R: Read>( - parser: &mut EventReader<R>, - attrs: &Vec<OwnedAttribute>, - ) -> Result<Tileset, TiledError> { - let ((spacing, margin, tilecount), (first_gid, name, width, height)) = get_attrs!( - attrs, - optionals: [ - ("spacing", spacing, |v:String| v.parse().ok()), - ("margin", margin, |v:String| v.parse().ok()), - ("tilecount", tilecount, |v:String| v.parse().ok()), - ], - required: [ - ("firstgid", first_gid, |v:String| v.parse().ok()), - ("name", name, |v| Some(v)), - ("tilewidth", width, |v:String| v.parse().ok()), - ("tileheight", height, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) - ); - - let mut images = Vec::new(); - let mut tiles = Vec::new(); - let mut properties = HashMap::new(); - parse_tag!(parser, "tileset", { - "image" => |attrs| { - images.push(Image::new(parser, attrs)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - "tile" => |attrs| { - tiles.push(Tile::new(parser, attrs)?); - Ok(()) - }, - }); - - Ok(Tileset { - tile_width: width, - tile_height: height, - spacing: spacing.unwrap_or(0), - margin: margin.unwrap_or(0), - first_gid, - name, - tilecount, - images, - tiles, - properties, - }) - } - - fn new_reference( - attrs: &Vec<OwnedAttribute>, - map_path: Option<&Path>, - ) -> Result<Tileset, TiledError> { - let ((), (first_gid, source)) = get_attrs!( - attrs, - optionals: [], - required: [ - ("firstgid", first_gid, |v:String| v.parse().ok()), - ("source", name, |v| Some(v)), - ], - TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) - ); - - let tileset_path = map_path.ok_or(TiledError::Other("Maps with external tilesets must know their file location. See parse_with_path(Path).".to_string()))?.with_file_name(source); - let file = File::open(&tileset_path).map_err(|_| { - TiledError::Other(format!( - "External tileset file not found: {:?}", - tileset_path - )) - })?; - Tileset::new_external(file, first_gid) - } - - fn new_external<R: Read>(file: R, first_gid: u32) -> Result<Tileset, TiledError> { - let mut tileset_parser = EventReader::new(file); - loop { - match tileset_parser - .next() - .map_err(TiledError::XmlDecodingError)? - { - XmlEvent::StartElement { - name, attributes, .. - } => { - if name.local_name == "tileset" { - return Tileset::parse_external_tileset( - first_gid, - &mut tileset_parser, - &attributes, - ); - } - } - XmlEvent::EndDocument => { - return Err(TiledError::PrematureEnd( - "Tileset Document ended before map was parsed".to_string(), - )) - } - _ => {} - } - } - } - - fn parse_external_tileset<R: Read>( - first_gid: u32, - parser: &mut EventReader<R>, - attrs: &Vec<OwnedAttribute>, - ) -> Result<Tileset, TiledError> { - let ((spacing, margin, tilecount), (name, width, height)) = get_attrs!( - attrs, - optionals: [ - ("spacing", spacing, |v:String| v.parse().ok()), - ("margin", margin, |v:String| v.parse().ok()), - ("tilecount", tilecount, |v:String| v.parse().ok()), - ], - required: [ - ("name", name, |v| Some(v)), - ("tilewidth", width, |v:String| v.parse().ok()), - ("tileheight", height, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) - ); - - let mut images = Vec::new(); - let mut tiles = Vec::new(); - let mut properties = HashMap::new(); - parse_tag!(parser, "tileset", { - "image" => |attrs| { - images.push(Image::new(parser, attrs)?); - Ok(()) - }, - "tile" => |attrs| { - tiles.push(Tile::new(parser, attrs)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - }); - - Ok(Tileset { - first_gid: first_gid, - name: name, - tile_width: width, - tile_height: height, - spacing: spacing.unwrap_or(0), - margin: margin.unwrap_or(0), - tilecount: tilecount, - images: images, - tiles: tiles, - properties, - }) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Tile { - pub id: u32, - pub images: Vec<Image>, - pub properties: Properties, - pub objectgroup: Option<ObjectGroup>, - pub animation: Option<Vec<Frame>>, - pub tile_type: Option<String>, - pub probability: f32, -} - -impl Tile { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - ) -> Result<Tile, TiledError> { - let ((tile_type, probability), id) = get_attrs!( - attrs, - optionals: [ - ("type", tile_type, |v:String| v.parse().ok()), - ("probability", probability, |v:String| v.parse().ok()), - ], - required: [ - ("id", id, |v:String| v.parse::<u32>().ok()), - ], - TiledError::MalformedAttributes("tile must have an id with the correct type".to_string()) - ); - - let mut images = Vec::new(); - let mut properties = HashMap::new(); - let mut objectgroup = None; - let mut animation = None; - parse_tag!(parser, "tile", { - "image" => |attrs| { - images.push(Image::new(parser, attrs)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - "objectgroup" => |attrs| { - objectgroup = Some(ObjectGroup::new(parser, attrs, None)?); - Ok(()) - }, - "animation" => |_| { - animation = Some(parse_animation(parser)?); - Ok(()) - }, - }); - Ok(Tile { - id, - images, - properties, - objectgroup, - animation, - tile_type, - probability: probability.unwrap_or(1.0), - }) - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Image { - /// The filepath of the image - pub source: String, - pub width: i32, - pub height: i32, - pub transparent_colour: Option<Colour>, -} - -impl Image { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - ) -> Result<Image, TiledError> { - let (c, (s, w, h)) = get_attrs!( - attrs, - optionals: [ - ("trans", trans, |v:String| v.parse().ok()), - ], - required: [ - ("source", source, |v| Some(v)), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("image must have a source, width and height with correct types".to_string()) - ); - - parse_tag!(parser, "image", { "" => |_| Ok(()) }); - Ok(Image { - source: s, - width: w, - height: h, - transparent_colour: c, - }) - } -} - -/// Stores the proper tile gid, along with how it is flipped. -// Maybe PartialEq and Eq should be custom, so that it ignores tile-flipping? -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct LayerTile { - pub gid: u32, - pub flip_h: bool, - pub flip_v: bool, - pub flip_d: bool, -} - -const FLIPPED_HORIZONTALLY_FLAG: u32 = 0x80000000; -const FLIPPED_VERTICALLY_FLAG: u32 = 0x40000000; -const FLIPPED_DIAGONALLY_FLAG: u32 = 0x20000000; -const ALL_FLIP_FLAGS: u32 = - FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG; - -impl LayerTile { - pub fn new(id: u32) -> LayerTile { - let flags = id & ALL_FLIP_FLAGS; - let gid = id & !ALL_FLIP_FLAGS; - let flip_d = flags & FLIPPED_DIAGONALLY_FLAG == FLIPPED_DIAGONALLY_FLAG; // Swap x and y axis (anti-diagonally) [flips over y = -x line] - let flip_h = flags & FLIPPED_HORIZONTALLY_FLAG == FLIPPED_HORIZONTALLY_FLAG; // Flip tile over y axis - let flip_v = flags & FLIPPED_VERTICALLY_FLAG == FLIPPED_VERTICALLY_FLAG; // Flip tile over x axis - - LayerTile { - gid, - flip_h, - flip_v, - flip_d, - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Layer { - pub name: String, - pub opacity: f32, - pub visible: bool, - pub offset_x: f32, - pub offset_y: f32, - /// The tiles are arranged in rows. Each tile is a number which can be used - /// to find which tileset it belongs to and can then be rendered. - pub tiles: LayerData, - pub properties: Properties, - pub layer_index: u32, -} - -impl Layer { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - width: u32, - layer_index: u32, - infinite: bool, - ) -> Result<Layer, TiledError> { - let ((o, v, ox, oy), n) = get_attrs!( - attrs, - optionals: [ - ("opacity", opacity, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("offsetx", offset_x, |v:String| v.parse().ok()), - ("offsety", offset_y, |v:String| v.parse().ok()), - ], - required: [ - ("name", name, |v| Some(v)), - ], - TiledError::MalformedAttributes("layer must have a name".to_string()) - ); - let mut tiles: LayerData = LayerData::Finite(Default::default()); - let mut properties = HashMap::new(); - parse_tag!(parser, "layer", { - "data" => |attrs| { - if infinite { - tiles = parse_infinite_data(parser, attrs, width)?; - } else { - tiles = parse_data(parser, attrs, width)?; - } - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - }); - - Ok(Layer { - name: n, - opacity: o.unwrap_or(1.0), - visible: v.unwrap_or(true), - offset_x: ox.unwrap_or(0.0), - offset_y: oy.unwrap_or(0.0), - tiles: tiles, - properties: properties, - layer_index, - }) - } -} -#[derive(Debug, PartialEq, Clone)] -pub enum LayerData { - Finite(Vec<Vec<LayerTile>>), - Infinite(HashMap<(i32, i32), Chunk>), -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Chunk { - pub x: i32, - pub y: i32, - pub width: u32, - pub height: u32, - pub tiles: Vec<Vec<LayerTile>>, -} - -impl Chunk { - pub(crate) fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - encoding: Option<String>, - compression: Option<String>, - ) -> Result<Chunk, TiledError> { - let ((), (x, y, width, height)) = get_attrs!( - attrs, - optionals: [], - required: [ - ("x", x, |v: String| v.parse().ok()), - ("y", y, |v: String| v.parse().ok()), - ("width", width, |v: String| v.parse().ok()), - ("height", height, |v: String| v.parse().ok()), - ], - TiledError::MalformedAttributes("layer must have a name".to_string()) - ); - - let tiles = parse_data_line(encoding, compression, parser, width)?; - - Ok(Chunk { - x, - y, - width, - height, - tiles, - }) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ImageLayer { - pub name: String, - pub opacity: f32, - pub visible: bool, - pub offset_x: f32, - pub offset_y: f32, - pub image: Option<Image>, - pub properties: Properties, - pub layer_index: u32, -} - -impl ImageLayer { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - layer_index: u32, - ) -> Result<ImageLayer, TiledError> { - let ((o, v, ox, oy), n) = get_attrs!( - attrs, - optionals: [ - ("opacity", opacity, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("offsetx", offset_x, |v:String| v.parse().ok()), - ("offsety", offset_y, |v:String| v.parse().ok()), - ], - required: [ - ("name", name, |v| Some(v)), - ], - TiledError::MalformedAttributes("layer must have a name".to_string())); - let mut properties = HashMap::new(); - let mut image: Option<Image> = None; - parse_tag!(parser, "imagelayer", { - "image" => |attrs| { - image = Some(Image::new(parser, attrs)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - }); - Ok(ImageLayer { - name: n, - opacity: o.unwrap_or(1.0), - visible: v.unwrap_or(true), - offset_x: ox.unwrap_or(0.0), - offset_y: oy.unwrap_or(0.0), - image, - properties, - layer_index, - }) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct ObjectGroup { - pub name: String, - pub opacity: f32, - pub visible: bool, - pub objects: Vec<Object>, - pub colour: Option<Colour>, - /** - * Layer index is not preset for tile collision boxes - */ - pub layer_index: Option<u32>, - pub properties: Properties, -} - -impl ObjectGroup { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - layer_index: Option<u32>, - ) -> Result<ObjectGroup, TiledError> { - let ((o, v, c, n), ()) = get_attrs!( - attrs, - optionals: [ - ("opacity", opacity, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("color", colour, |v:String| v.parse().ok()), - ("name", name, |v:String| v.into()), - ], - required: [], - TiledError::MalformedAttributes("object groups must have a name".to_string()) - ); - let mut objects = Vec::new(); - let mut properties = HashMap::new(); - parse_tag!(parser, "objectgroup", { - "object" => |attrs| { - objects.push(Object::new(parser, attrs)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - }); - Ok(ObjectGroup { - name: n.unwrap_or(String::new()), - opacity: o.unwrap_or(1.0), - visible: v.unwrap_or(true), - objects: objects, - colour: c, - layer_index, - properties, - }) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ObjectShape { - Rect { width: f32, height: f32 }, - Ellipse { width: f32, height: f32 }, - Polyline { points: Vec<(f32, f32)> }, - Polygon { points: Vec<(f32, f32)> }, - Point(f32, f32), -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Object { - pub id: u32, - pub gid: u32, - pub name: String, - pub obj_type: String, - pub width: f32, - pub height: f32, - pub x: f32, - pub y: f32, - pub rotation: f32, - pub visible: bool, - pub shape: ObjectShape, - pub properties: Properties, -} - -impl Object { - fn new<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - ) -> Result<Object, TiledError> { - let ((id, gid, n, t, w, h, v, r), (x, y)) = get_attrs!( - attrs, - optionals: [ - ("id", id, |v:String| v.parse().ok()), - ("gid", gid, |v:String| v.parse().ok()), - ("name", name, |v:String| v.parse().ok()), - ("type", obj_type, |v:String| v.parse().ok()), - ("width", width, |v:String| v.parse().ok()), - ("height", height, |v:String| v.parse().ok()), - ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), - ("rotation", rotation, |v:String| v.parse().ok()), - ], - required: [ - ("x", x, |v:String| v.parse().ok()), - ("y", y, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("objects must have an x and a y number".to_string()) - ); - let v = v.unwrap_or(true); - let w = w.unwrap_or(0f32); - let h = h.unwrap_or(0f32); - let r = r.unwrap_or(0f32); - let id = id.unwrap_or(0u32); - let gid = gid.unwrap_or(0u32); - let n = n.unwrap_or(String::new()); - let t = t.unwrap_or(String::new()); - let mut shape = None; - let mut properties = HashMap::new(); - - parse_tag!(parser, "object", { - "ellipse" => |_| { - shape = Some(ObjectShape::Ellipse { - width: w, - height: h, - }); - Ok(()) - }, - "polyline" => |attrs| { - shape = Some(Object::new_polyline(attrs)?); - Ok(()) - }, - "polygon" => |attrs| { - shape = Some(Object::new_polygon(attrs)?); - Ok(()) - }, - "point" => |_| { - shape = Some(Object::new_point(x, y)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - }); - - let shape = shape.unwrap_or(ObjectShape::Rect { - width: w, - height: h, - }); - - Ok(Object { - id: id, - gid: gid, - name: n.clone(), - obj_type: t.clone(), - width: w, - height: h, - x: x, - y: y, - rotation: r, - visible: v, - shape: shape, - properties: properties, - }) - } - - fn new_polyline(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> { - let ((), s) = get_attrs!( - attrs, - optionals: [], - required: [ - ("points", points, |v| Some(v)), - ], - TiledError::MalformedAttributes("A polyline must have points".to_string()) - ); - let points = Object::parse_points(s)?; - Ok(ObjectShape::Polyline { points: points }) - } - - fn new_polygon(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> { - let ((), s) = get_attrs!( - attrs, - optionals: [], - required: [ - ("points", points, |v| Some(v)), - ], - TiledError::MalformedAttributes("A polygon must have points".to_string()) - ); - let points = Object::parse_points(s)?; - Ok(ObjectShape::Polygon { points: points }) - } - - fn new_point(x: f32, y: f32) -> Result<ObjectShape, TiledError> { - Ok(ObjectShape::Point(x, y)) - } - - fn parse_points(s: String) -> Result<Vec<(f32, f32)>, TiledError> { - let pairs = s.split(' '); - let mut points = Vec::new(); - for v in pairs.map(|p| p.split(',')) { - let v: Vec<&str> = v.collect(); - if v.len() != 2 { - return Err(TiledError::MalformedAttributes( - "one of a polyline's points does not have an x and y coordinate".to_string(), - )); - } - let (x, y) = (v[0].parse().ok(), v[1].parse().ok()); - if x.is_none() || y.is_none() { - return Err(TiledError::MalformedAttributes( - "one of polyline's points does not have i32eger coordinates".to_string(), - )); - } - points.push((x.unwrap(), y.unwrap())); - } - Ok(points) - } -} - -#[derive(Debug, PartialEq, Clone)] -pub struct Frame { - pub tile_id: u32, - pub duration: u32, -} - -impl Frame { - fn new(attrs: Vec<OwnedAttribute>) -> Result<Frame, TiledError> { - let ((), (tile_id, duration)) = get_attrs!( - attrs, - optionals: [], - required: [ - ("tileid", tile_id, |v:String| v.parse().ok()), - ("duration", duration, |v:String| v.parse().ok()), - ], - TiledError::MalformedAttributes("A frame must have tileid and duration".to_string()) - ); - Ok(Frame { - tile_id: tile_id, - duration: duration, - }) - } -} - -fn parse_animation<R: Read>(parser: &mut EventReader<R>) -> Result<Vec<Frame>, TiledError> { - let mut animation = Vec::new(); - parse_tag!(parser, "animation", { - "frame" => |attrs| { - animation.push(Frame::new(attrs)?); - Ok(()) - }, - }); - Ok(animation) -} - -fn parse_infinite_data<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - width: u32, -) -> Result<LayerData, TiledError> { - let ((e, c), ()) = get_attrs!( - attrs, - optionals: [ - ("encoding", encoding, |v| Some(v)), - ("compression", compression, |v| Some(v)), - ], - required: [], - TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) - ); - - let mut chunks = HashMap::<(i32, i32), Chunk>::new(); - parse_tag!(parser, "data", { - "chunk" => |attrs| { - let chunk = Chunk::new(parser, attrs, e.clone(), c.clone())?; - chunks.insert((chunk.x, chunk.y), chunk); - Ok(()) - } - }); - - Ok(LayerData::Infinite(chunks)) -} - -fn parse_data<R: Read>( - parser: &mut EventReader<R>, - attrs: Vec<OwnedAttribute>, - width: u32, -) -> Result<LayerData, TiledError> { - let ((e, c), ()) = get_attrs!( - attrs, - optionals: [ - ("encoding", encoding, |v| Some(v)), - ("compression", compression, |v| Some(v)), - ], - required: [], - TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) - ); - - let tiles = parse_data_line(e, c, parser, width)?; - - Ok(LayerData::Finite(tiles)) -} - -fn parse_data_line<R: Read>( - encoding: Option<String>, - compression: Option<String>, - parser: &mut EventReader<R>, - width: u32, -) -> Result<Vec<Vec<LayerTile>>, TiledError> { - match (encoding, compression) { - (None, None) => { - return Err(TiledError::Other( - "XML format is currently not supported".to_string(), - )) - } - (Some(e), None) => match e.as_ref() { - "base64" => return parse_base64(parser).map(|v| convert_to_tile(&v, width)), - "csv" => return decode_csv(width, parser), - e => return Err(TiledError::Other(format!("Unknown encoding format {}", e))), - }, - (Some(e), Some(c)) => match (e.as_ref(), c.as_ref()) { - ("base64", "zlib") => { - return parse_base64(parser) - .and_then(decode_zlib) - .map(|v| convert_to_tile(&v, width)) - } - ("base64", "gzip") => { - return parse_base64(parser) - .and_then(decode_gzip) - .map(|v| convert_to_tile(&v, width)) - } - #[cfg(feature = "zstd")] - ("base64", "zstd") => { - return parse_base64(parser) - .and_then(decode_zstd) - .map(|v| convert_to_tile(&v, width)) - } - (e, c) => { - return Err(TiledError::Other(format!( - "Unknown combination of {} encoding and {} compression", - e, c - ))) - } - }, - _ => return Err(TiledError::Other("Missing encoding format".to_string())), - }; -} - -fn parse_base64<R: Read>(parser: &mut EventReader<R>) -> Result<Vec<u8>, TiledError> { - loop { - match parser.next().map_err(TiledError::XmlDecodingError)? { - XmlEvent::Characters(s) => { - return base64::decode(s.trim().as_bytes()).map_err(TiledError::Base64DecodingError) - } - XmlEvent::EndElement { name, .. } => { - if name.local_name == "data" { - return Ok(Vec::new()); - } - } - _ => {} - } - } -} - -fn decode_zlib(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { - use libflate::zlib::Decoder; - let mut zd = - Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?; - let mut data = Vec::new(); - match zd.read_to_end(&mut data) { - Ok(_v) => {} - Err(e) => return Err(TiledError::DecompressingError(e)), - } - Ok(data) -} - -fn decode_gzip(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { - use libflate::gzip::Decoder; - let mut zd = - Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?; - - let mut data = Vec::new(); - zd.read_to_end(&mut data) - .map_err(|e| TiledError::DecompressingError(e))?; - Ok(data) -} - -#[cfg(feature = "zstd")] -fn decode_zstd(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { - use std::io::Cursor; - use zstd::stream::read::Decoder; - - let buff = Cursor::new(&data); - let mut zd = Decoder::with_buffer(buff).map_err(|e| TiledError::DecompressingError(e))?; - - let mut data = Vec::new(); - zd.read_to_end(&mut data) - .map_err(|e| TiledError::DecompressingError(e))?; - Ok(data) -} - -fn decode_csv<R: Read>(width: u32, parser: &mut EventReader<R>) -> Result<Vec<Vec<LayerTile>>, TiledError> { - loop { - match parser.next().map_err(TiledError::XmlDecodingError)? { - XmlEvent::Characters(s) => { - let mut tiles_it = s - .split(&['\n', '\r', ','][0..]) - .filter(|v| v.trim() != "") - .map(|v| v.parse().unwrap()) - .map(LayerTile::new) - .peekable(); - let mut rows = Vec::new(); - while tiles_it.peek().is_some() { - let row = tiles_it.by_ref().take(width as usize).collect(); - rows.push(row); - } - return Ok(rows); - } - XmlEvent::EndElement { name, .. } => { - if name.local_name == "data" { - return Ok(Vec::new()); - } - } - _ => {} - } - } -} - -fn convert_to_tile(all: &Vec<u8>, width: u32) -> Vec<Vec<LayerTile>> { - let mut data = Vec::new(); - for chunk in all.chunks((width * 4) as usize) { - let mut row = Vec::new(); - for i in 0..width { - let start: usize = i as usize * 4; - let n = ((chunk[start + 3] as u32) << 24) - + ((chunk[start + 2] as u32) << 16) - + ((chunk[start + 1] as u32) << 8) - + chunk[start] as u32; - let n = LayerTile::new(n); - row.push(n); - } - data.push(row); - } - data -} - -fn parse_impl<R: Read>(reader: R, map_path: Option<&Path>) -> Result<Map, TiledError> { - let mut parser = EventReader::new(reader); - loop { - match parser.next().map_err(TiledError::XmlDecodingError)? { - XmlEvent::StartElement { - name, attributes, .. - } => { - if name.local_name == "map" { - return Map::new(&mut parser, attributes, map_path); - } - } - XmlEvent::EndDocument => { - return Err(TiledError::PrematureEnd( - "Document ended before map was parsed".to_string(), - )) - } - _ => {} - } - } -} - +// TODO move these /// Parse a buffer hopefully containing the contents of a Tiled file and try to /// parse it. This augments `parse` with a file location: some engines /// (e.g. Amethyst) simply hand over a byte stream (and file location) for parsing, diff --git a/src/map.rs b/src/map.rs new file mode 100644 index 0000000000000000000000000000000000000000..f9271fa2dd1234210ab2dcb27662a1b17190750f --- /dev/null +++ b/src/map.rs @@ -0,0 +1,150 @@ +use std::{collections::HashMap, fmt, io::Read, path::Path, str::FromStr}; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{ + error::{ParseTileError, TiledError}, + layers::{ImageLayer, Layer}, + objects::ObjectGroup, + properties::{parse_properties, Colour, Properties}, + tileset::Tileset, + util::*, +}; + +/// All Tiled files will be parsed into this. Holds all the layers and tilesets +#[derive(Debug, PartialEq, Clone)] +pub struct Map { + pub version: String, + pub orientation: Orientation, + /// Width of the map, in tiles + pub width: u32, + /// Height of the map, in tiles + pub height: u32, + pub tile_width: u32, + pub tile_height: u32, + pub tilesets: Vec<Tileset>, + pub layers: Vec<Layer>, + pub image_layers: Vec<ImageLayer>, + pub object_groups: Vec<ObjectGroup>, + pub properties: Properties, + pub background_colour: Option<Colour>, + pub infinite: bool, +} + +impl Map { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + map_path: Option<&Path>, + ) -> Result<Map, TiledError> { + let ((c, infinite), (v, o, w, h, tw, th)) = get_attrs!( + attrs, + optionals: [ + ("backgroundcolor", colour, |v:String| v.parse().ok()), + ("infinite", infinite, |v:String| Some(v == "1")), + ], + required: [ + ("version", version, |v| Some(v)), + ("orientation", orientation, |v:String| v.parse().ok()), + ("width", width, |v:String| v.parse().ok()), + ("height", height, |v:String| v.parse().ok()), + ("tilewidth", tile_width, |v:String| v.parse().ok()), + ("tileheight", tile_height, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("map must have a version, width and height with correct types".to_string()) + ); + + let mut tilesets = Vec::new(); + let mut layers = Vec::new(); + let mut image_layers = Vec::new(); + let mut properties = HashMap::new(); + let mut object_groups = Vec::new(); + let mut layer_index = 0; + parse_tag!(parser, "map", { + "tileset" => |attrs| { + tilesets.push(Tileset::new(parser, attrs, map_path)?); + Ok(()) + }, + "layer" => |attrs| { + layers.push(Layer::new(parser, attrs, w, layer_index, infinite.unwrap_or(false))?); + layer_index += 1; + Ok(()) + }, + "imagelayer" => |attrs| { + image_layers.push(ImageLayer::new(parser, attrs, layer_index)?); + layer_index += 1; + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + "objectgroup" => |attrs| { + object_groups.push(ObjectGroup::new(parser, attrs, Some(layer_index))?); + layer_index += 1; + Ok(()) + }, + }); + Ok(Map { + version: v, + orientation: o, + width: w, + height: h, + tile_width: tw, + tile_height: th, + tilesets, + layers, + image_layers, + object_groups, + properties, + background_colour: c, + infinite: infinite.unwrap_or(false), + }) + } + + /// This function will return the correct Tileset given a GID. + pub fn get_tileset_by_gid(&self, gid: u32) -> Option<&Tileset> { + let mut maximum_gid: i32 = -1; + let mut maximum_ts = None; + for tileset in self.tilesets.iter() { + if tileset.first_gid as i32 > maximum_gid && tileset.first_gid <= gid { + maximum_gid = tileset.first_gid as i32; + maximum_ts = Some(tileset); + } + } + maximum_ts + } +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum Orientation { + Orthogonal, + Isometric, + Staggered, + Hexagonal, +} + +impl FromStr for Orientation { + type Err = ParseTileError; + + fn from_str(s: &str) -> Result<Orientation, ParseTileError> { + match s { + "orthogonal" => Ok(Orientation::Orthogonal), + "isometric" => Ok(Orientation::Isometric), + "staggered" => Ok(Orientation::Staggered), + "hexagonal" => Ok(Orientation::Hexagonal), + _ => Err(ParseTileError::OrientationError), + } + } +} + +impl fmt::Display for Orientation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Orientation::Orthogonal => write!(f, "orthogonal"), + Orientation::Isometric => write!(f, "isometric"), + Orientation::Staggered => write!(f, "staggered"), + Orientation::Hexagonal => write!(f, "hexagonal"), + } + } +} diff --git a/src/objects.rs b/src/objects.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e5edf919dea4938bee8111f7f77ce5597455203 --- /dev/null +++ b/src/objects.rs @@ -0,0 +1,222 @@ +use std::{collections::HashMap, io::Read}; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{ + error::TiledError, + properties::{parse_properties, Colour, Properties}, + util::{get_attrs, parse_tag}, +}; + +#[derive(Debug, PartialEq, Clone)] +pub struct ObjectGroup { + pub name: String, + pub opacity: f32, + pub visible: bool, + pub objects: Vec<Object>, + pub colour: Option<Colour>, + /** + * Layer index is not preset for tile collision boxes + */ + pub layer_index: Option<u32>, + pub properties: Properties, +} + +impl ObjectGroup { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + layer_index: Option<u32>, + ) -> Result<ObjectGroup, TiledError> { + let ((o, v, c, n), ()) = get_attrs!( + attrs, + optionals: [ + ("opacity", opacity, |v:String| v.parse().ok()), + ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), + ("color", colour, |v:String| v.parse().ok()), + ("name", name, |v:String| v.into()), + ], + required: [], + TiledError::MalformedAttributes("object groups must have a name".to_string()) + ); + let mut objects = Vec::new(); + let mut properties = HashMap::new(); + parse_tag!(parser, "objectgroup", { + "object" => |attrs| { + objects.push(Object::new(parser, attrs)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + Ok(ObjectGroup { + name: n.unwrap_or(String::new()), + opacity: o.unwrap_or(1.0), + visible: v.unwrap_or(true), + objects: objects, + colour: c, + layer_index, + properties, + }) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum ObjectShape { + Rect { width: f32, height: f32 }, + Ellipse { width: f32, height: f32 }, + Polyline { points: Vec<(f32, f32)> }, + Polygon { points: Vec<(f32, f32)> }, + Point(f32, f32), +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Object { + pub id: u32, + pub gid: u32, + pub name: String, + pub obj_type: String, + pub width: f32, + pub height: f32, + pub x: f32, + pub y: f32, + pub rotation: f32, + pub visible: bool, + pub shape: ObjectShape, + pub properties: Properties, +} + +impl Object { + fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + ) -> Result<Object, TiledError> { + let ((id, gid, n, t, w, h, v, r), (x, y)) = get_attrs!( + attrs, + optionals: [ + ("id", id, |v:String| v.parse().ok()), + ("gid", gid, |v:String| v.parse().ok()), + ("name", name, |v:String| v.parse().ok()), + ("type", obj_type, |v:String| v.parse().ok()), + ("width", width, |v:String| v.parse().ok()), + ("height", height, |v:String| v.parse().ok()), + ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)), + ("rotation", rotation, |v:String| v.parse().ok()), + ], + required: [ + ("x", x, |v:String| v.parse().ok()), + ("y", y, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("objects must have an x and a y number".to_string()) + ); + let v = v.unwrap_or(true); + let w = w.unwrap_or(0f32); + let h = h.unwrap_or(0f32); + let r = r.unwrap_or(0f32); + let id = id.unwrap_or(0u32); + let gid = gid.unwrap_or(0u32); + let n = n.unwrap_or(String::new()); + let t = t.unwrap_or(String::new()); + let mut shape = None; + let mut properties = HashMap::new(); + + parse_tag!(parser, "object", { + "ellipse" => |_| { + shape = Some(ObjectShape::Ellipse { + width: w, + height: h, + }); + Ok(()) + }, + "polyline" => |attrs| { + shape = Some(Object::new_polyline(attrs)?); + Ok(()) + }, + "polygon" => |attrs| { + shape = Some(Object::new_polygon(attrs)?); + Ok(()) + }, + "point" => |_| { + shape = Some(Object::new_point(x, y)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + + let shape = shape.unwrap_or(ObjectShape::Rect { + width: w, + height: h, + }); + + Ok(Object { + id: id, + gid: gid, + name: n.clone(), + obj_type: t.clone(), + width: w, + height: h, + x: x, + y: y, + rotation: r, + visible: v, + shape: shape, + properties: properties, + }) + } + + fn new_polyline(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> { + let ((), s) = get_attrs!( + attrs, + optionals: [], + required: [ + ("points", points, |v| Some(v)), + ], + TiledError::MalformedAttributes("A polyline must have points".to_string()) + ); + let points = Object::parse_points(s)?; + Ok(ObjectShape::Polyline { points: points }) + } + + fn new_polygon(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> { + let ((), s) = get_attrs!( + attrs, + optionals: [], + required: [ + ("points", points, |v| Some(v)), + ], + TiledError::MalformedAttributes("A polygon must have points".to_string()) + ); + let points = Object::parse_points(s)?; + Ok(ObjectShape::Polygon { points: points }) + } + + fn new_point(x: f32, y: f32) -> Result<ObjectShape, TiledError> { + Ok(ObjectShape::Point(x, y)) + } + + fn parse_points(s: String) -> Result<Vec<(f32, f32)>, TiledError> { + let pairs = s.split(' '); + let mut points = Vec::new(); + for v in pairs.map(|p| p.split(',')) { + let v: Vec<&str> = v.collect(); + if v.len() != 2 { + return Err(TiledError::MalformedAttributes( + "one of a polyline's points does not have an x and y coordinate".to_string(), + )); + } + let (x, y) = (v[0].parse().ok(), v[1].parse().ok()); + if x.is_none() || y.is_none() { + return Err(TiledError::MalformedAttributes( + "one of polyline's points does not have i32eger coordinates".to_string(), + )); + } + points.push((x.unwrap(), y.unwrap())); + } + Ok(points) + } +} diff --git a/src/properties.rs b/src/properties.rs new file mode 100644 index 0000000000000000000000000000000000000000..6d9ae472c87c84a822a08ad963bf5a1ef0c4ecbd --- /dev/null +++ b/src/properties.rs @@ -0,0 +1,108 @@ +use std::{collections::HashMap, io::Read, str::FromStr}; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{ + error::{ParseTileError, TiledError}, + util::{get_attrs, parse_tag}, +}; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct Colour { + pub red: u8, + pub green: u8, + pub blue: u8, +} + +impl FromStr for Colour { + type Err = ParseTileError; + + fn from_str(s: &str) -> Result<Colour, ParseTileError> { + let s = if s.starts_with("#") { &s[1..] } else { s }; + if s.len() != 6 { + return Err(ParseTileError::ColourError); + } + let r = u8::from_str_radix(&s[0..2], 16); + let g = u8::from_str_radix(&s[2..4], 16); + let b = u8::from_str_radix(&s[4..6], 16); + if r.is_ok() && g.is_ok() && b.is_ok() { + return Ok(Colour { + red: r.unwrap(), + green: g.unwrap(), + blue: b.unwrap(), + }); + } + Err(ParseTileError::ColourError) + } +} + +#[derive(Debug, PartialEq, Clone)] +pub enum PropertyValue { + BoolValue(bool), + FloatValue(f32), + IntValue(i32), + ColorValue(u32), + StringValue(String), + /// Holds the path relative to the map or tileset + FileValue(String), +} + +impl PropertyValue { + fn new(property_type: String, value: String) -> Result<PropertyValue, TiledError> { + // Check the property type against the value. + match property_type.as_str() { + "bool" => match value.parse() { + Ok(val) => Ok(PropertyValue::BoolValue(val)), + Err(err) => Err(TiledError::Other(err.to_string())), + }, + "float" => match value.parse() { + Ok(val) => Ok(PropertyValue::FloatValue(val)), + Err(err) => Err(TiledError::Other(err.to_string())), + }, + "int" => match value.parse() { + Ok(val) => Ok(PropertyValue::IntValue(val)), + Err(err) => Err(TiledError::Other(err.to_string())), + }, + "color" if value.len() > 1 => match u32::from_str_radix(&value[1..], 16) { + Ok(color) => Ok(PropertyValue::ColorValue(color)), + Err(_) => Err(TiledError::Other(format!( + "Improperly formatted color property" + ))), + }, + "string" => Ok(PropertyValue::StringValue(value)), + "file" => Ok(PropertyValue::FileValue(value)), + _ => Err(TiledError::Other(format!( + "Unknown property type \"{}\"", + property_type + ))), + } + } +} + +pub type Properties = HashMap<String, PropertyValue>; + +pub(crate) fn parse_properties<R: Read>( + parser: &mut EventReader<R>, +) -> Result<Properties, TiledError> { + let mut p = HashMap::new(); + parse_tag!(parser, "properties", { + "property" => |attrs:Vec<OwnedAttribute>| { + let (t, (k, v)) = get_attrs!( + attrs, + optionals: [ + ("type", property_type, |v| Some(v)), + ], + required: [ + ("name", key, |v| Some(v)), + ("value", value, |v| Some(v)), + ], + TiledError::MalformedAttributes("property must have a name and a value".to_string()) + ); + let t = t.unwrap_or("string".into()); + + p.insert(k, PropertyValue::new(t, v)?); + Ok(()) + }, + }); + Ok(p) +} diff --git a/src/tile.rs b/src/tile.rs new file mode 100644 index 0000000000000000000000000000000000000000..80d6f979ea1c89f462b013c3f5ef7c4b20369aae --- /dev/null +++ b/src/tile.rs @@ -0,0 +1,74 @@ +use std::{collections::HashMap, io::Read}; + +use xml::{attribute::OwnedAttribute, EventReader}; + +use crate::{ + animation::Frame, + error::TiledError, + image::Image, + objects::ObjectGroup, + properties::{parse_properties, Properties}, + util::{get_attrs, parse_animation, parse_tag}, +}; + +#[derive(Debug, PartialEq, Clone)] +pub struct Tile { + pub id: u32, + pub images: Vec<Image>, + pub properties: Properties, + pub objectgroup: Option<ObjectGroup>, + pub animation: Option<Vec<Frame>>, + pub tile_type: Option<String>, + pub probability: f32, +} + +impl Tile { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + ) -> Result<Tile, TiledError> { + let ((tile_type, probability), id) = get_attrs!( + attrs, + optionals: [ + ("type", tile_type, |v:String| v.parse().ok()), + ("probability", probability, |v:String| v.parse().ok()), + ], + required: [ + ("id", id, |v:String| v.parse::<u32>().ok()), + ], + TiledError::MalformedAttributes("tile must have an id with the correct type".to_string()) + ); + + let mut images = Vec::new(); + let mut properties = HashMap::new(); + let mut objectgroup = None; + let mut animation = None; + parse_tag!(parser, "tile", { + "image" => |attrs| { + images.push(Image::new(parser, attrs)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + "objectgroup" => |attrs| { + objectgroup = Some(ObjectGroup::new(parser, attrs, None)?); + Ok(()) + }, + "animation" => |_| { + animation = Some(parse_animation(parser)?); + Ok(()) + }, + }); + Ok(Tile { + id, + images, + properties, + objectgroup, + animation, + tile_type, + probability: probability.unwrap_or(1.0), + }) + } +} diff --git a/src/tileset.rs b/src/tileset.rs new file mode 100644 index 0000000000000000000000000000000000000000..fe45353523aea6176296d6718ccd682e0688b647 --- /dev/null +++ b/src/tileset.rs @@ -0,0 +1,187 @@ +use crate::properties::{parse_properties, Properties}; +use crate::util::*; +use crate::*; // FIXME + +/// A tileset, usually the tilesheet image. +#[derive(Debug, PartialEq, Clone)] +pub struct Tileset { + /// The GID of the first tile stored + pub first_gid: u32, + pub name: String, + pub tile_width: u32, + pub tile_height: u32, + pub spacing: u32, + pub margin: u32, + pub tilecount: Option<u32>, + /// The Tiled spec says that a tileset can have mutliple images so a `Vec` + /// is used. Usually you will only use one. + pub images: Vec<Image>, + pub tiles: Vec<Tile>, + pub properties: Properties, +} + +impl Tileset { + pub(crate) fn new<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + map_path: Option<&Path>, + ) -> Result<Tileset, TiledError> { + Tileset::new_internal(parser, &attrs).or_else(|_| Tileset::new_reference(&attrs, map_path)) + } + + fn new_internal<R: Read>( + parser: &mut EventReader<R>, + attrs: &Vec<OwnedAttribute>, + ) -> Result<Tileset, TiledError> { + let ((spacing, margin, tilecount), (first_gid, name, width, height)) = get_attrs!( + attrs, + optionals: [ + ("spacing", spacing, |v:String| v.parse().ok()), + ("margin", margin, |v:String| v.parse().ok()), + ("tilecount", tilecount, |v:String| v.parse().ok()), + ], + required: [ + ("firstgid", first_gid, |v:String| v.parse().ok()), + ("name", name, |v| Some(v)), + ("tilewidth", width, |v:String| v.parse().ok()), + ("tileheight", height, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) + ); + + let mut images = Vec::new(); + let mut tiles = Vec::new(); + let mut properties = HashMap::new(); + parse_tag!(parser, "tileset", { + "image" => |attrs| { + images.push(Image::new(parser, attrs)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + "tile" => |attrs| { + tiles.push(Tile::new(parser, attrs)?); + Ok(()) + }, + }); + + Ok(Tileset { + tile_width: width, + tile_height: height, + spacing: spacing.unwrap_or(0), + margin: margin.unwrap_or(0), + first_gid, + name, + tilecount, + images, + tiles, + properties, + }) + } + + fn new_reference( + attrs: &Vec<OwnedAttribute>, + map_path: Option<&Path>, + ) -> Result<Tileset, TiledError> { + let ((), (first_gid, source)) = get_attrs!( + attrs, + optionals: [], + required: [ + ("firstgid", first_gid, |v:String| v.parse().ok()), + ("source", name, |v| Some(v)), + ], + TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) + ); + + let tileset_path = map_path.ok_or(TiledError::Other("Maps with external tilesets must know their file location. See parse_with_path(Path).".to_string()))?.with_file_name(source); + let file = File::open(&tileset_path).map_err(|_| { + TiledError::Other(format!( + "External tileset file not found: {:?}", + tileset_path + )) + })?; + Tileset::new_external(file, first_gid) + } + + pub(crate) fn new_external<R: Read>(file: R, first_gid: u32) -> Result<Tileset, TiledError> { + let mut tileset_parser = EventReader::new(file); + loop { + match tileset_parser + .next() + .map_err(TiledError::XmlDecodingError)? + { + XmlEvent::StartElement { + name, attributes, .. + } => { + if name.local_name == "tileset" { + return Tileset::parse_external_tileset( + first_gid, + &mut tileset_parser, + &attributes, + ); + } + } + XmlEvent::EndDocument => { + return Err(TiledError::PrematureEnd( + "Tileset Document ended before map was parsed".to_string(), + )) + } + _ => {} + } + } + } + + fn parse_external_tileset<R: Read>( + first_gid: u32, + parser: &mut EventReader<R>, + attrs: &Vec<OwnedAttribute>, + ) -> Result<Tileset, TiledError> { + let ((spacing, margin, tilecount), (name, width, height)) = get_attrs!( + attrs, + optionals: [ + ("spacing", spacing, |v:String| v.parse().ok()), + ("margin", margin, |v:String| v.parse().ok()), + ("tilecount", tilecount, |v:String| v.parse().ok()), + ], + required: [ + ("name", name, |v| Some(v)), + ("tilewidth", width, |v:String| v.parse().ok()), + ("tileheight", height, |v:String| v.parse().ok()), + ], + TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) + ); + + let mut images = Vec::new(); + let mut tiles = Vec::new(); + let mut properties = HashMap::new(); + parse_tag!(parser, "tileset", { + "image" => |attrs| { + images.push(Image::new(parser, attrs)?); + Ok(()) + }, + "tile" => |attrs| { + tiles.push(Tile::new(parser, attrs)?); + Ok(()) + }, + "properties" => |_| { + properties = parse_properties(parser)?; + Ok(()) + }, + }); + + Ok(Tileset { + first_gid: first_gid, + name: name, + tile_width: width, + tile_height: height, + spacing: spacing.unwrap_or(0), + margin: margin.unwrap_or(0), + tilecount: tilecount, + images: images, + tiles: tiles, + properties, + }) + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..61dcfd1c24bf24cdbebaba82fe17da8e93d048b4 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,301 @@ +/// Loops through the attributes once and pulls out the ones we ask it to. It +/// will check that the required ones are there. This could have been done with +/// attrs.find but that would be inefficient. +/// +/// This is probably a really terrible way to do this. It does cut down on lines +/// though which is nice. +macro_rules! get_attrs { + ($attrs:expr, optionals: [$(($oName:pat, $oVar:ident, $oMethod:expr)),* $(,)*], + required: [$(($name:pat, $var:ident, $method:expr)),* $(,)*], $err:expr) => { + { + $(let mut $oVar = None;)* + $(let mut $var = None;)* + for attr in $attrs.iter() { + match attr.name.local_name.as_ref() { + $($oName => $oVar = $oMethod(attr.value.clone()),)* + $($name => $var = $method(attr.value.clone()),)* + _ => {} + } + } + if !(true $(&& $var.is_some())*) { + return Err($err); + } + (($($oVar),*), ($($var.unwrap()),*)) + } + } +} + +/// Goes through the children of the tag and will call the correct function for +/// that child. Closes the tag +/// +/// Not quite as bad. +macro_rules! parse_tag { + ($parser:expr, $close_tag:expr, {$($open_tag:expr => $open_method:expr),* $(,)*}) => { + loop { + match $parser.next().map_err(TiledError::XmlDecodingError)? { + xml::reader::XmlEvent::StartElement {name, attributes, ..} => { + if false {} + $(else if name.local_name == $open_tag { + match $open_method(attributes) { + Ok(()) => {}, + Err(e) => return Err(e) + }; + })* + } + xml::reader::XmlEvent::EndElement {name, ..} => { + if name.local_name == $close_tag { + break; + } + } + xml::reader::XmlEvent::EndDocument => return Err(TiledError::PrematureEnd("Document ended before we expected.".to_string())), + _ => {} + } + } + } +} + +use std::{ + collections::HashMap, + fs::File, + io::{BufReader, Read}, + path::Path, +}; + +pub(crate) use get_attrs; +pub(crate) use parse_tag; +use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader}; + +use crate::{ + animation::Frame, + error::TiledError, + layers::{Chunk, LayerData, LayerTile}, + map::Map, + tileset::Tileset, +}; + +pub(crate) fn parse_animation<R: Read>( + parser: &mut EventReader<R>, +) -> Result<Vec<Frame>, TiledError> { + let mut animation = Vec::new(); + parse_tag!(parser, "animation", { + "frame" => |attrs| { + animation.push(Frame::new(attrs)?); + Ok(()) + }, + }); + Ok(animation) +} + +pub(crate) fn parse_infinite_data<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + width: u32, +) -> Result<LayerData, TiledError> { + let ((e, c), ()) = get_attrs!( + attrs, + optionals: [ + ("encoding", encoding, |v| Some(v)), + ("compression", compression, |v| Some(v)), + ], + required: [], + TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) + ); + + let mut chunks = HashMap::<(i32, i32), Chunk>::new(); + parse_tag!(parser, "data", { + "chunk" => |attrs| { + let chunk = Chunk::new(parser, attrs, e.clone(), c.clone())?; + chunks.insert((chunk.x, chunk.y), chunk); + Ok(()) + } + }); + + Ok(LayerData::Infinite(chunks)) +} + +pub(crate) fn parse_data<R: Read>( + parser: &mut EventReader<R>, + attrs: Vec<OwnedAttribute>, + width: u32, +) -> Result<LayerData, TiledError> { + let ((e, c), ()) = get_attrs!( + attrs, + optionals: [ + ("encoding", encoding, |v| Some(v)), + ("compression", compression, |v| Some(v)), + ], + required: [], + TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()) + ); + + let tiles = parse_data_line(e, c, parser, width)?; + + Ok(LayerData::Finite(tiles)) +} + +pub(crate) fn parse_data_line<R: Read>( + encoding: Option<String>, + compression: Option<String>, + parser: &mut EventReader<R>, + width: u32, +) -> Result<Vec<Vec<LayerTile>>, TiledError> { + match (encoding, compression) { + (None, None) => { + return Err(TiledError::Other( + "XML format is currently not supported".to_string(), + )) + } + (Some(e), None) => match e.as_ref() { + "base64" => return parse_base64(parser).map(|v| convert_to_tile(&v, width)), + "csv" => return decode_csv(width, parser), + e => return Err(TiledError::Other(format!("Unknown encoding format {}", e))), + }, + (Some(e), Some(c)) => match (e.as_ref(), c.as_ref()) { + ("base64", "zlib") => { + return parse_base64(parser) + .and_then(decode_zlib) + .map(|v| convert_to_tile(&v, width)) + } + ("base64", "gzip") => { + return parse_base64(parser) + .and_then(decode_gzip) + .map(|v| convert_to_tile(&v, width)) + } + #[cfg(feature = "zstd")] + ("base64", "zstd") => { + return parse_base64(parser) + .and_then(decode_zstd) + .map(|v| convert_to_tile(&v, width)) + } + (e, c) => { + return Err(TiledError::Other(format!( + "Unknown combination of {} encoding and {} compression", + e, c + ))) + } + }, + _ => return Err(TiledError::Other("Missing encoding format".to_string())), + }; +} + +pub(crate) fn parse_base64<R: Read>(parser: &mut EventReader<R>) -> Result<Vec<u8>, TiledError> { + loop { + match parser.next().map_err(TiledError::XmlDecodingError)? { + XmlEvent::Characters(s) => { + return base64::decode(s.trim().as_bytes()).map_err(TiledError::Base64DecodingError) + } + XmlEvent::EndElement { name, .. } => { + if name.local_name == "data" { + return Ok(Vec::new()); + } + } + _ => {} + } + } +} + +pub(crate) fn decode_zlib(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { + use libflate::zlib::Decoder; + let mut zd = + Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?; + let mut data = Vec::new(); + match zd.read_to_end(&mut data) { + Ok(_v) => {} + Err(e) => return Err(TiledError::DecompressingError(e)), + } + Ok(data) +} + +pub(crate) fn decode_gzip(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { + use libflate::gzip::Decoder; + let mut zd = + Decoder::new(BufReader::new(&data[..])).map_err(|e| TiledError::DecompressingError(e))?; + + let mut data = Vec::new(); + zd.read_to_end(&mut data) + .map_err(|e| TiledError::DecompressingError(e))?; + Ok(data) +} + +#[cfg(feature = "zstd")] +pub(crate) fn decode_zstd(data: Vec<u8>) -> Result<Vec<u8>, TiledError> { + use std::io::Cursor; + use zstd::stream::read::Decoder; + + let buff = Cursor::new(&data); + let mut zd = Decoder::with_buffer(buff).map_err(|e| TiledError::DecompressingError(e))?; + + let mut data = Vec::new(); + zd.read_to_end(&mut data) + .map_err(|e| TiledError::DecompressingError(e))?; + Ok(data) +} + +pub(crate) fn decode_csv<R: Read>( + width: u32, + parser: &mut EventReader<R>, +) -> Result<Vec<Vec<LayerTile>>, TiledError> { + loop { + match parser.next().map_err(TiledError::XmlDecodingError)? { + XmlEvent::Characters(s) => { + let mut tiles_it = s + .split(&['\n', '\r', ','][0..]) + .filter(|v| v.trim() != "") + .map(|v| v.parse().unwrap()) + .map(LayerTile::new) + .peekable(); + let mut rows = Vec::new(); + while tiles_it.peek().is_some() { + let row = tiles_it.by_ref().take(width as usize).collect(); + rows.push(row); + } + return Ok(rows); + } + XmlEvent::EndElement { name, .. } => { + if name.local_name == "data" { + return Ok(Vec::new()); + } + } + _ => {} + } + } +} + +pub(crate) fn convert_to_tile(all: &Vec<u8>, width: u32) -> Vec<Vec<LayerTile>> { + let mut data = Vec::new(); + for chunk in all.chunks((width * 4) as usize) { + let mut row = Vec::new(); + for i in 0..width { + let start: usize = i as usize * 4; + let n = ((chunk[start + 3] as u32) << 24) + + ((chunk[start + 2] as u32) << 16) + + ((chunk[start + 1] as u32) << 8) + + chunk[start] as u32; + let n = LayerTile::new(n); + row.push(n); + } + data.push(row); + } + data +} + +pub(crate) fn parse_impl<R: Read>(reader: R, map_path: Option<&Path>) -> Result<Map, TiledError> { + let mut parser = EventReader::new(reader); + loop { + match parser.next().map_err(TiledError::XmlDecodingError)? { + XmlEvent::StartElement { + name, attributes, .. + } => { + if name.local_name == "map" { + return Map::new(&mut parser, attributes, map_path); + } + } + XmlEvent::EndDocument => { + return Err(TiledError::PrematureEnd( + "Document ended before map was parsed".to_string(), + )) + } + _ => {} + } + } +} \ No newline at end of file diff --git a/tests/lib.rs b/tests/lib.rs index 80ab40b843c5d062166383073ea7ec0ba24701bf..92b1a284b368e0d65c51ee4724bf750a00383bc0 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,6 +1,9 @@ use std::fs::File; use std::path::Path; -use tiled::{parse, parse_file, parse_tileset, Map, PropertyValue, TiledError, LayerData}; +use tiled::{ + error::TiledError, layers::LayerData, map::Map, parse, parse_file, parse_tileset, + properties::PropertyValue, +}; fn read_from_file(p: &Path) -> Result<Map, TiledError> { let file = File::open(p).unwrap(); @@ -20,7 +23,7 @@ fn test_gzip_and_zlib_encoded_and_raw_are_the_same() { assert_eq!(z, g); assert_eq!(z, r); assert_eq!(z, c); - + if let LayerData::Finite(tiles) = &c.layers[0].tiles { assert_eq!(tiles.len(), 100); assert_eq!(tiles[0].len(), 100); @@ -51,7 +54,7 @@ fn test_just_tileset() { #[test] fn test_infinite_tileset() { - let r = read_from_file_with_path(&Path::new("assets/tiled_base64_zlib_infinite.tmx")).unwrap(); + let r = read_from_file_with_path(&Path::new("assets/tiled_base64_zlib_infinite.tmx")).unwrap(); if let LayerData::Infinite(chunks) = &r.layers[0].tiles { assert_eq!(chunks.len(), 4); @@ -63,7 +66,6 @@ fn test_infinite_tileset() { assert_eq!(chunks[&(-32, 32)].height, 32); } else { assert!(false, "It is wrongly recognised as a finite map"); - } } @@ -135,7 +137,7 @@ fn test_tileset_property() { #[test] fn test_flipped_gid() { let r = read_from_file_with_path(&Path::new("assets/tiled_flipped.tmx")).unwrap(); - + if let LayerData::Finite(tiles) = &r.layers[0].tiles { let t1 = tiles[0][0]; let t2 = tiles[0][1]; @@ -159,7 +161,6 @@ fn test_flipped_gid() { } else { assert!(false, "It is wrongly recognised as an infinite map"); } - } #[test] @@ -173,4 +174,4 @@ fn test_ldk_export() { } else { assert!(false, "It is wrongly recognised as an infinite map"); } -} \ No newline at end of file +}