summaryrefslogblamecommitdiffstats
path: root/tiny_skia/src/vector.rs
blob: fd9cfdc501f6d0de1fe0faf3dc9cab2c6c095d74 (plain) (tree)




















































































































































                                                                                
use crate::core::svg::{Data, Handle};
use crate::core::{Rectangle, Size};

use resvg::usvg;
use rustc_hash::{FxHashMap, FxHashSet};

use std::cell::RefCell;
use std::collections::hash_map;
use std::fs;

pub struct Pipeline {
    cache: RefCell<Cache>,
}

impl Pipeline {
    pub fn new() -> Self {
        Self {
            cache: RefCell::new(Cache::default()),
        }
    }

    pub fn viewport_dimensions(&self, handle: &Handle) -> Size<u32> {
        self.cache
            .borrow_mut()
            .viewport_dimensions(handle)
            .unwrap_or(Size::new(0, 0))
    }

    pub fn draw(
        &mut self,
        handle: &Handle,
        bounds: Rectangle,
        pixels: &mut tiny_skia::PixmapMut<'_>,
        clip_mask: Option<&tiny_skia::ClipMask>,
    ) {
        if let Some(image) = self
            .cache
            .borrow_mut()
            .draw(handle, Size::new(bounds.width as u32, bounds.height as u32))
        {
            pixels.draw_pixmap(
                bounds.x as i32,
                bounds.y as i32,
                image,
                &tiny_skia::PixmapPaint::default(),
                tiny_skia::Transform::identity(),
                clip_mask,
            );
        }
    }

    pub fn trim_cache(&mut self) {
        self.cache.borrow_mut().trim();
    }
}

#[derive(Default)]
struct Cache {
    trees: FxHashMap<u64, Option<resvg::usvg::Tree>>,
    tree_hits: FxHashSet<u64>,
    rasters: FxHashMap<(u64, Size<u32>), tiny_skia::Pixmap>,
    raster_hits: FxHashSet<(u64, Size<u32>)>,
}

impl Cache {
    fn load(&mut self, handle: &Handle) -> Option<&usvg::Tree> {
        let id = handle.id();

        if let hash_map::Entry::Vacant(entry) = self.trees.entry(id) {
            let svg = match handle.data() {
                Data::Path(path) => {
                    fs::read_to_string(path).ok().and_then(|contents| {
                        usvg::Tree::from_str(
                            &contents,
                            &usvg::Options::default(),
                        )
                        .ok()
                    })
                }
                Data::Bytes(bytes) => {
                    usvg::Tree::from_data(bytes, &usvg::Options::default()).ok()
                }
            };

            entry.insert(svg);
        }

        self.tree_hits.insert(id);
        self.trees.get(&id).unwrap().as_ref()
    }

    fn viewport_dimensions(&mut self, handle: &Handle) -> Option<Size<u32>> {
        let tree = self.load(handle)?;

        Some(Size::new(
            tree.size.width() as u32,
            tree.size.height() as u32,
        ))
    }

    fn draw(
        &mut self,
        handle: &Handle,
        size: Size<u32>,
    ) -> Option<tiny_skia::PixmapRef<'_>> {
        if size.width == 0 || size.height == 0 {
            return None;
        }

        let id = handle.id();

        if !self.rasters.contains_key(&(id, size)) {
            let tree = self.load(handle)?;

            let mut image = tiny_skia::Pixmap::new(size.width, size.height)?;

            resvg::render(
                tree,
                if size.width > size.height {
                    usvg::FitTo::Width(size.width)
                } else {
                    usvg::FitTo::Height(size.height)
                },
                tiny_skia::Transform::default(),
                image.as_mut(),
            )?;

            // Swap R and B channels for `softbuffer` presentation
            for pixel in bytemuck::cast_slice_mut::<u8, u32>(image.data_mut()) {
                *pixel = *pixel & 0xFF00FF00
                    | ((0x000000FF & *pixel) << 16)
                    | ((0x00FF0000 & *pixel) >> 16);
            }

            self.rasters.insert((id, size), image);
        }

        self.raster_hits.insert((id, size));
        self.rasters.get(&(id, size)).map(tiny_skia::Pixmap::as_ref)
    }

    fn trim(&mut self) {
        self.trees.retain(|key, _| self.tree_hits.contains(key));
        self.rasters.retain(|key, _| self.raster_hits.contains(key));

        self.tree_hits.clear();
        self.raster_hits.clear();
    }
}