tiled 0.16.0

A rust crate for loading maps created by the Tiled editor
Documentation
use std::{convert::TryInto, io::Read};

use base64::Engine;

use crate::{
    CsvDecodingError, Error, LayerTileData, MapTilesetGid, Result, util::read_text_or_cdata,
};

pub(crate) fn parse_data_line<R: std::io::BufRead>(
    encoding: Option<String>,
    compression: Option<String>,
    elem: crate::util::XmlElement<'_, R>,
    tilesets: &[MapTilesetGid],
) -> Result<Vec<Option<LayerTileData>>> {
    let encoding_ref = encoding.as_deref();
    let compression_ref = compression.as_deref();
    read_text_or_cdata(elem, |text| match (encoding_ref, compression_ref) {
        (Some("csv"), None) => decode_csv(text, tilesets),

        (Some("base64"), None) => parse_base64(text).map(|v| convert_to_tiles(&v, tilesets)),
        (Some("base64"), Some("zlib")) => parse_base64(text)
            .and_then(|data| process_decoder(Ok(flate2::bufread::ZlibDecoder::new(&data[..]))))
            .map(|v| convert_to_tiles(&v, tilesets)),
        (Some("base64"), Some("gzip")) => parse_base64(text)
            .and_then(|data| process_decoder(Ok(flate2::bufread::GzDecoder::new(&data[..]))))
            .map(|v| convert_to_tiles(&v, tilesets)),
        #[cfg(feature = "zstd")]
        (Some("base64"), Some("zstd")) => parse_base64(text)
            .and_then(|data| process_decoder(zstd::stream::read::Decoder::with_buffer(&data[..])))
            .map(|v| convert_to_tiles(&v, tilesets)),

        _ => Err(Error::InvalidEncodingFormat {
            encoding: encoding.clone(),
            compression: compression.clone(),
        }),
    })
}

fn parse_base64(text: &str) -> Result<Vec<u8>> {
    base64::engine::GeneralPurpose::new(
        &base64::alphabet::STANDARD,
        base64::engine::general_purpose::PAD,
    )
    .decode(text.trim().as_bytes())
    .map_err(Error::Base64DecodingError)
}

fn process_decoder(decoder: std::io::Result<impl Read>) -> Result<Vec<u8>> {
    decoder
        .and_then(|mut decoder| {
            let mut data = Vec::new();
            decoder.read_to_end(&mut data)?;
            Ok(data)
        })
        .map_err(Error::DecompressingError)
}

fn decode_csv(text: &str, tilesets: &[MapTilesetGid]) -> Result<Vec<Option<LayerTileData>>> {
    let mut tiles = Vec::new();
    for v in text.split(',') {
        match v.trim().parse() {
            Ok(bits) => tiles.push(LayerTileData::from_bits(bits, tilesets)),
            Err(e) => {
                return Err(Error::CsvDecodingError(
                    CsvDecodingError::TileDataParseError(e),
                ));
            }
        }
    }
    Ok(tiles)
}

fn convert_to_tiles(data: &[u8], tilesets: &[MapTilesetGid]) -> Vec<Option<LayerTileData>> {
    data.chunks_exact(4)
        .map(|chunk| {
            let bits = u32::from_le_bytes(chunk.try_into().unwrap());
            LayerTileData::from_bits(bits, tilesets)
        })
        .collect()
}