Skip to content
Snippets Groups Projects
lib.rs 38.5 KiB
Newer Older
TatriX's avatar
TatriX committed
use base64;

use std::collections::HashMap;
use std::fmt;
use std::io::{BufReader, Error, Read};
use std::str::FromStr;
Matthew Hall's avatar
Matthew Hall committed
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
use xml::reader::{Error as XmlError, EventReader};
#[derive(Debug, Copy, Clone)]
Kevin Balz's avatar
Kevin Balz committed
pub enum ParseTileError {
    ColourError,
    OrientationError,
}

Matthew Hall's avatar
Matthew Hall committed
// 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())*) {
Matthew Hall's avatar
Matthew Hall committed
                return Err($err);
            }
            (($($oVar),*), ($($var.unwrap()),*))
        }
    }
}

Matthew Hall's avatar
Matthew Hall committed
// Goes through the children of the tag and will call the correct function for
// that child. Closes the tag
//
// Not quite as bad.
Matthew Hall's avatar
Matthew Hall committed
macro_rules! parse_tag {
    ($parser:expr, $close_tag:expr, {$($open_tag:expr => $open_method:expr),* $(,)*}) => {
TatriX's avatar
TatriX committed
            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)
                        };
                    })*
Matthew Hall's avatar
Matthew Hall committed
                }
                XmlEvent::EndElement {name, ..} => {
                    if name.local_name == $close_tag {
Matthew Hall's avatar
Matthew Hall committed
                        break;
                    }
                }
                XmlEvent::EndDocument => return Err(TiledError::PrematureEnd("Document ended before we expected.".to_string())),
Matthew Hall's avatar
Matthew Hall committed
                _ => {}
            }
        }
    }
}

#[derive(Debug, PartialEq, Eq, Copy, Clone)]
Matthew Hall's avatar
Matthew Hall committed
pub struct Colour {
    pub red: u8,
    pub green: u8,
    pub blue: u8,
Matthew Hall's avatar
Matthew Hall committed
}

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 };
Matthew Hall's avatar
Matthew Hall committed
        if s.len() != 6 {
            return Err(ParseTileError::ColourError);
Matthew Hall's avatar
Matthew Hall committed
        }
        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(),
            });
Matthew Hall's avatar
Matthew Hall committed
        }
        Err(ParseTileError::ColourError)
Matthew Hall's avatar
Matthew Hall committed
/// Errors which occured when parsing the file
Matthew Hall's avatar
Matthew Hall committed
pub enum TiledError {
Matthew Hall's avatar
Matthew Hall committed
    /// A attribute was missing, had the wrong type of wasn't formated
    /// correctly.
Matthew Hall's avatar
Matthew Hall committed
    MalformedAttributes(String),
    /// An error occured when decompressing using the
Matthew Hall's avatar
Matthew Hall committed
    /// [flate2](https://github.com/alexcrichton/flate2-rs) crate.
Matthew Hall's avatar
Matthew Hall committed
    DecompressingError(Error),
    Base64DecodingError(base64::DecodeError),
    XmlDecodingError(XmlError),
    PrematureEnd(String),
    Other(String),
Matthew Hall's avatar
Matthew Hall committed
}

impl fmt::Display for TiledError {
TatriX's avatar
TatriX committed
    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 {
tatref's avatar
tatref committed
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            TiledError::MalformedAttributes(_) => None,
TatriX's avatar
TatriX committed
            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),
}

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)),
tatref's avatar
tatref committed
                Err(err) => Err(TiledError::Other(err.to_string())),
            },
            "float" => match value.parse() {
                Ok(val) => Ok(PropertyValue::FloatValue(val)),
tatref's avatar
tatref committed
                Err(err) => Err(TiledError::Other(err.to_string())),
            },
            "int" => match value.parse() {
                Ok(val) => Ok(PropertyValue::IntValue(val)),
tatref's avatar
tatref committed
                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)),
            _ => Err(TiledError::Other(format!(
                "Unknown property type \"{}\"",
                property_type
            ))),
        }
    }
}

pub type Properties = HashMap<String, PropertyValue>;
Matthew Hall's avatar
Matthew Hall committed
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());

TatriX's avatar
TatriX committed
            p.insert(k, PropertyValue::new(t, v)?);
