iced_tiny_skia/
vector.rs

1use crate::core::svg::{Data, Handle};
2use crate::core::{Color, Rectangle, Size};
3
4use resvg::usvg;
5use rustc_hash::{FxHashMap, FxHashSet};
6use tiny_skia::Transform;
7
8use std::cell::RefCell;
9use std::collections::hash_map;
10use std::fs;
11use std::sync::Arc;
12
13#[derive(Debug)]
14pub struct Pipeline {
15    cache: RefCell<Cache>,
16}
17
18impl Pipeline {
19    pub fn new() -> Self {
20        Self {
21            cache: RefCell::new(Cache::default()),
22        }
23    }
24
25    pub fn viewport_dimensions(&self, handle: &Handle) -> Size<u32> {
26        self.cache
27            .borrow_mut()
28            .viewport_dimensions(handle)
29            .unwrap_or(Size::new(0, 0))
30    }
31
32    pub fn draw(
33        &mut self,
34        handle: &Handle,
35        color: Option<Color>,
36        bounds: Rectangle,
37        opacity: f32,
38        pixels: &mut tiny_skia::PixmapMut<'_>,
39        transform: Transform,
40        clip_mask: Option<&tiny_skia::Mask>,
41    ) {
42        if let Some(image) = self.cache.borrow_mut().draw(
43            handle,
44            color,
45            Size::new(bounds.width as u32, bounds.height as u32),
46        ) {
47            pixels.draw_pixmap(
48                bounds.x as i32,
49                bounds.y as i32,
50                image,
51                &tiny_skia::PixmapPaint {
52                    opacity,
53                    quality: tiny_skia::FilterQuality::Bicubic,
54                    ..tiny_skia::PixmapPaint::default()
55                },
56                transform,
57                clip_mask,
58            );
59        }
60    }
61
62    pub fn trim_cache(&mut self) {
63        self.cache.borrow_mut().trim();
64    }
65}
66
67#[derive(Default)]
68struct Cache {
69    trees: FxHashMap<u64, Option<resvg::usvg::Tree>>,
70    tree_hits: FxHashSet<u64>,
71    rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
72    raster_hits: FxHashSet<RasterKey>,
73    fontdb: Option<Arc<usvg::fontdb::Database>>,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77struct RasterKey {
78    id: u64,
79    color: Option<[u8; 4]>,
80    size: Size<u32>,
81}
82
83impl Cache {
84    fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
85        let id = handle.id();
86
87        // TODO: Reuse `cosmic-text` font database
88        if self.fontdb.is_none() {
89            let mut fontdb = usvg::fontdb::Database::new();
90            fontdb.load_system_fonts();
91
92            self.fontdb = Some(Arc::new(fontdb));
93        }
94
95        let options = usvg::Options {
96            fontdb: self
97                .fontdb
98                .as_ref()
99                .expect("fontdb must be initialized")
100                .clone(),
101            ..usvg::Options::default()
102        };
103
104        if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
105            let svg = match handle.data() {
106                Data::Path(path) => {
107                    fs::read_to_string(path).ok().and_then(|contents| {
108                        usvg::Tree::from_str(&contents, &options).ok()
109                    })
110                }
111                Data::Bytes(bytes) => {
112                    usvg::Tree::from_data(bytes, &options).ok()
113                }
114            };
115
116            let _ = entry.insert(svg);
117        }
118
119        let _ = self.tree_hits.insert(id);
120        self.trees.get(&id).unwrap().as_ref()
121    }
122
123    fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
124        let tree = self.load(handle)?;
125        let size = tree.size();
126
127        Some(Size::new(size.width() as u32, size.height() as u32))
128    }
129
130    fn draw(
131        &mut self,
132        handle: &Handle,
133        color: Option<Color>,
134        size: Size<u32>,
135    ) -> Option<tiny_skia::PixmapRef<'_>> {
136        if size.width == 0 || size.height == 0 {
137            return None;
138        }
139
140        let key = RasterKey {
141            id: handle.id(),
142            color: color.map(Color::into_rgba8),
143            size,
144        };
145
146        #[allow(clippy::map_entry)]
147        if !self.rasters.contains_key(&key) {
148            let tree = self.load(handle)?;
149
150            let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;
151
152            let tree_size = tree.size().to_int_size();
153
154            let target_size = if size.width > size.height {
155                tree_size.scale_to_width(size.width)
156            } else {
157                tree_size.scale_to_height(size.height)
158            };
159
160            let transform = if let Some(target_size) = target_size {
161                let tree_size = tree_size.to_size();
162                let target_size = target_size.to_size();
163
164                tiny_skia::Transform::from_scale(
165                    target_size.width() / tree_size.width(),
166                    target_size.height() / tree_size.height(),
167                )
168            } else {
169                tiny_skia::Transform::default()
170            };
171
172            resvg::render(tree, transform, &mut image.as_mut());
173
174            if let Some([r, g, b, _]) = key.color {
175                // Apply color filter
176                for pixel in
177                    bytemuck::cast_slice_mut::<u8, u32>(image.data_mut())
178                {
179                    *pixel = bytemuck::cast(
180                        tiny_skia::ColorU8::from_rgba(
181                            b,
182                            g,
183                            r,
184                            (*pixel >> 24) as u8,
185                        )
186                        .premultiply(),
187                    );
188                }
189            } else {
190                // Swap R and B channels for `softbuffer` presentation
191                for pixel in
192                    bytemuck::cast_slice_mut::<u8, u32>(image.data_mut())
193                {
194                    *pixel = *pixel & 0xFF00_FF00
195                        | ((0x0000_00FF & *pixel) << 16)
196                        | ((0x00FF_0000 & *pixel) >> 16);
197                }
198            }
199
200            let _ = self.rasters.insert(key, image);
201        }
202
203        let _ = self.raster_hits.insert(key);
204        self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref)
205    }
206
207    fn trim(&mut self) {
208        self.trees.retain(|key, _| self.tree_hits.contains(key));
209        self.rasters.retain(|key, _| self.raster_hits.contains(key));
210
211        self.tree_hits.clear();
212        self.raster_hits.clear();
213    }
214}
215
216impl std::fmt::Debug for Cache {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        f.debug_struct("Cache")
219            .field("tree_hits", &self.tree_hits)
220            .field("rasters", &self.rasters)
221            .field("raster_hits", &self.raster_hits)
222            .finish_non_exhaustive()
223    }
224}