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