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 ..tiny_skia::PixmapPaint::default()
54 },
55 transform,
56 clip_mask,
57 );
58 }
59 }
60
61 pub fn trim_cache(&mut self) {
62 self.cache.borrow_mut().trim();
63 }
64}
65
66#[derive(Default)]
67struct Cache {
68 trees: FxHashMap<u64, Option<resvg::usvg::Tree>>,
69 tree_hits: FxHashSet<u64>,
70 rasters: FxHashMap<RasterKey, tiny_skia::Pixmap>,
71 raster_hits: FxHashSet<RasterKey>,
72 fontdb: Option<Arc<usvg::fontdb::Database>>,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
76struct RasterKey {
77 id: u64,
78 color: Option<[u8; 4]>,
79 size: Size<u32>,
80}
81
82impl Cache {
83 fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
84 let id = handle.id();
85
86 if self.fontdb.is_none() {
88 let mut fontdb = usvg::fontdb::Database::new();
89 fontdb.load_system_fonts();
90
91 self.fontdb = Some(Arc::new(fontdb));
92 }
93
94 let options = usvg::Options {
95 fontdb: self
96 .fontdb
97 .as_ref()
98 .expect("fontdb must be initialized")
99 .clone(),
100 ..usvg::Options::default()
101 };
102
103 if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
104 let svg = match handle.data() {
105 Data::Path(path) => {
106 fs::read_to_string(path).ok().and_then(|contents| {
107 usvg::Tree::from_str(&contents, &options).ok()
108 })
109 }
110 Data::Bytes(bytes) => {
111 usvg::Tree::from_data(bytes, &options).ok()
112 }
113 };
114
115 let _ = entry.insert(svg);
116 }
117
118 let _ = self.tree_hits.insert(id);
119 self.trees.get(&id).unwrap().as_ref()
120 }
121
122 fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
123 let tree = self.load(handle)?;
124 let size = tree.size();
125
126 Some(Size::new(size.width() as u32, size.height() as u32))
127 }
128
129 fn draw(
130 &mut self,
131 handle: &Handle,
132 color: Option<Color>,
133 size: Size<u32>,
134 ) -> Option<tiny_skia::PixmapRef<'_>> {
135 if size.width == 0 || size.height == 0 {
136 return None;
137 }
138
139 let key = RasterKey {
140 id: handle.id(),
141 color: color.map(Color::into_rgba8),
142 size,
143 };
144
145 #[allow(clippy::map_entry)]
146 if !self.rasters.contains_key(&key) {
147 let tree = self.load(handle)?;
148
149 let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;
150
151 let tree_size = tree.size().to_int_size();
152
153 let target_size = if size.width > size.height {
154 tree_size.scale_to_width(size.width)
155 } else {
156 tree_size.scale_to_height(size.height)
157 };
158
159 let transform = if let Some(target_size) = target_size {
160 let tree_size = tree_size.to_size();
161 let target_size = target_size.to_size();
162
163 tiny_skia::Transform::from_scale(
164 target_size.width() / tree_size.width(),
165 target_size.height() / tree_size.height(),
166 )
167 } else {
168 tiny_skia::Transform::default()
169 };
170
171 resvg::render(tree, transform, &mut image.as_mut());
172
173 if let Some([r, g, b, _]) = key.color {
174 for pixel in
176 bytemuck::cast_slice_mut::<u8, u32>(image.data_mut())
177 {
178 *pixel = bytemuck::cast(
179 tiny_skia::ColorU8::from_rgba(
180 b,
181 g,
182 r,
183 (*pixel >> 24) as u8,
184 )
185 .premultiply(),
186 );
187 }
188 } else {
189 for pixel in
191 bytemuck::cast_slice_mut::<u8, u32>(image.data_mut())
192 {
193 *pixel = *pixel & 0xFF00_FF00
194 | ((0x0000_00FF & *pixel) << 16)
195 | ((0x00FF_0000 & *pixel) >> 16);
196 }
197 }
198
199 let _ = self.rasters.insert(key, image);
200 }
201
202 let _ = self.raster_hits.insert(key);
203 self.rasters.get(&key).map(tiny_skia::Pixmap::as_ref)
204 }
205
206 fn trim(&mut self) {
207 self.trees.retain(|key, _| self.tree_hits.contains(key));
208 self.rasters.retain(|key, _| self.raster_hits.contains(key));
209
210 self.tree_hits.clear();
211 self.raster_hits.clear();
212 }
213}
214
215impl std::fmt::Debug for Cache {
216 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217 f.debug_struct("Cache")
218 .field("tree_hits", &self.tree_hits)
219 .field("rasters", &self.rasters)
220 .field("raster_hits", &self.raster_hits)
221 .finish_non_exhaustive()
222 }
223}