use std::path::Path;
use anyhow::Result;
use image::DynamicImage;
use crate::error::AtomwriteError;
pub(crate) fn render_page(
path: &Path,
page: u32,
dpi: f32,
max_pixels: u64,
) -> Result<DynamicImage> {
let bytes = std::fs::read(path).map_err(|source| AtomwriteError::Io { source })?;
let pdf = hayro_syntax::Pdf::new(bytes).map_err(|error| AtomwriteError::InvalidInput {
reason: format!("read: {} is not a readable PDF: {error:?}", path.display()),
})?;
let pages = pdf.pages();
let index = page.saturating_sub(1) as usize;
let Some(selected) = pages.get(index) else {
return Err(AtomwriteError::InvalidInput {
reason: format!(
"read: {} has {} page(s), so --page {page} does not exist",
path.display(),
pages.len()
),
}
.into());
};
let (base_width, base_height) = selected.render_dimensions();
let scale = (dpi / 72.0).max(0.01);
let width = (base_width * scale).round().max(1.0);
let height = (base_height * scale).round().max(1.0);
let pixels = u64::from(width as u32).saturating_mul(u64::from(height as u32));
if pixels > max_pixels {
return Err(AtomwriteError::InvalidInput {
reason: format!(
"read: page {page} of {} would render at {width} x {height} pixels, \
above the {max_pixels} pixel ceiling; lower --dpi",
path.display()
),
}
.into());
}
let pixmap = hayro::render(
selected,
&hayro::RenderCache::new(),
&hayro_interpret::InterpreterSettings::default(),
&hayro::RenderSettings::default(),
&hayro::PixmapSettings {
x_scale: scale,
y_scale: scale,
..hayro::PixmapSettings::default()
},
);
let (width, height) = (u32::from(pixmap.width()), u32::from(pixmap.height()));
let mut rgb = Vec::with_capacity(pixmap.data_as_u8_slice().len() / 4 * 3);
composite_over_white(pixmap.data_as_u8_slice(), &mut rgb);
let buffer = image::RgbImage::from_raw(width, height, rgb).ok_or_else(|| {
AtomwriteError::InternalError {
reason: format!(
"read: rendered page {page} of {} did not produce {width} x {height} pixels",
path.display()
),
}
})?;
Ok(DynamicImage::ImageRgb8(buffer))
}
fn composite_over_white(rgba: &[u8], rgb: &mut Vec<u8>) {
rgb.reserve(rgba.len() / 4 * 3);
for pixel in rgba.chunks_exact(4) {
let rest = 255 - u32::from(pixel[3]);
for channel in &pixel[..3] {
let mixed = u32::from(*channel) + rest;
rgb.push(mixed.min(255) as u8);
}
}
}