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