use std::collections::HashMap;
use indexmap::IndexMap;
use zip::ZipArchive;
use crate::{
decoder::{RawSprite, RawStage, RawTarget},
error::DecodeError,
structs::{decode_scripts, Costume, List, Script, Sound, Variable},
};
#[derive(Debug, Clone)]
pub enum Target {
Sprite(Sprite),
Stage(Stage),
}
impl Target {
pub fn new(
raw: RawTarget,
zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
) -> Result<Self, DecodeError> {
Ok(match raw {
RawTarget::Sprite(raw_sprite) => Target::Sprite(Sprite::new(raw_sprite, zip)?),
RawTarget::Stage(raw_stage) => Target::Stage(Stage::new(raw_stage, zip)?),
})
}
}
#[derive(Debug, Clone)]
pub struct Sprite {
pub name: String,
pub variables: HashMap<String, Variable>,
pub lists: HashMap<String, List>,
pub scripts: Vec<Script>,
pub current_costume: usize,
costumes: IndexMap<String, Costume>,
pub sounds: HashMap<String, Sound>,
pub volume: f32,
pub layer_order: isize,
pub visible: bool,
pub position: (i32, i32),
pub size: f32,
pub direction: i32,
pub draggable: bool,
pub rotation_style: RotationStyle,
}
impl Sprite {
pub fn new(
raw: RawSprite,
zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
) -> Result<Self, DecodeError> {
let variables = raw
.variables
.into_iter()
.map(|(_, raw_var)| (raw_var.name.clone(), Variable(raw_var.value.into())))
.collect();
#[cfg(feature = "costume_png")]
let costumes = raw
.costumes
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
match raw.data_format.as_str() {
"svg" => {
let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
})?;
let svg_tree =
resvg::usvg::Tree::from_str(svg_data, &resvg::usvg::Options::default())
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
})?;
let image = crate::utils::rasterize_svg_to_png(
&svg_tree,
svg_tree.size().width() as u32,
svg_tree.size().height() as u32,
)
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to rasterize SVG: {}", e))
})?;
Ok((raw.name, Costume(image)))
}
_ => {
let image = image::load_from_memory(&buffer).map_err(|e| {
DecodeError::InvalidData(format!("Failed to load image: {}", e))
})?;
Ok((raw.name, Costume(image)))
}
}
})
.collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
#[cfg(feature = "costume_svg")]
let costumes = raw
.costumes
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
match raw.data_format.as_str() {
"svg" => {
let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
})?;
let svg_tree = usvg::Tree::from_str(svg_data, &usvg::Options::default())
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
})?;
Ok((raw.name, Costume(svg_tree)))
}
_ => Err(DecodeError::InvalidData(format!(
"Unsupported data format: {}",
raw.data_format
))),
}
})
.collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
let sounds = raw
.sounds
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
let wav_reader = hound::WavReader::new(&buffer[..]).map_err(|e| {
DecodeError::InvalidData(format!("Failed to read WAV data: {}", e))
})?;
let spec = wav_reader.spec();
let samples: Vec<i16> = wav_reader
.into_samples::<i16>()
.collect::<Result<_, _>>()
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to read WAV samples: {}", e))
})?;
Ok((
raw.name.clone(),
Sound {
data: samples,
rate: spec.sample_rate,
},
))
})
.collect::<Result<HashMap<String, Sound>, DecodeError>>()?;
let lists = raw
.lists
.into_iter()
.map(|(_, raw_list)| {
(
raw_list.0.clone(),
List(raw_list.1.iter().map(|v| v.clone().into()).collect()),
)
})
.collect();
Ok(Self {
name: raw.name,
variables,
lists,
scripts: decode_scripts(&raw.blocks)?,
current_costume: raw.current_costume,
costumes,
sounds,
volume: raw.volume as f32 / 100.0,
layer_order: raw.layer_order,
visible: raw.visible,
position: (raw.x, raw.y),
size: raw.size as f32 / 100.0,
direction: raw.direction,
draggable: raw.draggable,
rotation_style: RotationStyle::try_from(raw.rotation_style.as_str())
.map_err(|_| DecodeError::InvalidData("Invalid rotation style".to_string()))?,
})
}
pub fn get_costume(&self, name: &str) -> Option<&Costume> {
self.costumes.get(name)
}
pub fn get_costume_mut(&mut self, name: &str) -> Option<&mut Costume> {
self.costumes.get_mut(name)
}
pub fn costume_names(&self) -> Vec<&String> {
self.costumes.keys().collect()
}
pub fn costumes(&self) -> Vec<&Costume> {
self.costumes.values().collect()
}
}
#[derive(Debug, Clone)]
pub struct Stage {
pub variables: HashMap<String, Variable>,
pub lists: HashMap<String, List>,
pub broadcasts: Vec<Broadcast>,
pub scripts: Vec<Script>,
pub current_backdrop: usize,
backdrops: IndexMap<String, Costume>,
pub sounds: HashMap<String, Sound>,
pub volume: f32,
}
impl Stage {
pub fn new(
raw: RawStage,
zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
) -> Result<Self, DecodeError> {
let variables = raw
.variables
.into_iter()
.map(|(_, raw_var)| (raw_var.name.clone(), Variable(raw_var.value.into())))
.collect();
#[cfg(feature = "costume_png")]
let backdrops = raw
.backdrops
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
match raw.data_format.as_str() {
"svg" => {
let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
})?;
let svg_tree =
resvg::usvg::Tree::from_str(svg_data, &resvg::usvg::Options::default())
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
})?;
let image = crate::utils::rasterize_svg_to_png(
&svg_tree,
svg_tree.size().width() as u32,
svg_tree.size().height() as u32,
)
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to rasterize SVG: {}", e))
})?;
Ok((raw.name, Costume(image)))
}
_ => {
let image = image::load_from_memory(&buffer).map_err(|e| {
DecodeError::InvalidData(format!("Failed to load image: {}", e))
})?;
Ok((raw.name, Costume(image)))
}
}
})
.collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
#[cfg(feature = "costume_svg")]
let backdrops = raw
.backdrops
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
match raw.data_format.as_str() {
"svg" => {
let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
})?;
let svg_tree = usvg::Tree::from_str(svg_data, &usvg::Options::default())
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
})?;
Ok((raw.name, Costume(svg_tree)))
}
_ => Err(DecodeError::InvalidData(format!(
"Unsupported data format: {}",
raw.data_format
))),
}
})
.collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
let sounds = raw
.sounds
.into_iter()
.map(|raw| {
let mut file = zip.by_name(&raw.md5ext)
.map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
let mut buffer = Vec::new();
std::io::copy(&mut file, &mut buffer)?;
let wav_reader = hound::WavReader::new(&buffer[..]).map_err(|e| {
DecodeError::InvalidData(format!("Failed to read WAV data: {}", e))
})?;
let spec = wav_reader.spec();
let samples: Vec<i16> = wav_reader
.into_samples::<i16>()
.collect::<Result<_, _>>()
.map_err(|e| {
DecodeError::InvalidData(format!("Failed to read WAV samples: {}", e))
})?;
Ok((
raw.name.clone(),
Sound {
data: samples,
rate: spec.sample_rate,
},
))
})
.collect::<Result<HashMap<String, Sound>, DecodeError>>()?;
let lists = raw
.lists
.into_iter()
.map(|(_, raw_list)| {
(
raw_list.0.clone(),
List(raw_list.1.iter().map(|v| v.clone().into()).collect()),
)
})
.collect();
Ok(Self {
variables,
lists,
broadcasts: raw
.broadcasts
.into_iter()
.map(|(_, msg)| Broadcast(msg))
.collect(),
scripts: decode_scripts(&raw.blocks)?,
current_backdrop: raw.current_backdrop,
backdrops,
sounds,
volume: raw.volume as f32 / 100.0,
})
}
pub fn get_backdrop(&self, name: &str) -> Option<&Costume> {
self.backdrops.get(name)
}
pub fn get_backdrop_mut(&mut self, name: &str) -> Option<&mut Costume> {
self.backdrops.get_mut(name)
}
pub fn backdrop_names(&self) -> Vec<&String> {
self.backdrops.keys().collect()
}
pub fn backdrops(&self) -> Vec<&Costume> {
self.backdrops.values().collect()
}
}
#[derive(Debug, Clone)]
pub struct Broadcast(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RotationStyle {
AllAround,
LeftRight,
DontRotate,
}
impl TryFrom<&str> for RotationStyle {
type Error = &'static str;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"all around" => Ok(RotationStyle::AllAround),
"left-right" => Ok(RotationStyle::LeftRight),
"don't rotate" => Ok(RotationStyle::DontRotate),
_ => Err("Invalid rotation style"),
}
}
}