Skip to main content

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