Risto Saarelma's avatar
Risto Saarelma committed
/// All Tiled files will be parsed into this. Holds all the layers and tilesets
#[derive(Debug, PartialEq, Clone)]
pub struct Map {
Matthew Hall's avatar
Matthew Hall committed
    pub version: String,
    pub orientation: Orientation,
Matthew Hall's avatar
Matthew Hall committed
    pub width: u32,
    pub height: u32,
    pub tile_width: u32,
    pub tile_height: u32,
Matthew Hall's avatar
Matthew Hall committed
    pub tilesets: Vec<Tileset>,
    pub layers: Vec<Layer>,
TatriX's avatar
TatriX committed
    pub image_layers: Vec<ImageLayer>,
Matthew Hall's avatar
Matthew Hall committed
    pub object_groups: Vec<ObjectGroup>,
    pub properties: Properties,
    pub background_colour: Option<Colour>,
    fn new<R: Read>(
        parser: &mut EventReader<R>,
        attrs: Vec<OwnedAttribute>,
        map_path: Option<&Path>,
    ) -> Result<Map, TiledError> {
Matthew Hall's avatar
Matthew Hall committed
        let (c, (v, o, w, h, tw, th)) = get_attrs!(
            optionals: [
                ("backgroundcolor", colour, |v:String| v.parse().ok()),
            ],
            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();
TatriX's avatar
TatriX committed
        let mut image_layers = Vec::new();
        let mut properties = HashMap::new();
Matthew Hall's avatar
Matthew Hall committed
        let mut object_groups = Vec::new();
        let mut layer_index = 0;
        parse_tag!(parser, "map", {
            "tileset" => | attrs| {
TatriX's avatar
TatriX committed
                tilesets.push(Tileset::new(parser, attrs, map_path)?);
                Ok(())
            },
            "layer" => |attrs| {
TatriX's avatar
TatriX committed
                layers.push(Layer::new(parser, attrs, w, layer_index)?);
                layer_index += 1;
                Ok(())
            },
            "imagelayer" => |attrs| {
TatriX's avatar
TatriX committed
                image_layers.push(ImageLayer::new(parser, attrs, layer_index)?);
                layer_index += 1;
                Ok(())
            },
            "properties" => |_| {
TatriX's avatar
TatriX committed
                properties = parse_properties(parser)?;
                Ok(())
            },
            "objectgroup" => |attrs| {
TatriX's avatar
TatriX committed
                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,
        })
Matthew Hall's avatar
Matthew Hall committed
    /// This function will return the correct Tileset given a GID.
Matthew Hall's avatar
Matthew Hall committed
    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)]
Matthew Hall's avatar
Matthew Hall committed
pub enum Orientation {
Matthew Hall's avatar
Matthew Hall committed
    Orthogonal,
    Isometric,
Matthew Hall's avatar
Matthew Hall committed
}

impl FromStr for Orientation {
    type Err = ParseTileError;
    fn from_str(s: &str) -> Result<Orientation, ParseTileError> {
Matthew Hall's avatar
Matthew Hall committed
        match s {
            "orthogonal" => Ok(Orientation::Orthogonal),
            "isometric" => Ok(Orientation::Isometric),
            "staggered" => Ok(Orientation::Staggered),
            "hexagonal" => Ok(Orientation::Hexagonal),
            _ => Err(ParseTileError::OrientationError),
Matthew Hall's avatar
Matthew Hall committed
/// A tileset, usually the tilesheet image.
#[derive(Debug, PartialEq, Clone)]
pub struct Tileset {
Matthew Hall's avatar
Matthew Hall committed
    /// The GID of the first tile stored
Matthew Hall's avatar
Matthew Hall committed
    pub first_gid: u32,
Matthew Hall's avatar
Matthew Hall committed
    pub name: String,
Matthew Hall's avatar
Matthew Hall committed
    pub tile_width: u32,
    pub tile_height: u32,
    pub spacing: u32,
    pub margin: u32,
Jonathan Nilsson's avatar
Jonathan Nilsson committed
    pub tilecount: Option<u32>,
    /// The Tiled spec says that a tileset can have mutliple images so a `Vec`
Matthew Hall's avatar
Matthew Hall committed
    /// is used. Usually you will only use one.
    pub tiles: Vec<Tile>,
    pub properties: Properties,
    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> {
Jonathan Nilsson's avatar
Jonathan Nilsson committed
        let ((spacing, margin, tilecount), (first_gid, name, width, height)) = get_attrs!(
           optionals: [
                ("spacing", spacing, |v:String| v.parse().ok()),
                ("margin", margin, |v:String| v.parse().ok()),
Jonathan Nilsson's avatar
Jonathan Nilsson committed
                ("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 properties = HashMap::new();
        parse_tag!(parser, "tileset", {
            "image" => |attrs| {
TatriX's avatar
TatriX committed
                images.push(Image::new(parser, attrs)?);
            "properties" => |_| {
                properties = parse_properties(parser)?;
                Ok(())
            },
            "tile" => |attrs| {
TatriX's avatar
TatriX committed
                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 {
Matt Stavola's avatar
Matt Stavola committed
            match tileset_parser
                .next()
                .map_err(TiledError::XmlDecodingError)?
            {
                XmlEvent::StartElement {
                    name, attributes, ..
                } => {
                        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> {
Jonathan Nilsson's avatar
Jonathan Nilsson committed
        let ((spacing, margin, tilecount), (name, width, height)) = get_attrs!(
            optionals: [
                ("spacing", spacing, |v:String| v.parse().ok()),
                ("margin", margin, |v:String| v.parse().ok()),
Jonathan Nilsson's avatar
Jonathan Nilsson committed
                ("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| {
TatriX's avatar
TatriX committed
                images.push(Image::new(parser, attrs)?);
                Ok(())
            },
            "tile" => |attrs| {
TatriX's avatar
TatriX committed
                tiles.push(Tile::new(parser, attrs)?);
            "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),
Jonathan Nilsson's avatar
Jonathan Nilsson committed
            tilecount: tilecount,
            images: images,
            tiles: tiles,
#[derive(Debug, PartialEq, Clone)]
    pub images: Vec<Image>,
    pub properties: Properties,
    pub objectgroup: Option<ObjectGroup>,
mraof's avatar
mraof committed
    pub animation: Option<Vec<Frame>>,
    pub tile_type: Option<String>,
    pub probability: f32,
    fn new<R: Read>(
        parser: &mut EventReader<R>,
        attrs: Vec<OwnedAttribute>,
    ) -> Result<Tile, TiledError> {
        let ((tile_type, probability), id) = get_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 properties = HashMap::new();
mraof's avatar
mraof committed
        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)]
Matthew Hall's avatar
Matthew Hall committed
pub struct Image {
Matthew Hall's avatar
Matthew Hall committed
    /// The filepath of the image
Matthew Hall's avatar
Matthew Hall committed
    pub source: String,
    pub width: i32,
    pub height: i32,
Matthew Hall's avatar
Matthew Hall committed
    pub transparent_colour: Option<Colour>,
Matthew Hall's avatar
Matthew Hall committed
}

impl Image {
    fn new<R: Read>(
        parser: &mut EventReader<R>,
        attrs: Vec<OwnedAttribute>,
    ) -> Result<Image, TiledError> {
Matthew Hall's avatar
Matthew Hall committed
        let (c, (s, w, h)) = get_attrs!(
Matthew Hall's avatar
Matthew Hall committed
            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,
        })
Matthew Hall's avatar
Matthew Hall committed
    }
}

Jengamon's avatar
Jengamon committed
/// 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 {
Matthew Hall's avatar
Matthew Hall committed
    pub name: String,
    pub opacity: f32,
    pub visible: bool,
Matthew Hall's avatar
Matthew Hall committed
    /// 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.
Jengamon's avatar
Jengamon committed
    pub tiles: Vec<Vec<LayerTile>>,
    pub properties: Properties,
    pub layer_index: u32,
    fn new<R: Read>(
        parser: &mut EventReader<R>,
        attrs: Vec<OwnedAttribute>,
        width: u32,
        layer_index: u32,
    ) -> Result<Layer, TiledError> {
        let ((o, v), n) = get_attrs!(
            optionals: [
                ("opacity", opacity, |v:String| v.parse().ok()),
                ("visible", visible, |v:String| v.parse().ok().map(|x:i32| x == 1)),
            ],
            required: [
                ("name", name, |v| Some(v)),
            ],
            TiledError::MalformedAttributes("layer must have a name".to_string())
        );
        let mut tiles = Vec::new();
        let mut properties = HashMap::new();
        parse_tag!(parser, "layer", {
            "data" => |attrs| {
TatriX's avatar
TatriX committed
                tiles = parse_data(parser, attrs, width)?;
                Ok(())
            },
            "properties" => |_| {
TatriX's avatar
TatriX committed
                properties = parse_properties(parser)?;
                Ok(())
            },
        });

        Ok(Layer {
            name: n,
            opacity: o.unwrap_or(1.0),
            visible: v.unwrap_or(true),
            tiles: tiles,
            properties: properties,
            layer_index,
        })
TatriX's avatar
TatriX committed
#[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,
TatriX's avatar
TatriX committed
}

impl ImageLayer {
    fn new<R: Read>(
        parser: &mut EventReader<R>,
        attrs: Vec<OwnedAttribute>,
        layer_index: u32,
    ) -> Result<ImageLayer, TiledError> {
TatriX's avatar
TatriX committed
        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)),
            ],
TatriX's avatar
TatriX committed
            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(())
            },
        });
TatriX's avatar
TatriX committed
        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,
#[derive(Debug, PartialEq, Clone)]
Matthew Hall's avatar
Matthew Hall committed
pub struct ObjectGroup {
    pub name: String,
    pub opacity: f32,
    pub visible: bool,
Matthew Hall's avatar
Matthew Hall committed
    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,
Matthew Hall's avatar
Matthew Hall committed
}

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!(
Matthew Hall's avatar
Matthew Hall committed
            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()),
            ],
            TiledError::MalformedAttributes("object groups must have a name".to_string())
        );
Matthew Hall's avatar
Matthew Hall committed
        let mut objects = Vec::new();
        let mut properties = HashMap::new();
        parse_tag!(parser, "objectgroup", {
            "object" => |attrs| {
TatriX's avatar
TatriX committed
                objects.push(Object::new(parser, attrs)?);
            "properties" => |_| {
TatriX's avatar
TatriX committed
                properties = parse_properties(parser)?;
        });
        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,
#[derive(Debug, PartialEq, Clone)]
    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 rotation: f32,
    pub visible: bool,
    pub shape: ObjectShape,
Matthew Hall's avatar
Matthew Hall committed
}

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!(
Matthew Hall's avatar
Matthew Hall committed
            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()),
                ("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())
        );
Matthew Hall's avatar
Matthew Hall committed
        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| {
TatriX's avatar
TatriX committed
                shape = Some(Object::new_polyline(attrs)?);
TatriX's avatar
TatriX committed
                shape = Some(Object::new_polygon(attrs)?);
            "point" => |_| {
                shape = Some(Object::new_point(x, y)?);
                Ok(())
            },
TatriX's avatar
TatriX committed
                properties = parse_properties(parser)?;
        let shape = shape.unwrap_or(ObjectShape::Rect {
            width: w,
            height: h,
        });

        Ok(Object {
            id: id,
            gid: gid,
            name: n.clone(),
            obj_type: t.clone(),
    fn new_polyline(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> {
Matthew Hall's avatar
Matthew Hall committed
        let ((), s) = get_attrs!(
            attrs,
            optionals: [],
            required: [
                ("points", points, |v| Some(v)),
            ],
            TiledError::MalformedAttributes("A polyline must have points".to_string())
        );
TatriX's avatar
TatriX committed
        let points = Object::parse_points(s)?;
        Ok(ObjectShape::Polyline { points: points })
    fn new_polygon(attrs: Vec<OwnedAttribute>) -> Result<ObjectShape, TiledError> {
Matthew Hall's avatar
Matthew Hall committed
        let ((), s) = get_attrs!(
            attrs,
            optionals: [],
            required: [
                ("points", points, |v| Some(v)),
            ],
            TiledError::MalformedAttributes("A polygon must have points".to_string())
        );
TatriX's avatar
TatriX committed
        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(' ');
Matthew Hall's avatar
Matthew Hall committed
        let mut points = Vec::new();
Matthew Hall's avatar
Matthew Hall committed
        for v in pairs.map(|p| p.split(',')) {
Kevin Balz's avatar
Kevin Balz committed
            let v: Vec<&str> = v.collect();
Matthew Hall's avatar
Matthew Hall committed
            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());
Matthew Hall's avatar
Matthew Hall committed
            if x.is_none() || y.is_none() {
                return Err(TiledError::MalformedAttributes(
                    "one of polyline's points does not have i32eger coordinates".to_string(),
                ));
Matthew Hall's avatar
Matthew Hall committed
            }
            points.push((x.unwrap(), y.unwrap()));
        }
        Ok(points)
    }
}

mraof's avatar
mraof committed
#[derive(Debug, PartialEq, Clone)]
pub struct Frame {
Matt Stavola's avatar
Matt Stavola committed
    pub tile_id: u32,
    pub duration: u32,
mraof's avatar
mraof committed
}

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())
        );
mraof's avatar
mraof committed
        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| {
TatriX's avatar
TatriX committed
            animation.push(Frame::new(attrs)?);