diff --git a/examples/main.rs b/examples/main.rs index c4ae445d7c21f05a1d29504181b649ad76678660..c683639689f56f202e4e62dfa59d6489ee98f4dc 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,11 +1,13 @@ use std::fs::File; -use std::path::Path; -use tiled::parse; +use std::path::PathBuf; + +use tiled::map::Map; fn main() { - let file = File::open(&Path::new("assets/tiled_base64_zlib.tmx")).unwrap(); + let path = PathBuf::from("assets/tiled_base64_zlib.tmx"); + let file = File::open(&path).unwrap(); println!("Opened file"); - let map = parse(file).unwrap(); + let map = Map::parse_reader(file, Some(&path)).unwrap(); println!("{:?}", map); - println!("{:?}", map.get_tileset_by_gid(22)); + println!("{:?}", map.tileset_by_gid(22)); } diff --git a/src/error.rs b/src/error.rs index 6a502c21c01edaa186ad2a9cca205ee7c963e74f..e3f243c1bf4fab2d73423338d85399337b7017fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use std::fmt; #[derive(Debug, Copy, Clone)] pub enum ParseTileError { - ColourError, + ColorError, OrientationError, } diff --git a/src/image.rs b/src/image.rs index 351cc5bf61ebf59f25543a9ed454780bb4fecc0b..21166f4b0837a6ffc39ef15cc1401b986787a771 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,7 +2,7 @@ use std::io::Read; use xml::{attribute::OwnedAttribute, EventReader}; -use crate::{error::TiledError, properties::Colour, util::*}; +use crate::{error::TiledError, properties::Color, util::*}; #[derive(Debug, PartialEq, Eq, Clone)] pub struct Image { @@ -10,7 +10,7 @@ pub struct Image { pub source: String, pub width: i32, pub height: i32, - pub transparent_colour: Option<Colour>, + pub transparent_colour: Option<Color>, } impl Image { diff --git a/src/lib.rs b/src/lib.rs index 06805e70b8daa4ce9e7102a1f65b57bc57d54e91..2033ef1188ba07f1a3501cfb2d3f1f5213cb5548 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,55 +8,3 @@ 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::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}; - -// 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, -/// in which case this function may be required. -pub fn parse_with_path<R: Read>(reader: R, path: &Path) -> Result<Map, TiledError> { - parse_impl(reader, Some(path)) -} - -/// Parse a file hopefully containing a Tiled map and try to parse it. If the -/// file has an external tileset, the tileset file will be loaded using a path -/// relative to the map file's path. -pub fn parse_file(path: &Path) -> Result<Map, TiledError> { - let file = File::open(path) - .map_err(|_| TiledError::Other(format!("Map file not found: {:?}", path)))?; - parse_impl(file, Some(path)) -} - -/// Parse a buffer hopefully containing the contents of a Tiled file and try to -/// parse it. -pub fn parse<R: Read>(reader: R) -> Result<Map, TiledError> { - parse_impl(reader, None) -} - -/// Parse a buffer hopefully containing the contents of a Tiled tileset. -/// -/// External tilesets do not have a firstgid attribute. That lives in the -/// map. You must pass in `first_gid`. If you do not need to use gids for anything, -/// passing in 1 will work fine. -pub fn parse_tileset<R: Read>(reader: R, first_gid: u32) -> Result<Tileset, TiledError> { - Tileset::new_external(reader, first_gid) -} diff --git a/src/map.rs b/src/map.rs index f9271fa2dd1234210ab2dcb27662a1b17190750f..7fb0342c1807e94a139f85b4a82283f956bf9333 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,38 +1,94 @@ -use std::{collections::HashMap, fmt, io::Read, path::Path, str::FromStr}; +use std::{ + collections::HashMap, + fmt, + fs::File, + io::Read, + path::{Path, PathBuf}, + str::FromStr, +}; -use xml::{attribute::OwnedAttribute, EventReader}; +use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader}; use crate::{ error::{ParseTileError, TiledError}, layers::{ImageLayer, Layer}, objects::ObjectGroup, - properties::{parse_properties, Colour, Properties}, + properties::{parse_properties, Color, Properties}, tileset::Tileset, - util::*, + util::{get_attrs, parse_tag}, }; /// All Tiled files will be parsed into this. Holds all the layers and tilesets #[derive(Debug, PartialEq, Clone)] pub struct Map { + /// The TMX format version this map was saved to. pub version: String, + /// The orientation of this map. pub orientation: Orientation, - /// Width of the map, in tiles + /// Width of the map, in tiles. pub width: u32, - /// Height of the map, in tiles + /// Height of the map, in tiles. pub height: u32, + /// Tile width, in pixels. pub tile_width: u32, + /// Tile height, in pixels. pub tile_height: u32, + /// The tilesets present in this map. pub tilesets: Vec<Tileset>, + /// The tile layers present in this map. pub layers: Vec<Layer>, + /// The image layers present in this map. pub image_layers: Vec<ImageLayer>, + /// The object groups present in this map. pub object_groups: Vec<ObjectGroup>, + /// The custom properties of this map. pub properties: Properties, - pub background_colour: Option<Colour>, + /// The background color of this map, if any. + pub background_color: Option<Color>, + /// Whether this map is infinite or not. pub infinite: bool, + /// Where this map was loaded from. + /// If fully embedded (loaded with path = `None`), this will return `None`. + pub source: Option<PathBuf>, } impl Map { - pub(crate) fn new<R: Read>( + /// 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, + /// in which case this function may be required. + /// The path may be skipped if the map is fully embedded (Doesn't refer to external files). + pub fn parse_reader<R: Read>(reader: R, path: Option<&Path>) -> Result<Self, 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 Self::parse_xml(&mut parser, attributes, path); + } + } + XmlEvent::EndDocument => { + return Err(TiledError::PrematureEnd( + "Document ended before map was parsed".to_string(), + )) + } + _ => {} + } + } + } + + /// Parse a file hopefully containing a Tiled map and try to parse it. If the + /// file has an external tileset, the tileset file will be loaded using a path + /// relative to the map file's path. + pub fn parse_file(path: &Path) -> Result<Self, TiledError> { + let file = File::open(path) + .map_err(|_| TiledError::Other(format!("Map file not found: {:?}", path)))?; + Self::parse_reader(file, Some(path)) + } + + fn parse_xml<R: Read>( parser: &mut EventReader<R>, attrs: Vec<OwnedAttribute>, map_path: Option<&Path>, @@ -62,7 +118,7 @@ impl Map { let mut layer_index = 0; parse_tag!(parser, "map", { "tileset" => |attrs| { - tilesets.push(Tileset::new(parser, attrs, map_path)?); + tilesets.push(Tileset::parse_xml(parser, attrs, map_path)?); Ok(()) }, "layer" => |attrs| { @@ -97,13 +153,14 @@ impl Map { image_layers, object_groups, properties, - background_colour: c, + background_color: c, infinite: infinite.unwrap_or(false), + source: map_path.and_then(|p| Some(p.to_owned())), }) } /// This function will return the correct Tileset given a GID. - pub fn get_tileset_by_gid(&self, gid: u32) -> Option<&Tileset> { + pub fn 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() { diff --git a/src/objects.rs b/src/objects.rs index 3e5edf919dea4938bee8111f7f77ce5597455203..0de2f49b5df6b421b289c4fc3800379ed2aa694b 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -4,7 +4,7 @@ use xml::{attribute::OwnedAttribute, EventReader}; use crate::{ error::TiledError, - properties::{parse_properties, Colour, Properties}, + properties::{parse_properties, Color, Properties}, util::{get_attrs, parse_tag}, }; @@ -14,7 +14,7 @@ pub struct ObjectGroup { pub opacity: f32, pub visible: bool, pub objects: Vec<Object>, - pub colour: Option<Colour>, + pub colour: Option<Color>, /** * Layer index is not preset for tile collision boxes */ diff --git a/src/properties.rs b/src/properties.rs index 6d9ae472c87c84a822a08ad963bf5a1ef0c4ecbd..9c3dba837693c20443836d75526a339c4923ab01 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -8,31 +8,31 @@ use crate::{ }; #[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct Colour { +pub struct Color { pub red: u8, pub green: u8, pub blue: u8, } -impl FromStr for Colour { +impl FromStr for Color { type Err = ParseTileError; - fn from_str(s: &str) -> Result<Colour, ParseTileError> { + fn from_str(s: &str) -> Result<Color, ParseTileError> { let s = if s.starts_with("#") { &s[1..] } else { s }; if s.len() != 6 { - return Err(ParseTileError::ColourError); + return Err(ParseTileError::ColorError); } 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 { + return Ok(Color { red: r.unwrap(), green: g.unwrap(), blue: b.unwrap(), }); } - Err(ParseTileError::ColourError) + Err(ParseTileError::ColorError) } } diff --git a/src/tileset.rs b/src/tileset.rs index fe45353523aea6176296d6718ccd682e0688b647..264c78609159edb108e366d6319f583aa3c26cfc 100644 --- a/src/tileset.rs +++ b/src/tileset.rs @@ -1,6 +1,17 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use xml::attribute::OwnedAttribute; +use xml::reader::XmlEvent; +use xml::EventReader; + +use crate::error::TiledError; +use crate::image::Image; use crate::properties::{parse_properties, Properties}; +use crate::tile::Tile; use crate::util::*; -use crate::*; // FIXME /// A tileset, usually the tilesheet image. #[derive(Debug, PartialEq, Clone)] @@ -21,15 +32,25 @@ pub struct Tileset { } impl Tileset { - pub(crate) fn new<R: Read>( + /// Parse a buffer hopefully containing the contents of a Tiled tileset. + /// + /// External tilesets do not have a firstgid attribute. That lives in the + /// map. You must pass in `first_gid`. If you do not need to use gids for anything, + /// passing in 1 will work fine. + pub fn parse<R: Read>(reader: R, first_gid: u32) -> Result<Self, TiledError> { + Tileset::new_external(reader, first_gid) + } + + pub(crate) fn parse_xml<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)) + Tileset::parse_xml_embedded(parser, &attrs) + .or_else(|_| Tileset::parse_xml_reference(&attrs, map_path)) } - fn new_internal<R: Read>( + fn parse_xml_embedded<R: Read>( parser: &mut EventReader<R>, attrs: &Vec<OwnedAttribute>, ) -> Result<Tileset, TiledError> { @@ -81,7 +102,7 @@ impl Tileset { }) } - fn new_reference( + fn parse_xml_reference( attrs: &Vec<OwnedAttribute>, map_path: Option<&Path>, ) -> Result<Tileset, TiledError> { diff --git a/src/util.rs b/src/util.rs index 61dcfd1c24bf24cdbebaba82fe17da8e93d048b4..96bc2624f0ceaf73d1fa28a2e3bc17d706013cbd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -26,9 +26,7 @@ macro_rules! get_attrs { } /// Goes through the children of the tag and will call the correct function for -/// that child. Closes the tag -/// -/// Not quite as bad. +/// that child. Closes the tag. macro_rules! parse_tag { ($parser:expr, $close_tag:expr, {$($open_tag:expr => $open_method:expr),* $(,)*}) => { loop { @@ -278,24 +276,3 @@ pub(crate) fn convert_to_tile(all: &Vec<u8>, width: u32) -> Vec<Vec<LayerTile>> } 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 92b1a284b368e0d65c51ee4724bf750a00383bc0..5fa1dc1b83e2aa1ab98132f375fcc77cfc42485f 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,25 +1,20 @@ use std::fs::File; use std::path::Path; use tiled::{ - error::TiledError, layers::LayerData, map::Map, parse, parse_file, parse_tileset, - properties::PropertyValue, + error::TiledError, layers::LayerData, map::Map, properties::PropertyValue, tileset::Tileset, }; -fn read_from_file(p: &Path) -> Result<Map, TiledError> { +fn parse_map_without_source(p: &Path) -> Result<Map, TiledError> { let file = File::open(p).unwrap(); - return parse(file); -} - -fn read_from_file_with_path(p: &Path) -> Result<Map, TiledError> { - return parse_file(p); + return Map::parse_reader(file, None); } #[test] fn test_gzip_and_zlib_encoded_and_raw_are_the_same() { - let z = read_from_file(&Path::new("assets/tiled_base64_zlib.tmx")).unwrap(); - let g = read_from_file(&Path::new("assets/tiled_base64_gzip.tmx")).unwrap(); - let r = read_from_file(&Path::new("assets/tiled_base64.tmx")).unwrap(); - let c = read_from_file(&Path::new("assets/tiled_csv.tmx")).unwrap(); + let z = parse_map_without_source(&Path::new("assets/tiled_base64_zlib.tmx")).unwrap(); + let g = parse_map_without_source(&Path::new("assets/tiled_base64_gzip.tmx")).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_base64.tmx")).unwrap(); + let c = parse_map_without_source(&Path::new("assets/tiled_csv.tmx")).unwrap(); assert_eq!(z, g); assert_eq!(z, r); assert_eq!(z, c); @@ -40,21 +35,34 @@ fn test_gzip_and_zlib_encoded_and_raw_are_the_same() { #[test] fn test_external_tileset() { - let r = read_from_file(&Path::new("assets/tiled_base64.tmx")).unwrap(); - let e = read_from_file_with_path(&Path::new("assets/tiled_base64_external.tmx")).unwrap(); - assert_eq!(r, e); + let r = parse_map_without_source(&Path::new("assets/tiled_base64.tmx")).unwrap(); + let e = Map::parse_file(&Path::new("assets/tiled_base64_external.tmx")).unwrap(); + // Compare everything BUT source + assert_eq!(r.version, e.version); + assert_eq!(r.orientation, e.orientation); + assert_eq!(r.width, e.width); + assert_eq!(r.height, e.height); + assert_eq!(r.tile_width, e.tile_width); + assert_eq!(r.tile_height, e.tile_height); + assert_eq!(r.tilesets, e.tilesets); + assert_eq!(r.layers, e.layers); + assert_eq!(r.image_layers, e.image_layers); + assert_eq!(r.object_groups, e.object_groups); + assert_eq!(r.properties, e.properties); + assert_eq!(r.background_color, e.background_color); + assert_eq!(r.infinite, e.infinite); } #[test] fn test_just_tileset() { - let r = read_from_file(&Path::new("assets/tiled_base64.tmx")).unwrap(); - let t = parse_tileset(File::open(Path::new("assets/tilesheet.tsx")).unwrap(), 1).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_base64.tmx")).unwrap(); + let t = Tileset::parse(File::open(Path::new("assets/tilesheet.tsx")).unwrap(), 1).unwrap(); assert_eq!(r.tilesets[0], t); } #[test] fn test_infinite_tileset() { - let r = read_from_file_with_path(&Path::new("assets/tiled_base64_zlib_infinite.tmx")).unwrap(); + let r = Map::parse_file(&Path::new("assets/tiled_base64_zlib_infinite.tmx")).unwrap(); if let LayerData::Infinite(chunks) = &r.layers[0].tiles { assert_eq!(chunks.len(), 4); @@ -71,7 +79,7 @@ fn test_infinite_tileset() { #[test] fn test_image_layers() { - let r = read_from_file(&Path::new("assets/tiled_image_layers.tmx")).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_image_layers.tmx")).unwrap(); assert_eq!(r.image_layers.len(), 2); { let first = &r.image_layers[0]; @@ -97,7 +105,7 @@ fn test_image_layers() { #[test] fn test_tile_property() { - let r = read_from_file(&Path::new("assets/tiled_base64.tmx")).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_base64.tmx")).unwrap(); let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) = r.tilesets[0].tiles[0].properties.get("a tile property") { @@ -110,7 +118,7 @@ fn test_tile_property() { #[test] fn test_object_group_property() { - let r = read_from_file(&Path::new("assets/tiled_object_groups.tmx")).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_object_groups.tmx")).unwrap(); let prop_value: bool = if let Some(&PropertyValue::BoolValue(ref v)) = r.object_groups[0] .properties .get("an object group property") @@ -123,7 +131,7 @@ fn test_object_group_property() { } #[test] fn test_tileset_property() { - let r = read_from_file(&Path::new("assets/tiled_base64.tmx")).unwrap(); + let r = parse_map_without_source(&Path::new("assets/tiled_base64.tmx")).unwrap(); let prop_value: String = if let Some(&PropertyValue::StringValue(ref v)) = r.tilesets[0].properties.get("tileset property") { @@ -136,7 +144,7 @@ fn test_tileset_property() { #[test] fn test_flipped_gid() { - let r = read_from_file_with_path(&Path::new("assets/tiled_flipped.tmx")).unwrap(); + let r = Map::parse_file(&Path::new("assets/tiled_flipped.tmx")).unwrap(); if let LayerData::Finite(tiles) = &r.layers[0].tiles { let t1 = tiles[0][0]; @@ -165,7 +173,7 @@ fn test_flipped_gid() { #[test] fn test_ldk_export() { - let r = read_from_file_with_path(&Path::new("assets/ldk_tiled_export.tmx")).unwrap(); + let r = Map::parse_file(&Path::new("assets/ldk_tiled_export.tmx")).unwrap(); if let LayerData::Finite(tiles) = &r.layers[0].tiles { assert_eq!(tiles.len(), 8); assert_eq!(tiles[0].len(), 8);