iced_wgpu/image/
vector.rs1use crate::core::svg;
2use crate::core::{Color, Size};
3use crate::image::atlas::{self, Atlas};
4
5use resvg::tiny_skia;
6use resvg::usvg;
7use rustc_hash::{FxHashMap, FxHashSet};
8use std::fs;
9use std::panic;
10use std::sync::Arc;
11
12pub enum Svg {
14 Loaded(usvg::Tree),
16 NotFound,
18}
19
20impl Svg {
21 pub fn viewport_dimensions(&self) -> Size<u32> {
23 match self {
24 Svg::Loaded(tree) => {
25 let size = tree.size();
26
27 Size::new(size.width() as u32, size.height() as u32)
28 }
29 Svg::NotFound => Size::new(1, 1),
30 }
31 }
32}
33
34#[derive(Debug, Default)]
36pub struct Cache {
37 svgs: FxHashMap<u64, Svg>,
38 rasterized: FxHashMap<(u64, u32, u32, ColorFilter), atlas::Entry>,
39 svg_hits: FxHashSet<u64>,
40 rasterized_hits: FxHashSet<(u64, u32, u32, ColorFilter)>,
41 should_trim: bool,
42 fontdb: Option<Arc<usvg::fontdb::Database>>,
43}
44
45type ColorFilter = Option<[u8; 4]>;
46
47impl Cache {
48 pub fn load(&mut self, handle: &svg::Handle) -> &Svg {
50 if self.svgs.contains_key(&handle.id()) {
51 return self.svgs.get(&handle.id()).unwrap();
52 }
53
54 if self.fontdb.is_none() {
56 let mut fontdb = usvg::fontdb::Database::new();
57 fontdb.load_system_fonts();
58
59 self.fontdb = Some(Arc::new(fontdb));
60 }
61
62 let options = usvg::Options {
63 fontdb: self
64 .fontdb
65 .as_ref()
66 .expect("fontdb must be initialized")
67 .clone(),
68 ..usvg::Options::default()
69 };
70
71 let svg = match handle.data() {
72 svg::Data::Path(path) => fs::read_to_string(path)
73 .ok()
74 .and_then(|contents| usvg::Tree::from_str(&contents, &options).ok())
75 .map(Svg::Loaded)
76 .unwrap_or(Svg::NotFound),
77 svg::Data::Bytes(bytes) => match usvg::Tree::from_data(bytes, &options) {
78 Ok(tree) => Svg::Loaded(tree),
79 Err(_) => Svg::NotFound,
80 },
81 };
82
83 self.should_trim = true;
84
85 let _ = self.svgs.insert(handle.id(), svg);
86 self.svgs.get(&handle.id()).unwrap()
87 }
88
89 pub fn upload(
91 &mut self,
92 device: &wgpu::Device,
93 encoder: &mut wgpu::CommandEncoder,
94 belt: &mut wgpu::util::StagingBelt,
95 handle: &svg::Handle,
96 color: Option<Color>,
97 size: Size,
98 scale: f32,
99 atlas: &mut Atlas,
100 ) -> Option<&atlas::Entry> {
101 let id = handle.id();
102
103 let (width, height) = (
104 (scale * size.width).ceil() as u32,
105 (scale * size.height).ceil() as u32,
106 );
107
108 let color = color.map(Color::into_rgba8);
109 let key = (id, width, height, color);
110
111 if self.rasterized.contains_key(&key) {
116 let _ = self.svg_hits.insert(id);
117 let _ = self.rasterized_hits.insert(key);
118
119 return self.rasterized.get(&key);
120 }
121
122 match self.load(handle) {
123 Svg::Loaded(tree) => {
124 if width == 0 || height == 0 {
125 return None;
126 }
127
128 let mut img = tiny_skia::Pixmap::new(width, height)?;
133
134 let tree_size = tree.size().to_int_size();
135
136 let target_size = if width > height {
137 tree_size.scale_to_width(width)
138 } else {
139 tree_size.scale_to_height(height)
140 };
141
142 let transform = if let Some(target_size) = target_size {
143 let tree_size = tree_size.to_size();
144 let target_size = target_size.to_size();
145
146 tiny_skia::Transform::from_scale(
147 target_size.width() / tree_size.width(),
148 target_size.height() / tree_size.height(),
149 )
150 } else {
151 tiny_skia::Transform::default()
152 };
153
154 let render = panic::catch_unwind(panic::AssertUnwindSafe(|| {
157 resvg::render(tree, transform, &mut img.as_mut());
158 }));
159
160 if let Err(error) = render {
161 log::warn!("SVG rendering for {handle:?} panicked: {error:?}");
162 }
163
164 let mut rgba = img.take();
165
166 if let Some(color) = color {
167 rgba.chunks_exact_mut(4).for_each(|rgba| {
168 if rgba[3] > 0 {
169 rgba[0] = color[0];
170 rgba[1] = color[1];
171 rgba[2] = color[2];
172 }
173 });
174 }
175
176 let allocation = atlas.upload(device, encoder, belt, width, height, &rgba)?;
177
178 log::debug!("allocating {id} {width}x{height}");
179
180 let _ = self.svg_hits.insert(id);
181 let _ = self.rasterized_hits.insert(key);
182 let _ = self.rasterized.insert(key, allocation);
183 self.should_trim = true;
184
185 self.rasterized.get(&key)
186 }
187 Svg::NotFound => None,
188 }
189 }
190
191 pub fn trim(&mut self, atlas: &mut Atlas) {
193 if !self.should_trim {
194 return;
195 }
196
197 let svg_hits = &self.svg_hits;
198 let rasterized_hits = &self.rasterized_hits;
199
200 self.svgs.retain(|k, _| svg_hits.contains(k));
201 self.rasterized.retain(|k, entry| {
202 let retain = rasterized_hits.contains(k);
203
204 if !retain {
205 atlas.remove(entry);
206 }
207
208 retain
209 });
210 self.svg_hits.clear();
211 self.rasterized_hits.clear();
212 self.should_trim = false;
213 }
214}
215
216impl std::fmt::Debug for Svg {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 match self {
219 Svg::Loaded(_) => write!(f, "Svg::Loaded"),
220 Svg::NotFound => write!(f, "Svg::NotFound"),
221 }
222 }
223}