1use std::collections::HashMap;
6
7use indexmap::IndexMap;
8use zip::ZipArchive;
9
10use crate::{
11 decoder::{RawSprite, RawStage, RawTarget},
12 error::DecodeError,
13 structs::{decode_scripts, Costume, List, Script, Sound, Variable},
14};
15
16#[derive(Debug, Clone)]
18pub enum Target {
19 Sprite(Sprite),
21
22 Stage(Stage),
24}
25
26impl Target {
27 pub fn new(
28 raw: RawTarget,
29 zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
30 ) -> Result<Self, DecodeError> {
31 Ok(match raw {
32 RawTarget::Sprite(raw_sprite) => Target::Sprite(Sprite::new(raw_sprite, zip)?),
33 RawTarget::Stage(raw_stage) => Target::Stage(Stage::new(raw_stage, zip)?),
34 })
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct Sprite {
41 pub name: String,
43
44 pub variables: HashMap<String, Variable>,
46
47 pub lists: HashMap<String, List>,
49
50 pub scripts: Vec<Script>,
52
53 pub current_costume: usize,
55
56 costumes: IndexMap<String, Costume>,
58
59 pub sounds: HashMap<String, Sound>,
61
62 pub volume: f32,
64
65 pub layer_order: isize,
67
68 pub visible: bool,
70
71 pub position: (i32, i32),
73
74 pub size: f32,
76
77 pub direction: i32,
79
80 pub draggable: bool,
82
83 pub rotation_style: RotationStyle,
85}
86
87impl Sprite {
88 pub fn new(
90 raw: RawSprite,
91 zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
92 ) -> Result<Self, DecodeError> {
93 let variables = raw
94 .variables
95 .into_iter()
96 .map(|(_, raw_var)| (raw_var.name.clone(), Variable(raw_var.value.into())))
97 .collect();
98 #[cfg(feature = "costume_png")]
99 let costumes = raw
100 .costumes
101 .into_iter()
102 .map(|raw| {
103 let mut file = zip.by_name(&raw.md5ext)
104 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
105 let mut buffer = Vec::new();
106 std::io::copy(&mut file, &mut buffer)?;
107 match raw.data_format.as_str() {
108 "svg" => {
109 let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
110 DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
111 })?;
112 let svg_tree =
113 resvg::usvg::Tree::from_str(svg_data, &resvg::usvg::Options::default())
114 .map_err(|e| {
115 DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
116 })?;
117 let image = crate::utils::rasterize_svg_to_png(
118 &svg_tree,
119 svg_tree.size().width() as u32,
120 svg_tree.size().height() as u32,
121 )
122 .map_err(|e| {
123 DecodeError::InvalidData(format!("Failed to rasterize SVG: {}", e))
124 })?;
125 Ok((raw.name, Costume(image)))
126 }
127 _ => {
128 let image = image::load_from_memory(&buffer).map_err(|e| {
129 DecodeError::InvalidData(format!("Failed to load image: {}", e))
130 })?;
131 Ok((raw.name, Costume(image)))
132 }
133 }
134 })
135 .collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
136 #[cfg(feature = "costume_svg")]
137 let costumes = raw
138 .costumes
139 .into_iter()
140 .map(|raw| {
141 let mut file = zip.by_name(&raw.md5ext)
142 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
143 let mut buffer = Vec::new();
144 std::io::copy(&mut file, &mut buffer)?;
145 match raw.data_format.as_str() {
146 "svg" => {
147 let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
148 DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
149 })?;
150 let svg_tree = usvg::Tree::from_str(svg_data, &usvg::Options::default())
151 .map_err(|e| {
152 DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
153 })?;
154 Ok((raw.name, Costume(svg_tree)))
155 }
156 _ => Err(DecodeError::InvalidData(format!(
157 "Unsupported data format: {}",
158 raw.data_format
159 ))),
160 }
161 })
162 .collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
163 let sounds = raw
164 .sounds
165 .into_iter()
166 .map(|raw| {
167 let mut file = zip.by_name(&raw.md5ext)
168 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
169 let mut buffer = Vec::new();
170 std::io::copy(&mut file, &mut buffer)?;
171 let wav_reader = hound::WavReader::new(&buffer[..]).map_err(|e| {
172 DecodeError::InvalidData(format!("Failed to read WAV data: {}", e))
173 })?;
174 let spec = wav_reader.spec();
175 let samples: Vec<i16> = wav_reader
176 .into_samples::<i16>()
177 .collect::<Result<_, _>>()
178 .map_err(|e| {
179 DecodeError::InvalidData(format!("Failed to read WAV samples: {}", e))
180 })?;
181 Ok((
182 raw.name.clone(),
183 Sound {
184 data: samples,
185 rate: spec.sample_rate,
186 },
187 ))
188 })
189 .collect::<Result<HashMap<String, Sound>, DecodeError>>()?;
190 let lists = raw
191 .lists
192 .into_iter()
193 .map(|(_, raw_list)| {
194 (
195 raw_list.0.clone(),
196 List(raw_list.1.iter().map(|v| v.clone().into()).collect()),
197 )
198 })
199 .collect();
200
201 Ok(Self {
202 name: raw.name,
203 variables,
204 lists,
205 scripts: decode_scripts(&raw.blocks)?,
206 current_costume: raw.current_costume,
207 costumes,
208 sounds,
209 volume: raw.volume as f32 / 100.0,
210 layer_order: raw.layer_order,
211 visible: raw.visible,
212 position: (raw.x, raw.y),
213 size: raw.size as f32 / 100.0,
214 direction: raw.direction,
215 draggable: raw.draggable,
216 rotation_style: RotationStyle::try_from(raw.rotation_style.as_str())
217 .map_err(|_| DecodeError::InvalidData("Invalid rotation style".to_string()))?,
218 })
219 }
220
221 pub fn get_costume(&self, name: &str) -> Option<&Costume> {
223 self.costumes.get(name)
224 }
225
226 pub fn get_costume_mut(&mut self, name: &str) -> Option<&mut Costume> {
228 self.costumes.get_mut(name)
229 }
230
231 pub fn costume_names(&self) -> Vec<&String> {
233 self.costumes.keys().collect()
234 }
235
236 pub fn costumes(&self) -> Vec<&Costume> {
238 self.costumes.values().collect()
239 }
240}
241
242#[derive(Debug, Clone)]
244pub struct Stage {
245 pub variables: HashMap<String, Variable>,
247
248 pub lists: HashMap<String, List>,
250
251 pub broadcasts: Vec<Broadcast>,
253
254 pub scripts: Vec<Script>,
256
257 pub current_backdrop: usize,
259
260 backdrops: IndexMap<String, Costume>,
262
263 pub sounds: HashMap<String, Sound>,
265
266 pub volume: f32,
268}
269
270impl Stage {
271 pub fn new(
273 raw: RawStage,
274 zip: &mut ZipArchive<std::io::Cursor<Vec<u8>>>,
275 ) -> Result<Self, DecodeError> {
276 let variables = raw
277 .variables
278 .into_iter()
279 .map(|(_, raw_var)| (raw_var.name.clone(), Variable(raw_var.value.into())))
280 .collect();
281 #[cfg(feature = "costume_png")]
282 let backdrops = raw
283 .backdrops
284 .into_iter()
285 .map(|raw| {
286 let mut file = zip.by_name(&raw.md5ext)
287 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
288 let mut buffer = Vec::new();
289 std::io::copy(&mut file, &mut buffer)?;
290 match raw.data_format.as_str() {
291 "svg" => {
292 let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
293 DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
294 })?;
295 let svg_tree =
296 resvg::usvg::Tree::from_str(svg_data, &resvg::usvg::Options::default())
297 .map_err(|e| {
298 DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
299 })?;
300 let image = crate::utils::rasterize_svg_to_png(
301 &svg_tree,
302 svg_tree.size().width() as u32,
303 svg_tree.size().height() as u32,
304 )
305 .map_err(|e| {
306 DecodeError::InvalidData(format!("Failed to rasterize SVG: {}", e))
307 })?;
308 Ok((raw.name, Costume(image)))
309 }
310 _ => {
311 let image = image::load_from_memory(&buffer).map_err(|e| {
312 DecodeError::InvalidData(format!("Failed to load image: {}", e))
313 })?;
314 Ok((raw.name, Costume(image)))
315 }
316 }
317 })
318 .collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
319 #[cfg(feature = "costume_svg")]
320 let backdrops = raw
321 .backdrops
322 .into_iter()
323 .map(|raw| {
324 let mut file = zip.by_name(&raw.md5ext)
325 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
326 let mut buffer = Vec::new();
327 std::io::copy(&mut file, &mut buffer)?;
328 match raw.data_format.as_str() {
329 "svg" => {
330 let svg_data = std::str::from_utf8(&buffer).map_err(|_| {
331 DecodeError::InvalidData("SVG data is not valid UTF-8".to_string())
332 })?;
333 let svg_tree = usvg::Tree::from_str(svg_data, &usvg::Options::default())
334 .map_err(|e| {
335 DecodeError::InvalidData(format!("Failed to parse SVG: {}", e))
336 })?;
337 Ok((raw.name, Costume(svg_tree)))
338 }
339 _ => Err(DecodeError::InvalidData(format!(
340 "Unsupported data format: {}",
341 raw.data_format
342 ))),
343 }
344 })
345 .collect::<Result<IndexMap<String, Costume>, DecodeError>>()?;
346 let sounds = raw
347 .sounds
348 .into_iter()
349 .map(|raw| {
350 let mut file = zip.by_name(&raw.md5ext)
351 .map_err(|_| DecodeError::NotFound(raw.md5ext.clone()))?;
352 let mut buffer = Vec::new();
353 std::io::copy(&mut file, &mut buffer)?;
354 let wav_reader = hound::WavReader::new(&buffer[..]).map_err(|e| {
355 DecodeError::InvalidData(format!("Failed to read WAV data: {}", e))
356 })?;
357 let spec = wav_reader.spec();
358 let samples: Vec<i16> = wav_reader
359 .into_samples::<i16>()
360 .collect::<Result<_, _>>()
361 .map_err(|e| {
362 DecodeError::InvalidData(format!("Failed to read WAV samples: {}", e))
363 })?;
364 Ok((
365 raw.name.clone(),
366 Sound {
367 data: samples,
368 rate: spec.sample_rate,
369 },
370 ))
371 })
372 .collect::<Result<HashMap<String, Sound>, DecodeError>>()?;
373 let lists = raw
374 .lists
375 .into_iter()
376 .map(|(_, raw_list)| {
377 (
378 raw_list.0.clone(),
379 List(raw_list.1.iter().map(|v| v.clone().into()).collect()),
380 )
381 })
382 .collect();
383
384 Ok(Self {
385 variables,
386 lists,
387 broadcasts: raw
388 .broadcasts
389 .into_iter()
390 .map(|(_, msg)| Broadcast(msg))
391 .collect(),
392 scripts: decode_scripts(&raw.blocks)?,
393 current_backdrop: raw.current_backdrop,
394 backdrops,
395 sounds,
396 volume: raw.volume as f32 / 100.0,
397 })
398 }
399
400 pub fn get_backdrop(&self, name: &str) -> Option<&Costume> {
402 self.backdrops.get(name)
403 }
404
405 pub fn get_backdrop_mut(&mut self, name: &str) -> Option<&mut Costume> {
407 self.backdrops.get_mut(name)
408 }
409
410 pub fn backdrop_names(&self) -> Vec<&String> {
412 self.backdrops.keys().collect()
413 }
414
415 pub fn backdrops(&self) -> Vec<&Costume> {
417 self.backdrops.values().collect()
418 }
419}
420
421#[derive(Debug, Clone)]
423pub struct Broadcast(pub String);
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
427pub enum RotationStyle {
428 AllAround,
430
431 LeftRight,
433
434 DontRotate,
436}
437
438impl TryFrom<&str> for RotationStyle {
439 type Error = &'static str;
440
441 fn try_from(value: &str) -> Result<Self, Self::Error> {
442 match value {
443 "all around" => Ok(RotationStyle::AllAround),
444 "left-right" => Ok(RotationStyle::LeftRight),
445 "don't rotate" => Ok(RotationStyle::DontRotate),
446 _ => Err("Invalid rotation style"),
447 }
448 }
449}