svg_renderer.rs

  1use crate::{
  2    AssetSource, DevicePixels, IsZero, RenderImage, Result, SharedString, Size,
  3    swap_rgba_pa_to_bgra,
  4};
  5use image::Frame;
  6use resvg::tiny_skia::Pixmap;
  7use smallvec::SmallVec;
  8use std::{
  9    hash::Hash,
 10    sync::{Arc, LazyLock},
 11};
 12
 13/// When rendering SVGs, we render them at twice the size to get a higher-quality result.
 14pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
 15
 16#[derive(Clone, PartialEq, Hash, Eq)]
 17#[expect(missing_docs)]
 18pub struct RenderSvgParams {
 19    pub path: SharedString,
 20    pub size: Size<DevicePixels>,
 21}
 22
 23#[derive(Clone)]
 24/// A struct holding everything necessary to render SVGs.
 25pub struct SvgRenderer {
 26    asset_source: Arc<dyn AssetSource>,
 27    usvg_options: Arc<usvg::Options<'static>>,
 28}
 29
 30/// The size in which to render the SVG.
 31pub enum SvgSize {
 32    /// An absolute size in device pixels.
 33    Size(Size<DevicePixels>),
 34    /// A scaling factor to apply to the size provided by the SVG.
 35    ScaleFactor(f32),
 36}
 37
 38impl SvgRenderer {
 39    /// Creates a new SVG renderer with the provided asset source.
 40    pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
 41        static FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = LazyLock::new(|| {
 42            let mut db = usvg::fontdb::Database::new();
 43            db.load_system_fonts();
 44            Arc::new(db)
 45        });
 46        let default_font_resolver = usvg::FontResolver::default_font_selector();
 47        let font_resolver = Box::new(
 48            move |font: &usvg::Font, db: &mut Arc<usvg::fontdb::Database>| {
 49                if db.is_empty() {
 50                    *db = FONT_DB.clone();
 51                }
 52                default_font_resolver(font, db)
 53            },
 54        );
 55        let options = usvg::Options {
 56            font_resolver: usvg::FontResolver {
 57                select_font: font_resolver,
 58                select_fallback: usvg::FontResolver::default_fallback_selector(),
 59            },
 60            ..Default::default()
 61        };
 62        Self {
 63            asset_source,
 64            usvg_options: Arc::new(options),
 65        }
 66    }
 67
 68    /// Renders the given bytes into an image buffer.
 69    pub fn render_single_frame(
 70        &self,
 71        bytes: &[u8],
 72        scale_factor: f32,
 73        to_brga: bool,
 74    ) -> Result<Arc<RenderImage>, usvg::Error> {
 75        self.render_pixmap(
 76            bytes,
 77            SvgSize::ScaleFactor(scale_factor * SMOOTH_SVG_SCALE_FACTOR),
 78        )
 79        .map(|pixmap| {
 80            let mut buffer =
 81                image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
 82                    .unwrap();
 83
 84            if to_brga {
 85                for pixel in buffer.chunks_exact_mut(4) {
 86                    swap_rgba_pa_to_bgra(pixel);
 87                }
 88            }
 89
 90            let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));
 91            image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
 92            Arc::new(image)
 93        })
 94    }
 95
 96    pub(crate) fn render_alpha_mask(
 97        &self,
 98        params: &RenderSvgParams,
 99        bytes: Option<&[u8]>,
100    ) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
101        anyhow::ensure!(!params.size.is_zero(), "can't render at a zero size");
102
103        let render_pixmap = |bytes| {
104            let pixmap = self.render_pixmap(bytes, SvgSize::Size(params.size))?;
105
106            // Convert the pixmap's pixels into an alpha mask.
107            let size = Size::new(
108                DevicePixels(pixmap.width() as i32),
109                DevicePixels(pixmap.height() as i32),
110            );
111            let alpha_mask = pixmap
112                .pixels()
113                .iter()
114                .map(|p| p.alpha())
115                .collect::<Vec<_>>();
116
117            Ok(Some((size, alpha_mask)))
118        };
119
120        if let Some(bytes) = bytes {
121            render_pixmap(bytes)
122        } else if let Some(bytes) = self.asset_source.load(&params.path)? {
123            render_pixmap(&bytes)
124        } else {
125            Ok(None)
126        }
127    }
128
129    fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
130        let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
131        let svg_size = tree.size();
132        let scale = match size {
133            SvgSize::Size(size) => size.width.0 as f32 / svg_size.width(),
134            SvgSize::ScaleFactor(scale) => scale,
135        };
136
137        // Render the SVG to a pixmap with the specified width and height.
138        let mut pixmap = resvg::tiny_skia::Pixmap::new(
139            (svg_size.width() * scale) as u32,
140            (svg_size.height() * scale) as u32,
141        )
142        .ok_or(usvg::Error::InvalidSize)?;
143
144        let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
145
146        resvg::render(&tree, transform, &mut pixmap.as_mut());
147
148        Ok(pixmap)
149    }
150}