use std::sync::Arc;
use parking_lot::Mutex;
use crate::Result;
use crate::engine::structured::rasterize::{PageImage, RasterizeError, render_all_pages};
const PDF_MIME: &str = "application/pdf";
pub struct ParsedDocument {
mime_type: String,
page_count: u32,
source: Arc<[u8]>,
rendered: Mutex<Option<(u32, Vec<PageImage>)>>,
}
impl ParsedDocument {
pub fn from_bytes(source: Arc<[u8]>) -> Result<Self> {
let mime_type = crate::core::mime::detect_mime_type_from_bytes(&source)?;
let page_count = if mime_type.eq_ignore_ascii_case(PDF_MIME) {
crate::pdf::render::pdf_page_count(&source, None)? as u32
} else {
1
};
Ok(Self {
mime_type,
page_count,
source,
rendered: Mutex::new(None),
})
}
pub fn mime_type(&self) -> &str {
&self.mime_type
}
pub fn page_count(&self) -> u32 {
self.page_count
}
pub fn source(&self) -> &Arc<[u8]> {
&self.source
}
pub fn rendered_pages(&self, dpi: u32) -> std::result::Result<Vec<PageImage>, RasterizeError> {
let mut guard = self.rendered.lock();
if let Some((cached_dpi, pages)) = guard.as_ref()
&& *cached_dpi == dpi
{
return Ok(pages.clone());
}
let pages = render_all_pages(&self.source, &self.mime_type, dpi)?;
*guard = Some((dpi, pages.clone()));
Ok(pages)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
const DPI: u32 = 150;
fn one_pixel_png() -> Vec<u8> {
let img = image::RgbImage::new(1, 1);
let mut out = Vec::new();
image::DynamicImage::ImageRgb8(img)
.write_to(&mut Cursor::new(&mut out), image::ImageFormat::Png)
.expect("encode 1x1 png");
out
}
#[test]
fn from_bytes_detects_image_and_counts_one_page() {
let png = one_pixel_png();
let parsed = ParsedDocument::from_bytes(Arc::from(png.as_slice())).expect("parse memo builds");
assert!(
parsed.mime_type().starts_with("image/"),
"detected an image MIME, got {}",
parsed.mime_type()
);
assert_eq!(parsed.page_count(), 1, "an image is a single page");
assert_eq!(
parsed.source().as_ref(),
png.as_slice(),
"memo retains the shared source bytes"
);
}
#[test]
fn rendered_pages_is_memoized_and_matches_direct_render() {
let png = one_pixel_png();
let parsed = ParsedDocument::from_bytes(Arc::from(png.as_slice())).expect("parse memo builds");
let first = parsed.rendered_pages(DPI).expect("first render succeeds");
let second = parsed.rendered_pages(DPI).expect("second render hits the memo");
assert_eq!(first.len(), 1, "one page rendered");
assert_eq!(first[0].page_number, 1, "1-based page numbering");
assert_eq!(
first[0].png_bytes, second[0].png_bytes,
"second call returns the memoized PNG"
);
let direct = render_all_pages(&png, "image/png", DPI).expect("direct rasterize succeeds");
assert_eq!(direct.len(), 1, "direct render yields one page");
assert_eq!(
first[0].png_bytes, direct[0].png_bytes,
"memo render is identical to a direct rasterize"
);
}
}