| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use super::{ImageCacheKey, SharedImageBuffer, SharedPixelBuffer}; |
| 5 | use crate::lengths::PhysicalPx; |
| 6 | #[cfg (not(target_arch = "wasm32" ))] |
| 7 | use crate::SharedString; |
| 8 | use resvg::{tiny_skia, usvg}; |
| 9 | |
| 10 | pub struct ParsedSVG { |
| 11 | svg_tree: usvg::Tree, |
| 12 | cache_key: ImageCacheKey, |
| 13 | } |
| 14 | |
| 15 | impl super::OpaqueImage for ParsedSVG { |
| 16 | fn size(&self) -> crate::graphics::IntSize { |
| 17 | self.size() |
| 18 | } |
| 19 | fn cache_key(&self) -> ImageCacheKey { |
| 20 | self.cache_key.clone() |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | impl core::fmt::Debug for ParsedSVG { |
| 25 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 26 | f.debug_tuple(name:"ParsedSVG" ).finish() |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | impl ParsedSVG { |
| 31 | pub fn size(&self) -> crate::graphics::IntSize { |
| 32 | let size = self.svg_tree.size().to_int_size(); |
| 33 | [size.width(), size.height()].into() |
| 34 | } |
| 35 | |
| 36 | pub fn cache_key(&self) -> ImageCacheKey { |
| 37 | self.cache_key.clone() |
| 38 | } |
| 39 | |
| 40 | /// Renders the SVG with the specified size, if no size is specified, get the size from the image |
| 41 | #[allow (clippy::unnecessary_cast)] // Coord |
| 42 | pub fn render( |
| 43 | &self, |
| 44 | size: Option<euclid::Size2D<u32, PhysicalPx>>, |
| 45 | ) -> Result<SharedImageBuffer, usvg::Error> { |
| 46 | let tree = &self.svg_tree; |
| 47 | |
| 48 | let (target_size, transform) = match size { |
| 49 | Some(size) => { |
| 50 | let target_size = tiny_skia::IntSize::from_wh(size.width, size.height) |
| 51 | .ok_or(usvg::Error::InvalidSize)?; |
| 52 | let target_size = tree.size().to_int_size().scale_to(target_size); |
| 53 | let target_size_f = target_size.to_size(); |
| 54 | |
| 55 | let transform = tiny_skia::Transform::from_scale( |
| 56 | target_size_f.width() as f32 / tree.size().width() as f32, |
| 57 | target_size_f.height() as f32 / tree.size().height() as f32, |
| 58 | ); |
| 59 | (target_size, transform) |
| 60 | } |
| 61 | None => (tree.size().to_int_size(), tiny_skia::Transform::default()), |
| 62 | }; |
| 63 | |
| 64 | let mut buffer = SharedPixelBuffer::new(target_size.width(), target_size.height()); |
| 65 | let mut skia_buffer = tiny_skia::PixmapMut::from_bytes( |
| 66 | buffer.make_mut_bytes(), |
| 67 | target_size.width(), |
| 68 | target_size.height(), |
| 69 | ) |
| 70 | .ok_or(usvg::Error::InvalidSize)?; |
| 71 | |
| 72 | resvg::render(tree, transform, &mut skia_buffer); |
| 73 | Ok(SharedImageBuffer::RGBA8Premultiplied(buffer)) |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | #[cfg (not(target_arch = "wasm32" ))] |
| 78 | pub fn load_from_path( |
| 79 | path: &SharedString, |
| 80 | cache_key: ImageCacheKey, |
| 81 | ) -> Result<ParsedSVG, std::io::Error> { |
| 82 | let svg_data: Vec = std::fs::read(std::path::Path::new(&path.as_str()))?; |
| 83 | |
| 84 | i_slint_common::sharedfontdb::FONT_DB.with_borrow(|db: &FontDatabase| { |
| 85 | let option: Options<'_> = usvg::Options { fontdb: (*db).clone(), ..Default::default() }; |
| 86 | usvg::Tree::from_data(&svg_data, &option) |
| 87 | .map(|svg| ParsedSVG { svg_tree: svg, cache_key }) |
| 88 | .map_err(|e: Error| std::io::Error::new(kind:std::io::ErrorKind::Other, error:e)) |
| 89 | }) |
| 90 | } |
| 91 | |
| 92 | pub fn load_from_data(slice: &[u8], cache_key: ImageCacheKey) -> Result<ParsedSVG, usvg::Error> { |
| 93 | i_slint_common::sharedfontdb::FONT_DB.with_borrow(|db: &FontDatabase| { |
| 94 | let option: Options<'_> = usvg::Options { fontdb: (*db).clone(), ..Default::default() }; |
| 95 | usvg::Tree::from_data(slice, &option).map(|svg: Tree| ParsedSVG { svg_tree: svg, cache_key }) |
| 96 | }) |
| 97 | } |
| 98 | |