Skip to content
Snippets Groups Projects
lib.rs 19.30 KiB
#![allow(unstable)]
#![feature(slicing_syntax)]
extern crate flate2;
extern crate xml;
extern crate "rustc-serialize" as serialize;

use std::old_io::{BufReader, IoError, EndOfFile};
use std::str::FromStr;
use std::collections::HashMap;
use std::fmt;
use xml::reader::EventReader;
use xml::reader::events::XmlEvent::*;
use xml::attribute::OwnedAttribute;
use serialize::base64::{FromBase64, FromBase64Error};
use flate2::reader::ZlibDecoder;
use std::num::from_str_radix;

// 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_slice() {
                    $($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() {
                StartElement {name, attributes, ..} => {
                    if false {}
                    $(else if name.local_name == $open_tag {
                        match $open_method(attributes) {
                            Ok(()) => {},
                            Err(e) => return Err(e)
                        };
                    })*
                }
                EndElement {name, ..} => {
                    if name.local_name == $close_tag {
                        break;
                    }
                }
                EndDocument => return Err(TiledError::PrematureEnd("Document ended before we expected.".to_string())),
                _ => {}
            }
        }
    }
}

#[derive(Show)]
pub struct Colour {
    pub red: u8,
    pub green: u8,
    pub blue: u8
}

impl FromStr for Colour {
    fn from_str(s: &str) -> Option<Colour> {
        let s = if s.starts_with("#") {
            &s[1..]
        } else { 
            s 
        };
        if s.len() != 6 {
            return None;
        }
        let r = from_str_radix(&s[0..2], 16);
        let g = from_str_radix(&s[2..4], 16);
        let b = from_str_radix(&s[4..6], 16);
        if r.is_some() && g.is_some() && b.is_some() {
            return Some(Colour {red: r.unwrap(), green: g.unwrap(), blue: b.unwrap()})
        }
        None
    }
}

/// Errors which occured when parsing the file
#[derive(Show)]
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(IoError),
    DecodingError(FromBase64Error),
    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::DecodingError(ref e) => write!(fmt, "{}", e),
            TiledError::PrematureEnd(ref e) => write!(fmt, "{}", e),
            TiledError::Other(ref s) => write!(fmt, "{}", s),
        }
    }
}

pub type Properties = HashMap<String, String>;

fn parse_properties<B: Buffer>(parser: &mut EventReader<B>) -> Result<Properties, TiledError> {
    let mut p = HashMap::new();
    parse_tag!(parser, "properties",
               "property" => |&mut:attrs:Vec<OwnedAttribute>| {
                    let ((), (k, v)) = get_attrs!(
                        attrs,
                        optionals: [],
                        required: [("name", key, |&:v| Some(v)),
                                   ("value", value, |&:v| Some(v))],
                        TiledError::MalformedAttributes("property must have a name and a value".to_string()));
                    p.insert(k, v);
                    Ok(())
               });
    Ok(p)
}

/// All Tiled files will be parsed i32o this. Holds all the layers and tilesets
#[derive(Show)]
pub struct Map {
    pub version: String,
    pub orientation: Orientation,
    pub width: u32,
    pub height: u32,
    pub tile_width: u32,
    pub tile_height: u32,
    pub tilesets: Vec<Tileset>,
    pub layers: Vec<Layer>,
    pub object_groups: Vec<ObjectGroup>,
    pub properties: Properties,
    pub background_colour: Option<Colour>,
}

impl Map {
    fn new<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>) -> Result<Map, TiledError>  {
        let (c, (v, o, w, h, tw, th)) = get_attrs!(
            attrs, 
            optionals: [("backgroundcolor", colour, |&:v:String| v.parse())], 
            required: [("version", version, |&:v| Some(v)),
                       ("orientation", orientation, |&:v:String| v.parse()),
                       ("width", width, |&:v:String| v.parse()),
                       ("height", height, |&:v:String| v.parse()),
                       ("tilewidth", tile_width, |&:v:String| v.parse()),
                       ("tileheight", tile_height, |&:v:String| v.parse())],
            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 properties = HashMap::new();
        let mut object_groups = Vec::new();
        parse_tag!(parser, "map", 
                   "tileset" => |&mut: attrs| {
                        tilesets.push(try!(Tileset::new(parser, attrs)));
                        Ok(())
                   },
                   "layer" => |&mut:attrs| {
                        layers.push(try!(Layer::new(parser, attrs, w )));
                        Ok(())
                   },
                   "properties" => |&mut:_| {
                        properties = try!(parse_properties(parser));
                        Ok(())
                   },
                   "objectgroup" => |&mut:attrs| {
                       object_groups.push(try!(ObjectGroup::new(parser, attrs)));
                       Ok(())
                   });
        Ok(Map {version: v, orientation: o,
                width: w, height: h, 
                tile_width: tw, tile_height: th,
                tilesets: tilesets, layers: layers, object_groups: object_groups,
                properties: properties,
                background_colour: c,})
    }

    /// 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(Show)]
pub enum Orientation {
    Orthogonal,
    Isometric,
    Staggered
}

impl FromStr for Orientation {
    fn from_str(s: &str) -> Option<Orientation> {
        match s {
            "orthogonal" => Some(Orientation::Orthogonal),
            "isometric" => Some(Orientation::Isometric),
            "Staggered" => Some(Orientation::Staggered),
            _ => None
        }
    }
}

/// A tileset, usually the tilesheet image.
#[derive(Show)]
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,
    /// 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>
}

