Repository: typst/typst
File: crates/typst-library/src/visualize/image/svg.rs (Rust)

Code before:
#[derive(Clone, Hash)]
pub struct SvgImage(Arc<Repr>);

/// The internal representation.
struct Repr {
    data: Bytes,
    size: Axes<f64>,
    font_hash: u128,
    tree: usvg::Tree,
}

impl SvgImage {
    /// Decode an SVG image without fonts.
    #[comemo::memoize]
    #[typst_macros::time(name = "load svg")]
    pub fn new(data: Bytes) -> LoadResult<SvgImage> {
        let tree =
            usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?;
        Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree })))
    }

    /// Decode an SVG image with access to fonts.
    #[comemo::memoize]
    #[typst_macros::time(name = "load svg")]
    pub fn with_fonts(
        svg_parent_file: FileId,
        data: Bytes,
        world: Tracked<dyn World + '_>,
        families: &[&str],
    ) -> LoadResult<SvgImage> {
        let book = world.book();
        let font_resolver = Mutex::new(FontResolver::new(world, book, families));
        let image_resolver = Mutex::new(ImageResolver::new(world, svg_parent_file));
        let tree = usvg::Tree::from_data(
            &data,
            &usvg::Options {
                font_resolver: usvg::FontResolver {
                    select_font: Box::new(|font, db| {
                        font_resolver.lock().unwrap().select_font(font, db)
                    }),
                    select_fallback: Box::new(|c, exclude_fonts, db| {
                        font_resolver.lock().unwrap().select_fallback(
                            c,
                            exclude_fonts,
                            db,
                        )
                    }),
                },
                image_href_resolver: usvg::ImageHrefResolver {
                    resolve_data: usvg::ImageHrefResolver::default_data_resolver(),
                    resolve_string: Box::new(|href, _opts| {

Review comment (performance):
This is not ideal as it prevents an image from being cached if it's used across files even if it does not use this at all. 

It can be _probably_ be prevented by creating a new tracked type that wraps `World` and handles this logic internally. (Extra points for also handling `families` in there.) But it's definitely tricky and could also be done in a followup PR or maybe I'd just push this over the PR once it's otherwise ready.

Code after:
};

/// A decoded SVG.
#[derive(Clone, Hash)]
pub struct SvgImage(Arc<Repr>);

/// The internal representation.
struct Repr {
    data: Bytes,
    size: Axes<f64>,
    font_hash: u128,
    tree: usvg::Tree,
}

impl SvgImage {
    /// Decode an SVG image without fonts.
    #[comemo::memoize]
    #[typst_macros::time(name = "load svg")]
    pub fn new(data: Bytes) -> LoadResult<SvgImage> {
        let tree =
            usvg::Tree::from_data(&data, &base_options()).map_err(format_usvg_error)?;
        Ok(Self(Arc::new(Repr { data, size: tree_size(&tree), font_hash: 0, tree })))
    }

    /// Decode an SVG image with access to fonts and linked images.
    #[comemo::memoize]
    #[typst_macros::time(name = "load svg")]
    pub fn with_fonts_images(
        data: Bytes,
        world: Tracked<dyn World + '_>,
        families: &[&str],
        svg_file: Option<FileId>,
    ) -> LoadResult<SvgImage> {
        let book = world.book();
        let font_resolver = Mutex::new(FontResolver::new(world, book, families));
        let image_resolver = Mutex::new(ImageResolver::new(world, svg_file));
        let tree = usvg::Tree::from_data(
            &data,
            &usvg::Options {
                font_resolver: usvg::FontResolver {
                    select_font: Box::new(|font, db| {
                        font_resolver.lock().unwrap().select_font(font, db)
                    }),
                    select_fallback: Box::new(|c, exclude_fonts, db| {
                        font_resolver.lock().unwrap().select_fallback(
                            c,
                            exclude_fonts,
                            db,
                        )
                    }),
                },