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)]
125pub struct SvgViewer {
126 source: ImageSource,
127 asset_age: AssetAge,
128
129 layout: LayoutData,
130 image_data: ImageData,
131 accessibility: AccessibilityData,
132 effect: EffectData,
133 event_handlers: EventHandlers,
134 style: SvgStyle,
135 show_loader: bool,
136 parallel: bool,
137
138 children: Vec<Element>,
139 error_renderer: Option<Callback<String, Element>>,
140
141 key: DiffKey,
142}
143
144impl SvgViewer {
145 pub fn new(source: impl Into<ImageSource>) -> Self {
146 let mut accessibility = AccessibilityData::default();
147 accessibility.builder.set_role(AccessibilityRole::SvgRoot);
148
149 SvgViewer {
150 source: source.into(),
151 asset_age: AssetAge::default(),
152 layout: LayoutData::default(),
153 image_data: ImageData::default(),
154 accessibility,
155 effect: EffectData::default(),
156 event_handlers: EventHandlers::default(),
157 style: SvgStyle::default(),
158 show_loader: true,
159 parallel: false,
160 children: Vec::new(),
161 error_renderer: None,
162 key: DiffKey::None,
163 }
164 }
165
166 pub fn show_loader(mut self, show_loader: bool) -> Self {
168 self.show_loader = show_loader;
169 self
170 }
171
172 pub fn parallel(mut self, parallel: bool) -> Self {
174 self.parallel = parallel;
175 self
176 }
177
178 pub fn color(mut self, color: impl Into<Color>) -> Self {
181 self.style.color = Some(color.into());
182 self
183 }
184
185 pub fn fill(mut self, fill: impl Into<Color>) -> Self {
187 self.style.fill = Some(fill.into());
188 self
189 }
190
191 pub fn stroke(mut self, stroke: impl Into<Color>) -> Self {
193 self.style.stroke = Some(stroke.into());
194 self
195 }
196
197 pub fn stroke_width(mut self, stroke_width: f32) -> Self {
199 self.style.stroke_width = Some(stroke_width);
200 self
201 }
202
203 pub fn asset_age(mut self, asset_age: impl Into<AssetAge>) -> Self {
205 self.asset_age = asset_age.into();
206 self
207 }
208
209 pub fn error_renderer(mut self, renderer: impl Into<Callback<String, Element>>) -> Self {
211 self.error_renderer = Some(renderer.into());
212 self
213 }
214}
215
216impl KeyExt for SvgViewer {
217 fn write_key(&mut self) -> &mut DiffKey {
218 &mut self.key
219 }
220}
221
222impl LayoutExt for SvgViewer {
223 fn get_layout(&mut self) -> &mut LayoutData {
224 &mut self.layout
225 }
226}
227
228impl ContainerSizeExt for SvgViewer {}
229impl ContainerWithContentExt for SvgViewer {}
230impl ContainerPositionExt for SvgViewer {}
231
232impl ChildrenExt for SvgViewer {
233 fn get_children(&mut self) -> &mut Vec<Element> {
234 &mut self.children
235 }
236}
237
238impl ImageExt for SvgViewer {
239 fn get_image_data(&mut self) -> &mut ImageData {
240 &mut self.image_data
241 }
242}
243
244impl AccessibilityExt for SvgViewer {
245 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
246 &mut self.accessibility
247 }
248}
249
250impl EffectExt for SvgViewer {
251 fn get_effect(&mut self) -> &mut EffectData {
252 &mut self.effect
253 }
254}
255
256impl EventHandlersExt for SvgViewer {
257 fn get_event_handlers(&mut self) -> &mut EventHandlers {
258 &mut self.event_handlers
259 }
260}
261
262impl Component for SvgViewer {
263 fn render(&self) -> impl IntoElement {
264 let scale_factor = *Platform::get().scale_factor.read();
265 let layout = self.layout.clone();
266 let mut measured = use_state(|| match (&layout.width, &layout.height) {
267 (Size::Pixels(width), Size::Pixels(height)) => {
268 Some(Size2D::new(width.get(), height.get()))
269 }
270 _ => None,
271 });
272 let mut asset_cacher = use_hook(AssetCacher::get);
273 let mut inherited_color = use_state::<Option<Color>>(|| None);
274
275 let target = measured().map(|logical| {
276 DecodeSize::new(
277 (logical.width * scale_factor as f32).round().max(1.) as u32,
278 (logical.height * scale_factor as f32).round().max(1.) as u32,
279 )
280 });
281
282 let mut style = self.style;
283 if style.color.is_none() {
284 style.color = inherited_color();
285 }
286
287 let asset_config =
288 AssetConfiguration::new((&self.source, target, style.as_key()), self.asset_age);
289 use_asset(&asset_config);
290
291 let mut previous_configuration = use_state(|| None);
293 if *previous_configuration.peek() != Some((asset_config.clone(), self.parallel)) {
294 previous_configuration.set(Some((asset_config.clone(), self.parallel)));
295
296 if let Some(target) = target
297 && style.color.is_some()
298 && matches!(
299 asset_cacher.read_asset(&asset_config),
300 Some(Asset::Pending) | Some(Asset::Error(_))
301 )
302 {
303 asset_cacher.update_asset(asset_config.clone(), Asset::Loading);
304
305 if self.parallel {
306 let source = self.source.clone();
307 let asset_config = asset_config.clone();
308 spawn_forever(async move {
309 #[cfg(feature = "remote-asset")]
310 let bytes = {
311 let client = Http::get();
312 blocking::unblock(move || source.fetch(&client)).await
313 };
314 #[cfg(not(feature = "remote-asset"))]
315 let bytes = blocking::unblock(move || source.fetch()).await;
316
317 let result = match bytes {
318 Ok(bytes) => {
319 let _permit = RASTER_LIMIT.acquire().await;
320 blocking::unblock(move || rasterize_bytes(&bytes, target, style))
321 .await
322 }
323 Err(err) => Err(err),
324 };
325 store_raster(asset_cacher, asset_config.clone(), result);
326 });
327 } else {
328 #[cfg(feature = "remote-asset")]
329 let bytes = self.source.clone().fetch(&Http::get());
330 #[cfg(not(feature = "remote-asset"))]
331 let bytes = self.source.clone().fetch();
332
333 let result = bytes.and_then(|bytes| rasterize_bytes(&bytes, target, style));
334 store_raster(asset_cacher, asset_config.clone(), result);
335 }
336 }
337 }
338
339 let asset = asset_cacher
340 .read_asset(&asset_config)
341 .expect("Asset should exist by now");
342
343 match asset {
344 Asset::Cached(asset) => {
345 let handle = asset.downcast_ref::<ImageHandle>().unwrap().clone();
346 image(handle)
347 .accessibility(self.accessibility.clone())
348 .layout(layout)
349 .image_data(self.image_data.clone())
350 .effect(self.effect.clone())
351 .children(self.children.clone())
352 .event_handlers(self.event_handlers.clone())
353 .on_sized(move |event: Event<SizedEventData>| {
354 measured.set_if_modified(Some(event.visible_area.size));
355 })
356 .on_styled(move |event: Event<StyledEventData>| {
357 let color = event.text_style.color.as_color().unwrap_or(Color::BLACK);
358 inherited_color.set_if_modified(Some(color));
359 })
360 .into_element()
361 }
362 Asset::Error(err) => match &self.error_renderer {
363 Some(renderer) => renderer.call(err),
364 None => err.into(),
365 },
366 Asset::Pending | Asset::Loading => rect()
367 .layout(layout)
368 .event_handlers(self.event_handlers.clone())
369 .on_sized(move |event: Event<SizedEventData>| {
370 measured.set_if_modified(Some(event.visible_area.size));
371 })
372 .on_styled(move |event: Event<StyledEventData>| {
373 let color = event.text_style.color.as_color().unwrap_or(Color::BLACK);
374 inherited_color.set_if_modified(Some(color));
375 })
376 .center()
377 .maybe(self.show_loader, |loading| {
378 loading.child(CircularLoader::new())
379 })
380 .into_element(),
381 }
382 }
383
384 fn render_key(&self) -> DiffKey {
385 self.key.clone().or(self.default_key())
386 }
387}
388
389pub trait SvgThemeExt {
391 fn theme_color(self) -> Self;
392 fn theme_accent_color(self) -> Self;
393 fn theme_fill(self) -> Self;
394 fn theme_stroke(self) -> Self;
395 fn theme_accent_fill(self) -> Self;
396 fn theme_accent_stroke(self) -> Self;
397}
398
399impl SvgThemeExt for SvgViewer {
400 fn theme_color(self) -> Self {
401 let theme = get_theme_or_default();
402 self.color(theme.read().colors.text_primary)
403 }
404
405 fn theme_accent_color(self) -> Self {
406 let theme = get_theme_or_default();
407 self.color(theme.read().colors.primary)
408 }
409
410 fn theme_fill(self) -> Self {
411 let theme = get_theme_or_default();
412 self.fill(theme.read().colors.text_primary)
413 }
414
415 fn theme_stroke(self) -> Self {
416 let theme = get_theme_or_default();
417 self.stroke(theme.read().colors.text_primary)
418 }
419
420 fn theme_accent_fill(self) -> Self {
421 let theme = get_theme_or_default();
422 self.fill(theme.read().colors.primary)
423 }
424
425 fn theme_accent_stroke(self) -> Self {
426 let theme = get_theme_or_default();
427 self.stroke(theme.read().colors.primary)
428 }
429}