katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use tiny_skia::Pixmap;

use resvg::usvg;

const MAX_RASTERIZED_SVG_EDGE: f32 = 8192.0;

pub(super) struct RasterTarget {
    effective_scale: f32,
    width: u32,
    height: u32,
}

impl RasterTarget {
    pub(super) fn new(size: usvg::Size, max_width: u32) -> Self {
        let display_width = size.width();
        let display_height = size.height();
        let effective_scale = effective_scale(display_width, display_height, max_width);
        Self {
            effective_scale,
            width: ((display_width * effective_scale).ceil() as u32).max(1),
            height: ((display_height * effective_scale).ceil() as u32).max(1),
        }
    }

    pub(super) fn render(&self, tree: &usvg::Tree) -> Option<Pixmap> {
        let mut pixmap = Pixmap::new(self.width, self.height)?;
        let transform =
            tiny_skia::Transform::from_scale(self.effective_scale, self.effective_scale);
        resvg::render(tree, transform, &mut pixmap.as_mut());
        Some(pixmap)
    }

    pub(super) fn width(&self) -> u32 {
        self.width
    }

    pub(super) fn height(&self) -> u32 {
        self.height
    }
}

pub(super) fn rasterizer_options() -> usvg::Options<'static> {
    usvg::Options {
        fontdb: font_db(),
        ..usvg::Options::default()
    }
}

fn font_db() -> std::sync::Arc<usvg::fontdb::Database> {
    static FONT_DB: std::sync::OnceLock<std::sync::Arc<usvg::fontdb::Database>> =
        std::sync::OnceLock::new();
    std::sync::Arc::clone(FONT_DB.get_or_init(|| {
        let mut db = usvg::fontdb::Database::new();
        db.load_system_fonts();
        std::sync::Arc::new(db)
    }))
}

fn effective_scale(width: f32, height: f32, max_width: u32) -> f32 {
    let requested_scale = (max_width as f32 / width.max(1.0)).min(1.0);
    let width_scale = MAX_RASTERIZED_SVG_EDGE / width.max(1.0);
    let height_scale = MAX_RASTERIZED_SVG_EDGE / height.max(1.0);
    requested_scale
        .min(width_scale)
        .min(height_scale)
        .max(f32::MIN_POSITIVE)
}

#[cfg(test)]
#[path = "export_surface_svg_raster_tests.rs"]
mod tests;