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