svg.rs

  1use std::{borrow::Cow, ops::Range};
  2
  3use serde_json::json;
  4
  5use crate::{
  6    color::Color,
  7    geometry::{
  8        rect::RectF,
  9        vector::{vec2f, Vector2F},
 10    },
 11    presenter::MeasurementContext,
 12    scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
 13};
 14
 15pub struct Svg {
 16    path: Cow<'static, str>,
 17    color: Color,
 18}
 19
 20impl Svg {
 21    pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
 22        Self {
 23            path: path.into(),
 24            color: Color::black(),
 25        }
 26    }
 27
 28    pub fn with_color(mut self, color: Color) -> Self {
 29        self.color = color;
 30        self
 31    }
 32}
 33
 34impl Element for Svg {
 35    type LayoutState = Option<usvg::Tree>;
 36    type PaintState = ();
 37
 38    fn layout(
 39        &mut self,
 40        constraint: SizeConstraint,
 41        cx: &mut LayoutContext,
 42    ) -> (Vector2F, Self::LayoutState) {
 43        match cx.asset_cache.svg(&self.path) {
 44            Ok(tree) => {
 45                let size = constrain_size_preserving_aspect_ratio(
 46                    constraint.max,
 47                    from_usvg_rect(tree.svg_node().view_box.rect).size(),
 48                );
 49                (size, Some(tree))
 50            }
 51            Err(_error) => {
 52                #[cfg(not(any(test, feature = "test-support")))]
 53                log::error!("{}", _error);
 54                (constraint.min, None)
 55            }
 56        }
 57    }
 58
 59    fn paint(
 60        &mut self,
 61        bounds: RectF,
 62        _visible_bounds: RectF,
 63        svg: &mut Self::LayoutState,
 64        cx: &mut PaintContext,
 65    ) {
 66        if let Some(svg) = svg.clone() {
 67            cx.scene.push_icon(scene::Icon {
 68                bounds,
 69                svg,
 70                path: self.path.clone(),
 71                color: self.color,
 72            });
 73        }
 74    }
 75
 76    fn rect_for_text_range(
 77        &self,
 78        _: Range<usize>,
 79        _: RectF,
 80        _: RectF,
 81        _: &Self::LayoutState,
 82        _: &Self::PaintState,
 83        _: &MeasurementContext,
 84    ) -> Option<RectF> {
 85        None
 86    }
 87
 88    fn debug(
 89        &self,
 90        bounds: RectF,
 91        _: &Self::LayoutState,
 92        _: &Self::PaintState,
 93        _: &DebugContext,
 94    ) -> serde_json::Value {
 95        json!({
 96            "type": "Svg",
 97            "bounds": bounds.to_json(),
 98            "path": self.path,
 99            "color": self.color.to_json(),
100        })
101    }
102}
103
104use crate::json::ToJson;
105
106use super::constrain_size_preserving_aspect_ratio;
107
108fn from_usvg_rect(rect: usvg::Rect) -> RectF {
109    RectF::new(
110        vec2f(rect.x() as f32, rect.y() as f32),
111        vec2f(rect.width() as f32, rect.height() as f32),
112    )
113}