cursive-image 0.0.6

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

use {
    hayro::{
        hayro_interpret::*,
        hayro_syntax::{page::*, *},
        *,
    },
    std::{fs::*, io, path::*, sync::*},
};

impl Image {
    /// Constructor.
    ///
    /// Creates an image for every page in the PDF.
    ///
    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
    pub fn new_owned_from_pdf_file<PathT>(
        path: PathT,
        format: ImageFormat,
        scale: f64,
        compress: bool,
    ) -> io::Result<Vec<Self>>
    where
        PathT: AsRef<Path>,
    {
        let mut images = Vec::default();

        let pdf = read_pdf(path)?;
        for page in pdf.pages().into_iter() {
            let (width, height) = size(page, scale);
            let data = render_page(page, format, scale)?;
            let data = if compress { zlib(&data)? } else { data };
            images.push(Self::new_owned(data, compress, format, (width, height)));
        }

        Ok(images)
    }

    /// Constructor.
    ///
    /// Creates an image for every page in the PDF.
    ///
    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
    pub fn new_stream_from_pdf_file<PathT>(path: PathT, format: ImageFormat, scale: f64) -> io::Result<Vec<Self>>
    where
        PathT: AsRef<Path>,
    {
        let mut images = Vec::default();

        let path = path.as_ref();
        let pdf = read_pdf(path)?;
        for (index, page) in pdf.pages().into_iter().enumerate() {
            let (width, height) = size(page, scale);
            let stream = PdfPageImageStream::new(path.into(), index, format, scale);
            images.push(Self::new_stream(stream, format, (width, height)));
        }

        Ok(images)
    }
}

//
// PdfPageImageStream
//

struct PdfPageImageStream {
    path: PathBuf,
    index: usize,
    format: ImageFormat,
    scale: f64,
}

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

impl ImageStream for PdfPageImageStream {
    fn open(&self) -> io::Result<(Box<dyn io::Read>, bool)> {
        // We must load the entire file into memory to render it
        let pdf = read_pdf(&self.path)?;
        let page = pdf.pages().get(self.index).ok_or_else(|| io::Error::other("PDF missing page"))?;
        let data = render_page(page, self.format, self.scale)?;
        Ok((Box::new(io::Cursor::new(data)), false))
    }
}

// Utils

fn read_pdf<PathT>(path: PathT) -> io::Result<Pdf>
where
    PathT: AsRef<Path>,
{
    let data = read(path.as_ref())?;
    Ok(Pdf::new(Arc::new(data)).map_err(|_| io::Error::other("PDF parsing error"))?)
}

fn render_page(page: &Page, format: ImageFormat, scale: f64) -> io::Result<Vec<u8>> {
    let interpreter_settings = InterpreterSettings::default();

    let mut render_settings = RenderSettings::default();
    let scale = scale as f32;
    render_settings.x_scale = scale;
    render_settings.y_scale = scale;

    let pixmap = render(page, &interpreter_settings, &render_settings);

    Ok(match format {
        ImageFormat::PNG => pixmap.into_png()?,
        // TODO: how can we make this zero copy?
        ImageFormat::RGBA => bytemuck::cast_slice(&pixmap.take()).into(),
        _ => return Err(io::Error::other("unsupported format")),
    })
}

fn size(page: &Page, scale: f64) -> (usize, usize) {
    let scale = scale as f32;
    let (width, height) = page.render_dimensions();
    ((width * scale).round() as usize, (height * scale).round() as usize)
}