1use std::{
2 rc::Rc,
3 sync::LazyLock,
4};
5
6use anyhow::Context;
7use async_lock::Semaphore;
8use bytes::Bytes;
9use freya_core::{
10 element::EventHandlerType,
11 elements::image::*,
12 events::name::EventName,
13 prelude::*,
14};
15use freya_engine::prelude::{
16 FontMgr,
17 SkImage,
18 raster_n32_premul,
19 svg,
20};
21use rustc_hash::FxHashMap;
22use torin::prelude::{
23 Size,
24 Size2D,
25};
26
27#[cfg(feature = "remote-asset")]
28use crate::http::Http;
29use crate::{
30 cache::*,
31 image_viewer::{
32 DecodeSize,
33 ImageSource,
34 },
35 loader::CircularLoader,
36 theming::hooks::get_theme_or_default,
37};
38
39static RASTER_LIMIT: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(4));
41
42#[derive(Default, Clone, Copy, PartialEq)]
44struct SvgStyle {
45 color: Option<Color>,
46 fill: Option<Color>,
47 stroke: Option<Color>,
48 stroke_width: Option<f32>,
49}
50
51impl SvgStyle {
52 fn as_key(&self) -> (Option<Color>, Option<Color>, Option<Color>, Option<u32>) {
54 (
55 self.color,
56 self.fill,
57 self.stroke,
58 self.stroke_width.map(f32::to_bits),
59 )
60 }
61}
62
63fn rasterize_bytes(bytes: &[u8], size: DecodeSize, style: SvgStyle) -> anyhow::Result<SkImage> {
65 let width = size.width.max(1) as i32;
66 let height = size.height.max(1) as i32;
67
68 let mut dom = svg::Dom::from_bytes(bytes, FontMgr::empty())
69 .map_err(|err| anyhow::anyhow!("Failed to parse SVG: {err:?}"))?;
70 dom.set_container_size((width, height));
71
72 let mut root = dom.root();
73 root.set_width(svg::Length::new(width as f32, svg::LengthUnit::PX));
74 root.set_height(svg::Length::new(height as f32, svg::LengthUnit::PX));
75 root.set_color(style.color.unwrap_or(Color::BLACK).into());
76 if let Some(fill) = style.fill {
77 root.set_fill(svg::Paint::from_color(fill.into()));
78 }
79 if let Some(stroke) = style.stroke {
80 root.set_stroke(svg::Paint::from_color(stroke.into()));
81 }
82 if let Some(stroke_width) = style.stroke_width {
83 root.set_stroke_width(svg::Length::new(stroke_width, svg::LengthUnit::PX));
84 }
85
86 let mut surface =
87 raster_n32_premul((width, height)).context("Failed to create the SVG surface.")?;
88 dom.render(surface.canvas());
89 Ok(surface.image_snapshot())
90}
91
92fn store_raster(
94 mut asset_cacher: AssetCacher,
95 asset_config: AssetConfiguration,
96 result: anyhow::Result<SkImage>,
97) {
98 match result {
99 Ok(image) => {
100 asset_cacher.update_asset(
101 asset_config,
102 Asset::Cached(Rc::new(ImageHandle::new(image, Bytes::new()))),
103 );
104 }
105 Err(err) => {
106 asset_cacher.update_asset(asset_config, Asset::Error(err.to_string()));
107 }
108 }
109}
110
111#[derive(PartialEq)]
127pub struct SvgViewer {
128 source: ImageSource,
129 asset_age: AssetAge,
130
131 layout: LayoutData,
132 image_data: ImageData,
133 accessibility: AccessibilityData,
134 effect: EffectData,
135 event_handlers: FxHashMap<EventName, EventHandlerType>,
136 style: SvgStyle,
137 show_loader: bool,
138 parallel: bool,
139
140 children: Vec<Element>,
141 error_renderer: Option<Callback<String, Element>>,
142
143 key: DiffKey,
144}
145
146impl SvgViewer {
147 pub fn new(source: impl Into<ImageSource>) -> Self {
148 let mut accessibility = AccessibilityData::default();
149 accessibility.builder.set_role(AccessibilityRole::SvgRoot);
150
151 SvgViewer {
152 source: source.into(),
153 asset_age: AssetAge::default(),
154 layout: LayoutData::default(),
155 image_data: ImageData::default(),
156 accessibility,
157 effect: EffectData::default(),
158 event_handlers: FxHashMap::default(),
159 style: SvgStyle::default(),
160 show_loader: true,
161 parallel: false,
162 children: Vec::new(),
163 error_renderer: None,
164 key: DiffKey::None,
165 }
166 }
167
168 pub fn show_loader(mut self, show_loader: bool) -> Self {
170 self.show_loader = show_loader;
171 self
172 }
173
174 pub fn parallel(mut self, parallel: bool) -> Self {
176 self.parallel = parallel;
177 self
178 }
179
180 pub fn color(mut self, color: impl Into<Color>) -> Self {
183 self.style.color = Some(color.into());
184 self
185 }
186
187 pub fn fill(mut self, fill: impl Into<Color>) -> Self {
189 self.style.fill = Some(fill.into());
190 self
191 }
192
193 pub fn stroke(mut self, stroke: impl Into<Color>) -> Self {
195 self.style.stroke = Some(stroke.into());
196 self
197 }
198
199 pub fn stroke_width(mut self, stroke_width: f32) -> Self {
201 self.style.stroke_width = Some(stroke_width);
202 self
203 }
204
205 pub fn asset_age(mut self, asset_age: impl Into<AssetAge>) -> Self {
207 self.asset_age = asset_age.into();
208 self
209 }
210
211 pub fn error_renderer(mut self, renderer: impl Into<Callback<String, Element>>) -> Self {
213 self.error_renderer = Some(renderer.into());
214 self
215 }
216}
217
218impl KeyExt for SvgViewer {
219 fn write_key(&mut self) -> &mut DiffKey {
220 &mut self.key
221 }
222}
223
224impl LayoutExt for SvgViewer {
225 fn get_layout(&mut self) -> &mut LayoutData {
226 &mut self.layout
227 }
228}
229
230impl ContainerSizeExt for SvgViewer {}
231impl ContainerWithContentExt for SvgViewer {}
232impl ContainerPositionExt for SvgViewer {}
233
234impl ChildrenExt for SvgViewer {
235 fn get_children(&mut self) -> &mut Vec<Element> {
236 &mut self.children
237 }
238}
239
240impl ImageExt for SvgViewer {
241 fn get_image_data(&mut self) -> &mut ImageData {
242 &mut self.image_data
243 }
244}
245
246impl AccessibilityExt for SvgViewer {
247 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
248 &mut self.accessibility
249 }
250}
251
252impl EffectExt for SvgViewer {
253 fn get_effect(&mut self) -> &mut EffectData {
254 &mut self.effect
255 }
256}
257
258impl EventHandlersExt for SvgViewer {
259 fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
260 &mut self.event_handlers
261 }
262}
263
264impl Component for SvgViewer {
265 fn render(&self) -> impl IntoElement {
266 let scale_factor = *Platform::get().scale_factor.read();
267 let layout = self.layout.clone();
268 let mut measured = use_state(|| match (&layout.width, &layout.height) {
269 (Size::Pixels(width), Size::Pixels(height)) => {
270 Some(Size2D::new(width.get(), height.get()))
271 }
272 _ => None,
273 });
274 let mut asset_cacher = use_hook(AssetCacher::get);
275 let mut inherited_color = use_state::<Option<Color>>(|| None);
276
277 let target = measured().map(|logical| {
278 DecodeSize::new(
279 (logical.width * scale_factor as f32).round().max(1.) as u32,
280 (logical.height * scale_factor as f32).round().max(1.) as u32,
281 )
282 });
283
284 let mut style = self.style;
285 if style.color.is_none() {
286 style.color = inherited_color();
287 }
288
289 let asset_config =
290 AssetConfiguration::new((&self.source, target, style.as_key()), self.asset_age);
291 use_asset(&asset_config);
292
293 let mut previous_configuration = use_state(|| None);
295 if *previous_configuration.peek() != Some((asset_config.clone(), self.parallel)) {
296 previous_configuration.set(Some((asset_config.clone(), self.parallel)));
297
298 if let Some(target) = target
299 && style.color.is_some()
300 && matches!(
301 asset_cacher.read_asset(&asset_config),
302 Some(Asset::Pending) | Some(Asset::Error(_))
303 )
304 {
305 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
306
307 if self.parallel {
308 let source = self.source.clone();
309 let asset_config = asset_config.clone();
310 spawn_forever(async move {
311 #[cfg(feature = "remote-asset")]
312 let bytes = {
313 let client = Http::get();
314 blocking::unblock(move || source.fetch(&client)).await
315 };
316 #[cfg(not(feature = "remote-asset"))]
317 let bytes = blocking::unblock(move || source.fetch()).await;
318
319 let result = match bytes {
320 Ok(bytes) => {
321 let _permit = RASTER_LIMIT.acquire().await;
322 blocking::unblock(move || rasterize_bytes(&bytes, target, style))
323 .await
324 }
325 Err(err) => Err(err),
326 };
327 store_raster(asset_cacher, asset_config.clone(), result);
328 });
329 } else {
330 #[cfg(feature = "remote-asset")]
331 let bytes = self.source.clone().fetch(&Http::get());
332 #[cfg(not(feature = "remote-asset"))]
333 let bytes = self.source.clone().fetch();
334
335 let result = bytes.and_then(|bytes| rasterize_bytes(&bytes, target, style));
336 store_raster(asset_cacher, asset_config.clone(), result);
337 }
338 }
339 }
340
341 let asset = asset_cacher
342 .read_asset(&asset_config)
343 .expect("Asset should exist by now");
344
345 match asset {
346 Asset::Cached(asset) => {
347 let handle = asset.downcast_ref::<ImageHandle>().unwrap().clone();
348 image(handle)
349 .accessibility(self.accessibility.clone())
350 .layout(layout)
351 .image_data(self.image_data.clone())
352 .effect(self.effect.clone())
353 .children(self.children.clone())
354 .with_event_handlers(self.event_handlers.clone())
355 .on_sized(move |event: Event<SizedEventData>| {
356 measured.set_if_modified(Some(event.visible_area.size));
357 })
358 .on_styled(move |event: Event<StyledEventData>| {
359 let color = event.text_style.color.as_color().unwrap_or(Color::BLACK);
360 inherited_color.set_if_modified(Some(color));
361 })
362 .into_element()
363 }
364 Asset::Error(err) => match &self.error_renderer {
365 Some(renderer) => renderer.call(err),
366 None => err.into(),
367 },
368 Asset::Pending | Asset::Loading => rect()
369 .layout(layout)
370 .with_event_handlers(self.event_handlers.clone())
371 .on_sized(move |event: Event<SizedEventData>| {
372 measured.set_if_modified(Some(event.visible_area.size));
373 })
374 .on_styled(move |event: Event<StyledEventData>| {
375 let color = event.text_style.color.as_color().unwrap_or(Color::BLACK);
376 inherited_color.set_if_modified(Some(color));
377 })
378 .center()
379 .maybe(self.show_loader, |loading| {
380 loading.child(CircularLoader::new())
381 })
382 .into_element(),
383 }
384 }
385
386 fn render_key(&self) -> DiffKey {
387 self.key.clone().or(self.default_key())
388 }
389}
390
391pub trait SvgThemeExt {
393 fn theme_color(self) -> Self;
394 fn theme_accent_color(self) -> Self;
395 fn theme_fill(self) -> Self;
396 fn theme_stroke(self) -> Self;
397 fn theme_accent_fill(self) -> Self;
398 fn theme_accent_stroke(self) -> Self;
399}
400
401impl SvgThemeExt for SvgViewer {
402 fn theme_color(self) -> Self {
403 let theme = get_theme_or_default();
404 self.color(theme.read().colors.text_primary)
405 }
406
407 fn theme_accent_color(self) -> Self {
408 let theme = get_theme_or_default();
409 self.color(theme.read().colors.primary)
410 }
411
412 fn theme_fill(self) -> Self {
413 let theme = get_theme_or_default();
414 self.fill(theme.read().colors.text_primary)
415 }
416
417 fn theme_stroke(self) -> Self {
418 let theme = get_theme_or_default();
419 self.stroke(theme.read().colors.text_primary)
420 }
421
422 fn theme_accent_fill(self) -> Self {
423 let theme = get_theme_or_default();
424 self.fill(theme.read().colors.primary)
425 }
426
427 fn theme_accent_stroke(self) -> Self {
428 let theme = get_theme_or_default();
429 self.stroke(theme.read().colors.primary)
430 }
431}