impl Tileset {
    fn new<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>) -> Result<Tileset, TiledError> {
        let ((s, m), (g, n, w, h)) = get_attrs!(
           attrs,
           optionals: [("spacing", spacing, |&:v:String| v.parse()),
                       ("margin", margin, |&:v:String| v.parse())],
           required: [("firstgid", first_gid, |&:v:String| v.parse()),
                      ("name", name, |&:v| Some(v)),
                      ("tilewidth", width, |&:v:String| v.parse()),
                      ("tileheight", height, |&:v:String| v.parse())],
           TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()));

        let mut images = Vec::new();
        parse_tag!(parser, "tileset",
                   "image" => |&mut:attrs| {
                        images.push(try!(Image::new(parser, attrs)));
                        Ok(())
                   });
        Ok(Tileset {first_gid: g, 
                    name: n, 
                    tile_width: w, tile_height: h, 
                    spacing: s.unwrap_or(0),
                    margin: m.unwrap_or(0),
                    images: images})
   }
}

#[derive(Show)]
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<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>) -> Result<Image, TiledError> {
        let (c, (s, w, h)) = get_attrs!(
            attrs,
            optionals: [("trans", trans, |&:v:String| v.parse())],
            required: [("source", source, |&:v| Some(v)),
                       ("width", width, |&:v:String| v.parse()),
                       ("height", height, |&:v:String| v.parse())],
            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})
    }
}

#[derive(Show)]
pub struct Layer {
    pub name: String,
    pub opacity: f32,
    pub visible: bool,
    /// 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: Vec<Vec<u32>>,
    pub properties: Properties
}

impl Layer {
    fn new<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>, width: u32) -> Result<Layer, TiledError> {
        let ((o, v), n) = get_attrs!(
            attrs,
            optionals: [("opacity", opacity, |&:v:String| v.parse()),
                        ("visible", visible, |&:v:String| v.parse().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" => |&mut:attrs| {
                        tiles = try!(parse_data(parser, attrs, width));
                        Ok(())
                   },
                   "properties" => |&mut:_| {
                        properties = try!(parse_properties(parser));
                        Ok(())
                   });
        Ok(Layer {name: n, opacity: o.unwrap_or(1.0), visible: v.unwrap_or(true), tiles: tiles,
                  properties: properties})
    }
}

#[derive(Show)]
pub struct ObjectGroup {
    pub name: String,
    pub opacity: f32,
    pub visible: bool,
    pub objects: Vec<Object>,
    pub colour: Option<Colour>,
}

impl ObjectGroup {
    fn new<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>) -> Result<ObjectGroup, TiledError> {
        let ((o, v, c), n) = get_attrs!(
            attrs,
            optionals: [("opacity", opacity, |&:v:String| v.parse()),
                        ("visible", visible, |&:v:String| v.parse().map(|x:i32| x == 1)),
                        ("color", colour, |&:v:String| v.parse())],
            required: [("name", name, |&:v| Some(v))],
            TiledError::MalformedAttributes("object groups must have a name".to_string()));
        let mut objects = Vec::new();
        parse_tag!(parser, "objectgroup",
                   "object" => |&mut:attrs| {
                        objects.push(try!(Object::new(parser, attrs)));
                        Ok(())
                   });
        Ok(ObjectGroup {name: n, 
                        opacity: o.unwrap_or(1.0), visible: v.unwrap_or(true), 
                        objects: objects,
                        colour: c})
    }
}

#[derive(Show)]
pub enum Object {
     Rect { x: i32,  y: i32,  width: u32,  height: u32,  visible: bool},
     Ellipse { x: i32,  y: i32,  width: u32,  height: u32,  visible: bool},
     Polyline { x: i32,  y: i32,  points: Vec<(i32, i32)>,  visible: bool},
     Polygon { x: i32,  y: i32,  points: Vec<(i32, i32)>,  visible: bool}
}

