freya_core/elements/
svg.rs

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