1use bevy::asset::{Asset, AssetLoader, LoadContext, io::Reader};
10use bevy::math::Vec2;
11use bevy::prelude::BevyError;
12use bevy::reflect::TypePath;
13
14#[derive(Asset, TypePath, Debug)]
17pub struct SvgDocument {
18 pub tree: usvg::Tree,
20 pub size: Vec2,
22}
23
24#[derive(Debug)]
28pub struct SvgParseError(pub usvg::Error);
29
30impl std::fmt::Display for SvgParseError {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "invalid SVG: {}", self.0)
33 }
34}
35
36impl std::error::Error for SvgParseError {
37 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
38 Some(&self.0)
39 }
40}
41
42pub fn parse_svg_bytes(bytes: &[u8]) -> Result<SvgDocument, SvgParseError> {
45 let mut opts = usvg::Options::default();
46 super::text::configure_text_options(&mut opts);
47 let tree = usvg::Tree::from_data(bytes, &opts).map_err(SvgParseError)?;
48 let size = tree.size();
49 Ok(SvgDocument {
50 size: Vec2::new(size.width(), size.height()),
51 tree,
52 })
53}
54
55#[derive(TypePath)]
57pub struct SvgAssetLoader;
58
59impl AssetLoader for SvgAssetLoader {
60 type Asset = SvgDocument;
61 type Settings = ();
62 type Error = BevyError;
63
64 async fn load(
65 &self,
66 reader: &mut dyn Reader,
67 _settings: &Self::Settings,
68 _load_context: &mut LoadContext<'_>,
69 ) -> Result<Self::Asset, Self::Error> {
70 let mut bytes = Vec::new();
71 reader.read_to_end(&mut bytes).await?;
72 Ok(parse_svg_bytes(&bytes)?)
73 }
74
75 fn extensions(&self) -> &[&str] {
76 &["svg"]
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::svg::CIRCLE_SVG;
84
85 #[test]
86 fn parses_valid_svg_and_rejects_garbage() {
87 let doc = parse_svg_bytes(CIRCLE_SVG.as_bytes()).expect("valid SVG parses");
88 assert_eq!(
89 doc.size,
90 Vec2::new(100.0, 100.0),
91 "intrinsic size must come from the viewBox"
92 );
93
94 parse_svg_bytes(b"not svg").expect_err("garbage bytes must be rejected");
95 parse_svg_bytes(b"").expect_err("empty input must be rejected");
96 }
97
98 #[test]
101 fn loader_claims_svg_extension() {
102 assert_eq!(SvgAssetLoader.extensions(), ["svg"]);
103 }
104}