impl Object {
    fn new<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>) -> Result<Object, TiledError> {
        let ((w, h, v), (x, y)) = get_attrs!(
            attrs,
            optionals: [("width", width, |&:v:String| v.parse()),
                        ("height", height, |&:v:String| v.parse()),
                        ("visible", visible, |&:v:String| v.parse())],
            required: [("x", x, |&:v:String| v.parse()),
                       ("y", y, |&:v:String| v.parse())],
            TiledError::MalformedAttributes("objects must have an x and a y number".to_string()));
        let mut obj = None;
        let v = v.unwrap_or(true);
        parse_tag!(parser, "object",
                   "ellipse" => |&mut:_| {
                        if w.is_none() || h.is_none() {
                            return Err(TiledError::MalformedAttributes("An ellipse must have a width and height".to_string()));
                        }
                        let (w, h) = (w.unwrap(), h.unwrap());
                        obj = Some(Object::Ellipse {x: x, y: y, 
                                            width: w , height: h ,
                                            visible: v});
                        Ok(())
                    },
                    "polyline" => |&mut:attrs| {
                        obj = Some(try!(Object::new_polyline(x, y, v, attrs)));
                        Ok(())
                    },
                    "polygon" => |&mut:attrs| {
                        obj = Some(try!(Object::new_polygon(x, y, v, attrs)));
                        Ok(())
                    });
        if obj.is_some() {
            Ok(obj.unwrap())
        } else if w.is_some() && h.is_some() {
            let w = w.unwrap();
            let h = h.unwrap();
            Ok(Object::Rect {x: x, y: y, width: w, height: h, visible: v})
        } else {
            Err(TiledError::MalformedAttributes("A rect must have a width and a height".to_string()))
        }
    }

    fn new_polyline(x: i32, y: i32, v: bool, attrs: Vec<OwnedAttribute>) -> Result<Object, TiledError> {
        let ((), s) = get_attrs!(
            attrs,
            optionals: [],
            required: [("points", points, |&:v| Some(v))],
            TiledError::MalformedAttributes("A polyline must have points".to_string()));
       let points = try!(Object::parse_points(s));
       Ok(Object::Polyline {x: x, y: y, points: points, visible: v})
    }

    fn new_polygon(x: i32, y: i32, v: bool, attrs: Vec<OwnedAttribute>) -> Result<Object, TiledError> {
        let ((), s) = get_attrs!(
            attrs,
            optionals: [],
            required: [("points", points, |&:v| Some(v))],
            TiledError::MalformedAttributes("A polygon must have points".to_string()));
       let points = try!(Object::parse_points(s));
       Ok(Object::Polygon {x: x, y: y, points: points, visible: v})
    }

    fn parse_points(s: String) -> Result<Vec<(i32, i32)>, TiledError> {
        let pairs = s.split(' ');
        let mut points = Vec::new();
        for v in pairs.map(|&:p| p.splitn(1, ',')) {
            let v: Vec<&str> = v.clone().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(), v[1].parse());
            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)
    }
}

fn parse_data<B: Buffer>(parser: &mut EventReader<B>, attrs: Vec<OwnedAttribute>, width: u32) -> Result<Vec<Vec<u32>>, TiledError> {
    let ((), (e, c)) = get_attrs!(
        attrs,
        optionals: [],
        required: [("encoding", encoding, |&:v| Some(v)),
                   ("compression", compression, |&:v| Some(v))],
        TiledError::MalformedAttributes("data must have an encoding and a compression".to_string()));
    if !(e == "base64" && c == "zlib") {
        return Err(TiledError::Other("Only base64 and zlib allowed for the moment".to_string()));
    }
    loop {
        match parser.next() {
            Characters(s) => {
                match s.trim().from_base64() {
                    Ok(v) => {
                        let mut zd = ZlibDecoder::new(BufReader::new(v.as_slice()));
                        let mut data = Vec::new();
                        let mut row = Vec::new();
                        loop {
                            match zd.read_le_u32() {
                                Ok(v) => row.push(v),
                                Err(IoError{kind, ..}) if kind == EndOfFile => return Ok(data),
                                Err(e) => return Err(TiledError::DecompressingError(e))
                            }
                            if row.len() as u32 == width {
                                data.push(row);
                                row = Vec::new();
                            }
                        }
                    }
                    Err(e) => return Err(TiledError::DecodingError(e))
                }
            }
            EndElement {name, ..} => {
                if name.local_name == "data" {
                    return Ok(Vec::new());
                }
            }
            _ => {}
        }
    }
}

/// Parse a buffer hopefully containing the contents of a Tiled file and try to
/// parse it.
pub fn parse<B: Buffer>(reader: B) -> Result<Map, TiledError> {
    let mut parser = EventReader::new(reader);
    loop {
        match parser.next() {
            StartElement {name, attributes, ..}  => {
                if name.local_name == "map" {
                    return Map::new(&mut parser, attributes);
                }
            }
            EndDocument => return Err(TiledError::PrematureEnd("Document ended before map was parsed".to_string())),
            _ => {}
        }
    }
}