cursive-image 0.0.6

Image view for the Cursive TUI library
Documentation
use super::{
    super::{format::*, image::Image, stream::*},
    zlib::*,
};

use {
    resvg::*,
    std::{
        fs::*,
        io,
        path::{Path, *},
    },
    tiny_skia::*,
    usvg::*,
};

impl Image {
    /// Constructor.
    ///
    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
    pub fn new_owned_from_svg_file<PathT>(
        path: PathT,
        format: ImageFormat,
        scale: f64,
        compress: bool,
    ) -> io::Result<Self>
    where
        PathT: AsRef<Path>,
    {
        let tree = read_svg(path)?;
        let size = size(&tree, scale)?;
        let data = render_svg(tree, format, scale)?;
        let data = if compress { zlib(&data)? } else { data };
        Ok(Self::new_owned(data, compress, format, (size.width(), size.height())))
    }

    /// Constructor.
    ///
    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
    pub fn new_stream_from_svg_file<PathT>(path: PathT, format: ImageFormat, scale: f64) -> io::Result<Self>
    where
        PathT: AsRef<Path>,
    {
        let path = path.as_ref();
        let tree = read_svg(path)?;
        let size = size(&tree, scale)?;
        let stream = SvgImageStream::new(path.into(), format, scale);
        Ok(Self::new_stream(stream, format, (size.width(), size.height())))
    }
}

//
// SvgImageStream
//

struct SvgImageStream {
    path: PathBuf,
    format: ImageFormat,
    scale: f64,
}

impl SvgImageStream {
    fn new(path: PathBuf, format: ImageFormat, scale: f64) -> Self {
        Self { path, format, scale }
    }
}

impl ImageStream for SvgImageStream {
    fn open(&self) -> io::Result<(Box<dyn io::Read>, bool)> {
        // We must load the entire file into memory to render it
        let tree = read_svg(&self.path)?;
        let data = render_svg(tree, self.format, self.scale)?;
        Ok((Box::new(io::Cursor::new(data)), false))
    }
}

// Utils

fn read_svg<PathT>(path: PathT) -> io::Result<Tree>
where
    PathT: AsRef<Path>,
{
    let data = read(path.as_ref())?;

    let mut options = Options::default();
    options.fontdb_mut().load_system_fonts();

    // TODO: this is not safe...
    // if let Some(parent) = path.as_ref().parent() {
    //     options.resources_dir = Some(parent.into());
    // }

    Ok(Tree::from_data(&data, &options).map_err(io::Error::other)?)
}

fn render_svg(tree: Tree, format: ImageFormat, scale: f64) -> io::Result<Vec<u8>> {
    let size = size(&tree, scale)?;
    let mut pixmap =
        Pixmap::new(size.width(), size.height()).ok_or_else(|| io::Error::other("cannot create Pixmap"))?;
    let scale = scale as f32;
    render(&tree, Transform::from_scale(scale, scale), &mut pixmap.as_mut());

    Ok(match format {
        ImageFormat::PNG => pixmap.encode_png()?,
        ImageFormat::RGBA => pixmap.take(),
        _ => return Err(io::Error::other("unsupported format")),
    })
}

fn size(tree: &Tree, scale: f64) -> io::Result<IntSize> {
    Ok(tree.size().scale_by(scale as f32).ok_or_else(|| io::Error::other("scaling error"))?.to_int_size())
}