summaryrefslogtreecommitdiffstats
path: root/src/image/svg.rs
blob: 4176983f8b41f1f11e1613830646cf5ebeede769 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use {
    crate::{
        errors::SvgError,
    },
    image::{
        DynamicImage,
        RgbaImage,
    },
    std::path::PathBuf,
    usvg::ScreenSize,
    usvg_text_layout::{fontdb, TreeTextToPath},
};

fn compute_zoom(width:u32, height:u32, max_width:u32, max_height:u32) -> Result<f32, SvgError> {
    let w: f32 = width as f32;
    let h: f32 = height as f32;
    let mw: f32 = max_width.max(2) as f32;
    let mh: f32 = max_height.max(2) as f32;
    let zoom = 1.0f32
        .min(mw / w)
        .min(mh / h);
    if zoom > 0.0f32 {
        Ok(zoom)
    } else {
        Err(SvgError::Internal { message: "invalid SVG dimensions" })
    }
}

/// Generate a bitmap at the natural dimensions of the SVG unless it's too big
///  in which case a smaller one is generated to fit into (max_width x max_height).
pub fn render<P: Into<PathBuf>>(
    path: P,
    max_width: u32,
    max_height: u32,
) -> Result<DynamicImage, SvgError> {
    let path: PathBuf = path.into();
    let opt = usvg::Options {
        resources_dir: Some(path.clone()),
        ..Default::default()
    };
    let mut fontdb = fontdb::Database::new();
    fontdb.load_system_fonts();
    let svg_data = std::fs::read(path)?;
    let mut tree = usvg::Tree::from_data(&svg_data, &opt)?;
    debug!("SVG natural size: {} x {}", tree.size.width(), tree.size.height());
    let px_size = tree.size.to_screen_size();
    let zoom = compute_zoom(px_size.width(), px_size.height(), max_width, max_height)?;
    debug!("svg rendering zoom: {zoom}");
    let Some(px_size) = ScreenSize::new(
        (px_size.width() as f32 * zoom) as u32,
        (px_size.height() as f32 * zoom) as u32,
    ) else {
        return Err(SvgError::Internal { message: "invalid SVG dimensions" });
    };
    debug!("px_size: {px_size:?}");
    tree.convert_text(&fontdb, opt.keep_named_groups);
    let mut pixmap = tiny_skia::Pixmap::new(
        px_size.width(),
        px_size.height(),
    ).ok_or(SvgError::Internal { message: "unable to create pixmap buffer" })?;
    resvg::render(
        &tree,
        usvg::FitTo::Zoom(zoom),
        tiny_skia::Transform::default(),
        pixmap.as_mut(),
    ).ok_or(SvgError::Internal { message: "resvg doesn't look happy (not sure)" })?;
    let image_buffer = RgbaImage::from_vec(
        pixmap.width(),
        pixmap.height(),
        pixmap.take(),
    ).ok_or(SvgError::Internal { message: "wrong image buffer size" })?;
    Ok(DynamicImage::ImageRgba8(image_buffer))
}