pulldown_cmark_mdcat/resources/
svg.rs

1// Copyright 2018-2020 Sebastian Wiesner <sebastian@swsnr.de>
2
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! SVG rendering for mdcat.
8
9use std::io::Result;
10
11/// Render an SVG image to a PNG pixel graphic for display.
12pub fn render_svg_to_png(svg: &[u8]) -> Result<Vec<u8>> {
13    implementation::render_svg_to_png(svg)
14}
15
16#[cfg(feature = "svg")]
17mod implementation {
18    use std::fmt::Display;
19    use std::sync::{Arc, OnceLock};
20    use std::{error::Error, io::ErrorKind};
21
22    use resvg::tiny_skia::{IntSize, Pixmap, Transform};
23    use resvg::usvg::{self, Tree};
24    use usvg::fontdb;
25
26    #[derive(Debug)]
27    pub enum RenderSvgError {
28        ParseError(usvg::Error),
29        FailedToCreatePixmap(IntSize),
30        EncodePngError(Box<dyn Error + Send + Sync>),
31    }
32
33    impl Display for RenderSvgError {
34        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35            match self {
36                RenderSvgError::ParseError(error) => write!(f, "Failed to parse SVG: {error}"),
37                RenderSvgError::FailedToCreatePixmap(int_size) => {
38                    write!(f, "Failed to create pixmap of size {int_size:?}")
39                }
40                RenderSvgError::EncodePngError(error) => {
41                    write!(f, "Failed to encode pixmap to PNG image: {error}")
42                }
43            }
44        }
45    }
46
47    impl std::error::Error for RenderSvgError {
48        fn source(&self) -> Option<&(dyn Error + 'static)> {
49            match self {
50                RenderSvgError::ParseError(error) => Some(error),
51                RenderSvgError::FailedToCreatePixmap(_) => None,
52                RenderSvgError::EncodePngError(error) => Some(error.as_ref()),
53            }
54        }
55    }
56
57    impl From<RenderSvgError> for std::io::Error {
58        fn from(value: RenderSvgError) -> Self {
59            std::io::Error::new(ErrorKind::Other, value)
60        }
61    }
62
63    impl From<usvg::Error> for RenderSvgError {
64        fn from(value: usvg::Error) -> Self {
65            Self::ParseError(value)
66        }
67    }
68
69    static FONTS: OnceLock<Arc<fontdb::Database>> = OnceLock::new();
70
71    fn parse_svg(svg: &[u8]) -> Result<Tree, RenderSvgError> {
72        let fonts = FONTS.get_or_init(|| {
73            let mut fontdb = fontdb::Database::new();
74            fontdb.load_system_fonts();
75            Arc::new(fontdb)
76        });
77        let options = usvg::Options {
78            fontdb: fonts.clone(),
79            ..Default::default()
80        };
81        Ok(usvg::Tree::from_data(svg, &options)?)
82    }
83
84    fn render_svg_to_png_with_resvg(svg: &[u8]) -> Result<Vec<u8>, RenderSvgError> {
85        let tree = parse_svg(svg)?;
86        let size = tree.size().to_int_size();
87        let mut pixmap = Pixmap::new(size.width(), size.height())
88            .ok_or(RenderSvgError::FailedToCreatePixmap(size))?;
89        // We create a pixmap of the appropriate size so the size transform in render cannot fail, so
90        // if it fails it's a bug in our code or in resvg which we should fix and not hide.  Hence we
91        // unwrap the result.
92        resvg::render(&tree, Transform::default(), &mut pixmap.as_mut());
93        pixmap
94            .encode_png()
95            .map_err(|err| RenderSvgError::EncodePngError(Box::new(err)))
96    }
97
98    pub fn render_svg_to_png(svg: &[u8]) -> std::io::Result<Vec<u8>> {
99        render_svg_to_png_with_resvg(svg).map_err(Into::into)
100    }
101}
102
103#[cfg(not(feature = "svg"))]
104mod implementation {
105    use std::io::{Error, ErrorKind, Result};
106    pub fn render_svg_to_png(_svg: &[u8]) -> Result<Vec<u8>> {
107        Err(Error::new(
108            ErrorKind::Unsupported,
109            "SVG rendering not enabled in this build",
110        ))
111    }
112}