freya_core/elements/
svg.rs

1//! Use [svg()] to render SVG in your app.
2
3use std::{
4    any::Any,
5    borrow::Cow,
6    cell::RefCell,
7    collections::HashMap,
8    rc::Rc,
9};
10
11use bytes::Bytes;
12use freya_engine::prelude::{
13    ClipOp,
14    LocalResourceProvider,
15    Paint,
16    SkRect,
17    svg,
18};
19use rustc_hash::FxHashMap;
20use torin::{
21    prelude::Size2D,
22    size::Size,
23};
24
25use crate::{
26    data::{
27        AccessibilityData,
28        EffectData,
29        LayoutData,
30        StyleState,
31        TextStyleData,
32    },
33    diff_key::DiffKey,
34    element::{
35        ClipContext,
36        Element,
37        ElementExt,
38        EventHandlerType,
39        LayoutContext,
40        RenderContext,
41    },
42    events::name::EventName,
43    layers::Layer,
44    prelude::{
45        AccessibilityExt,
46        Color,
47        ContainerExt,
48        EventHandlersExt,
49        KeyExt,
50        LayerExt,
51        LayoutExt,
52        MaybeExt,
53    },
54    tree::DiffModifies,
55};
56
57/// Use [svg()] to render SVG in your app.
58///
59/// See the available methods in [Svg].
60///
61/// ```rust, no_run
62/// # use freya::prelude::*;
63/// fn app() -> impl IntoElement {
64///     svg(Bytes::from_static(include_bytes!("../../../../logo.svg")))
65/// }
66/// ```
67pub fn svg(bytes: Bytes) -> Svg {
68    let mut accessibility = AccessibilityData::default();
69    accessibility.builder.set_role(accesskit::Role::SvgRoot);
70
71    Svg {
72        key: DiffKey::None,
73        element: SvgElement {
74            accessibility,
75            layout: LayoutData::default(),
76            event_handlers: HashMap::default(),
77            bytes,
78            effect: None,
79            color: Color::BLACK,
80            stroke: None,
81            fill: None,
82            relative_layer: Layer::default(),
83        },
84    }
85}
86
87#[derive(PartialEq, Clone)]
88pub struct SvgElement {
89    pub accessibility: AccessibilityData,
90    pub layout: LayoutData,
91    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
92    pub bytes: Bytes,
93    pub color: Color,
94    pub stroke: Option<Color>,
95    pub fill: Option<Color>,
96    pub effect: Option<EffectData>,
97    pub relative_layer: Layer,
98}
99
100impl ElementExt for SvgElement {
101    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
102        let Some(image) = (other.as_ref() as &dyn Any).downcast_ref::<SvgElement>() else {
103            return false;
104        };
105        self != image
106    }
107
108    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
109        let Some(svg) = (other.as_ref() as &dyn Any).downcast_ref::<SvgElement>() else {
110            return DiffModifies::all();
111        };
112
113        let mut diff = DiffModifies::empty();
114
115        if self.accessibility != svg.accessibility {
116            diff.insert(DiffModifies::ACCESSIBILITY);
117        }
118
119        if self.relative_layer != svg.relative_layer {
120            diff.insert(DiffModifies::LAYER);
121        }
122
123        if self.layout != svg.layout || self.bytes != svg.bytes {
124            diff.insert(DiffModifies::LAYOUT);
125        }
126
127        if self.color != svg.color || self.stroke != svg.stroke {
128            diff.insert(DiffModifies::STYLE);
129        }
130
131        if self.effect != svg.effect {
132            diff.insert(DiffModifies::EFFECT);
133        }
134
135        diff
136    }
137
138    fn layout(&'_ self) -> Cow<'_, LayoutData> {
139        Cow::Borrowed(&self.layout)
140    }
141
142    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
143        self.effect.as_ref().map(Cow::Borrowed)
144    }
145
146    fn style(&'_ self) -> Cow<'_, StyleState> {
147        Cow::Owned(StyleState::default())
148    }
149
150    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
151        Cow::Owned(TextStyleData::default())
152    }
153
154    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
155        Cow::Borrowed(&self.accessibility)
156    }
157
158    fn layer(&self) -> Layer {
159        self.relative_layer
160    }
161
162    fn should_measure_inner_children(&self) -> bool {
163        false
164    }
165
166    fn should_hook_measurement(&self) -> bool {
167        true
168    }
169
170    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
171        let resource_provider = LocalResourceProvider::new(context.font_manager);
172        let svg_dom = svg::Dom::from_bytes(&self.bytes, resource_provider);
173        if let Ok(mut svg_dom) = svg_dom {
174            svg_dom.set_container_size(context.area_size.to_i32().to_tuple());
175            let mut root = svg_dom.root();
176            match self.layout.width {
177                Size::Pixels(px) => {
178                    root.set_width(svg::Length::new(px.get(), svg::LengthUnit::PX));
179                }
180                Size::Percentage(per) => {
181                    root.set_width(svg::Length::new(per.get(), svg::LengthUnit::Percentage));
182                }
183                Size::Fill => {
184                    root.set_width(svg::Length::new(100., svg::LengthUnit::Percentage));
185                }
186                _ => {}
187            }
188            match self.layout.height {
189                Size::Pixels(px) => {
190                    root.set_height(svg::Length::new(px.get(), svg::LengthUnit::PX));
191                }
192                Size::Percentage(per) => {
193                    root.set_height(svg::Length::new(per.get(), svg::LengthUnit::Percentage));
194                }
195                Size::Fill => {
196                    root.set_height(svg::Length::new(100., svg::LengthUnit::Percentage));
197                }
198                _ => {}
199            }
200            Some((
201                Size2D::new(root.width().value, root.height().value),
202                Rc::new(RefCell::new(svg_dom)),
203            ))
204        } else {
205            None
206        }
207    }
208
209    fn clip(&self, context: ClipContext) {
210        let area = context.visible_area;
211        context.canvas.clip_rect(
212            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
213            ClipOp::Intersect,
214            true,
215        );
216    }
217
218    fn render(&self, context: RenderContext) {
219        let mut paint = Paint::default();
220        paint.set_anti_alias(true);
221
222        let svg_dom = context
223            .layout_node
224            .data
225            .as_ref()
226            .unwrap()
227            .downcast_ref::<RefCell<svg::Dom>>()
228            .unwrap();
229        let svg_dom = svg_dom.borrow();
230
231        let mut root = svg_dom.root();
232        context.canvas.save();
233        context
234            .canvas
235            .translate(context.layout_node.visible_area().origin.to_tuple());
236
237        root.set_color(self.color.into());
238        if let Some(fill) = self.fill {
239            root.set_fill(svg::Paint::from_color(fill.into()));
240        }
241        if let Some(stroke) = self.stroke {
242            root.set_stroke(svg::Paint::from_color(stroke.into()));
243        }
244        svg_dom.render(context.canvas);
245        context.canvas.restore();
246    }
247}
248
249impl From<Svg> for Element {
250    fn from(value: Svg) -> Self {
251        Element::Element {
252            key: value.key,
253            element: Rc::new(value.element),
254            elements: vec![],
255        }
256    }
257}
258
259impl KeyExt for Svg {
260    fn write_key(&mut self) -> &mut DiffKey {
261        &mut self.key
262    }
263}
264
265impl EventHandlersExt for Svg {
266    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
267        &mut self.element.event_handlers
268    }
269}
270
271impl LayoutExt for Svg {
272    fn get_layout(&mut self) -> &mut LayoutData {
273        &mut self.element.layout
274    }
275}
276
277impl ContainerExt for Svg {}
278
279impl AccessibilityExt for Svg {
280    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
281        &mut self.element.accessibility
282    }
283}
284
285impl MaybeExt for Svg {}
286
287impl LayerExt for Svg {
288    fn get_layer(&mut self) -> &mut Layer {
289        &mut self.element.relative_layer
290    }
291}
292
293pub struct Svg {
294    key: DiffKey,
295    element: SvgElement,
296}
297
298impl Svg {
299    pub fn try_downcast(element: &dyn ElementExt) -> Option<SvgElement> {
300        (element as &dyn Any).downcast_ref::<SvgElement>().cloned()
301    }
302
303    pub fn color(mut self, color: impl Into<Color>) -> Self {
304        self.element.color = color.into();
305        self
306    }
307
308    pub fn fill(mut self, fill: impl Into<Color>) -> Self {
309        self.element.fill = Some(fill.into());
310        self
311    }
312
313    pub fn stroke<R: Into<Option<Color>>>(mut self, stroke: R) -> Self {
314        self.element.stroke = stroke.into();
315        self
316    }
317
318    pub fn rotate<R: Into<Option<f32>>>(mut self, rotation: R) -> Self {
319        self.element
320            .effect
321            .get_or_insert_with(Default::default)
322            .rotation = rotation.into();
323        self
324    }
325}