Skip to main content

cursive_image/image/compatibility/
pdf.rs

1use super::{
2    super::{format::*, image::Image, stream::*},
3    zlib::*,
4};
5
6use {
7    hayro::{
8        hayro_interpret::*,
9        hayro_syntax::{page::*, *},
10        *,
11    },
12    std::{fs::*, io, path::*, sync::*},
13};
14
15impl Image {
16    /// Constructor.
17    ///
18    /// Creates an image for every page in the PDF.
19    ///
20    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
21    pub fn new_owned_from_pdf_file<PathT>(
22        path: PathT,
23        format: ImageFormat,
24        scale: f64,
25        compress: bool,
26    ) -> io::Result<Vec<Self>>
27    where
28        PathT: AsRef<Path>,
29    {
30        let mut images = Vec::default();
31
32        let pdf = read_pdf(path)?;
33        for page in pdf.pages().into_iter() {
34            let (width, height) = size(page, scale);
35            let data = render_page(page, format, scale)?;
36            let data = if compress { zlib(&data)? } else { data };
37            images.push(Self::new_owned(data, compress, format, (width, height)));
38        }
39
40        Ok(images)
41    }
42
43    /// Constructor.
44    ///
45    /// Creates an image for every page in the PDF.
46    ///
47    /// The implementation supports both [PNG](ImageFormat::PNG) and [RGBA](ImageFormat::RGBA).
48    pub fn new_stream_from_pdf_file<PathT>(path: PathT, format: ImageFormat, scale: f64) -> io::Result<Vec<Self>>
49    where
50        PathT: AsRef<Path>,
51    {
52        let mut images = Vec::default();
53
54        let path = path.as_ref();
55        let pdf = read_pdf(path)?;
56        for (index, page) in pdf.pages().into_iter().enumerate() {
57            let (width, height) = size(page, scale);
58            let stream = PdfPageImageStream::new(path.into(), index, format, scale);
59            images.push(Self::new_stream(stream, format, (width, height)));
60        }
61
62        Ok(images)
63    }
64}
65
66//
67// PdfPageImageStream
68//
69
70struct PdfPageImageStream {
71    path: PathBuf,
72    index: usize,
73    format: ImageFormat,
74    scale: f64,
75}
76
77impl PdfPageImageStream {
78    fn new(path: PathBuf, index: usize, format: ImageFormat, scale: f64) -> Self {
79        Self { path, index, format, scale }
80    }
81}
82
83impl ImageStream for PdfPageImageStream {
84    fn open(&self) -> io::Result<(Box<dyn io::Read>, bool)> {
85        // We must load the entire file into memory to render it
86        let pdf = read_pdf(&self.path)?;
87        let page = pdf.pages().get(self.index).ok_or_else(|| io::Error::other("PDF missing page"))?;
88        let data = render_page(page, self.format, self.scale)?;
89        Ok((Box::new(io::Cursor::new(data)), false))
90    }
91}
92
93// Utils
94
95fn read_pdf<PathT>(path: PathT) -> io::Result<Pdf>
96where
97    PathT: AsRef<Path>,
98{
99    let data = read(path.as_ref())?;
100    Ok(Pdf::new(Arc::new(data)).map_err(|_| io::Error::other("PDF parsing error"))?)
101}
102
103fn render_page(page: &Page, format: ImageFormat, scale: f64) -> io::Result<Vec<u8>> {
104    let interpreter_settings = InterpreterSettings::default();
105
106    let mut render_settings = RenderSettings::default();
107    let scale = scale as f32;
108    render_settings.x_scale = scale;
109    render_settings.y_scale = scale;
110
111    let pixmap = render(page, &interpreter_settings, &render_settings);
112
113    Ok(match format {
114        ImageFormat::PNG => pixmap.into_png()?,
115        // TODO: how can we make this zero copy?
116        ImageFormat::RGBA => bytemuck::cast_slice(&pixmap.take()).into(),
117        _ => return Err(io::Error::other("unsupported format")),
118    })
119}
120
121fn size(page: &Page, scale: f64) -> (usize, usize) {
122    let scale = scale as f32;
123    let (width, height) = page.render_dimensions();
124    ((width * scale).round() as usize, (height * scale).round() as usize)
125}