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