Newer
Older
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
use xml::EventReader;
use crate::error::TiledError;
use crate::image::Image;
use crate::util::*;
/// A tileset, usually the tilesheet image.
#[derive(Debug, PartialEq, Clone)]
pub struct Tileset {
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>,
pub columns: u32,
/// A tileset can either:
/// * have a single spritesheet `image` in `tileset` ("regular" tileset);
/// * have zero images in `tileset` and one `image` per `tile` ("image collection" tileset).
///
/// - Source: [tiled issue #2117](https://github.com/mapeditor/tiled/issues/2117)
/// - Source: [`columns` documentation](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#tileset)
pub image: Option<Image>,
/// Where this tileset was loaded from.
/// If fully embedded (loaded with path = `None`), this will return `None`.
pub source: Option<PathBuf>,
/// 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, 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_with_path<R: Read>(
reader: R,
first_gid: u32,
path: impl AsRef<Path>,
) -> Result<Self, TiledError> {
Tileset::new_external(reader, first_gid, Some(path.as_ref()))
parser: &mut EventReader<R>,
attrs: Vec<OwnedAttribute>,
Tileset::parse_xml_embedded(parser, &attrs, path_relative_to).or_else(|err| {
if matches!(err, TiledError::MalformedAttributes(_)) {
Tileset::parse_xml_reference(&attrs, path_relative_to)
} else {
Err(err)
}
})
parser: &mut EventReader<R>,
attrs: &Vec<OwnedAttribute>,
let ((spacing, margin, tilecount, columns), (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()),
("columns", columns, |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 image = Option::None;
let mut tiles = Vec::new();
let mut properties = HashMap::new();
parse_tag!(parser, "tileset", {
"image" => |attrs| {
image = Some(Image::new(parser, attrs, path_relative_to.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?);
Ok(())
},
"properties" => |_| {
properties = parse_properties(parser)?;
Ok(())
},
"tile" => |attrs| {
let columns = match columns {
Some(col) => col,
None => match &image {
None => {
return Err(TiledError::MalformedAttributes(
"No <image> nor columns attribute in <tileset>".to_string(),
Some(image) => image.width as u32 / width,
},
};
Ok(Tileset {
tile_width: width,
tile_height: height,
spacing: spacing.unwrap_or(0),
margin: margin.unwrap_or(0),
first_gid,
name,
tilecount,
columns,
image,
) -> 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 reference must have a firstgid and source with correct types".to_string())
let tileset_path = path_relative_to
.ok_or(TiledError::SourceRequired {
object_to_parse: "Tileset".to_string(),
})?
.join(source);
let file = File::open(&tileset_path).map_err(|_| {
TiledError::Other(format!(
"External tileset file not found: {:?}",
tileset_path
))
})?;
pub(crate) fn new_external<R: Read>(
file: R,
first_gid: u32,
path: Option<&Path>,
) -> Result<Self, 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" {
);
}
}
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>,
let ((spacing, margin, tilecount, columns), (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()),
("columns", columns, |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 image = Option::None;
let mut tiles = Vec::new();
let mut properties = HashMap::new();
parse_tag!(parser, "tileset", {
"image" => |attrs| {
image = Some(Image::new(parser, attrs, source_path.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?);
Ok(())
},
"properties" => |_| {
properties = parse_properties(parser)?;
Ok(())
},
"tile" => |attrs| {
Ok(())
},
let columns = match columns {
Some(col) => col,
None => match &image {
None => {
return Err(TiledError::MalformedAttributes(
"No <image> and no <columns> in <tileset>".to_string(),
))
}
Some(image) => image.width as u32 / width,
},
};
Ok(Tileset {
first_gid: first_gid,
name: name,
tile_width: width,
tile_height: height,
spacing: spacing.unwrap_or(0),
margin: margin.unwrap_or(0),
columns,
tilecount,
image,