use std::ops::Range;
use std::path::Path;
use super::config::{ImageFormat, RenderConfig};
use super::error::Result;
#[derive(Debug, Clone)]
pub struct RenderedImage {
pub width: u32,
pub height: u32,
pub format: ImageFormat,
pub pixels: Vec<u8>,
pub page_index: usize,
}
impl RenderedImage {
#[must_use]
pub fn new(
width: u32,
height: u32,
format: ImageFormat,
pixels: Vec<u8>,
page_index: usize,
) -> Self {
Self {
width,
height,
format,
pixels,
page_index,
}
}
pub fn save(&self, path: &Path) -> Result<()> {
let dynamic = self.to_dynamic_image();
dynamic
.save(path)
.map_err(|e| super::RenderError::ImageEncode(e.to_string()))?;
Ok(())
}
#[must_use]
pub fn to_dynamic_image(&self) -> image::DynamicImage {
let img = image::RgbaImage::from_raw(self.width, self.height, self.pixels.clone())
.expect("pixel buffer size must match width * height * 4");
image::DynamicImage::ImageRgba8(img)
}
pub fn to_png_bytes(&self) -> Result<Vec<u8>> {
let dynamic = self.to_dynamic_image();
let mut buf = Vec::new();
dynamic
.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.map_err(|e| super::RenderError::ImageEncode(e.to_string()))?;
Ok(buf)
}
}
pub trait PdfRenderer: Send + Sync {
fn render_page(&self, page_index: usize, config: &RenderConfig) -> Result<RenderedImage>;
fn render_page_to_path(
&self,
page_index: usize,
config: &RenderConfig,
output: &Path,
) -> Result<()> {
self.render_page(page_index, config)?.save(output)
}
fn render_pages(
&self,
page_range: Range<usize>,
config: &RenderConfig,
) -> Result<Vec<RenderedImage>> {
page_range.map(|i| self.render_page(i, config)).collect()
}
fn name(&self) -> &'static str;
fn max_dpi(&self) -> u32 {
600
}
fn supports_vector(&self) -> bool {
false
}
}
#[cfg(test)]
#[allow(clippy::uninlined_format_args, clippy::float_cmp)]
mod tests {
use super::super::config::{ImageFormat, RenderConfig};
use super::*;
#[test]
fn rendered_image_new() {
let pixels = vec![255u8; 4 * 2 * 2]; let img = RenderedImage::new(2, 2, ImageFormat::Png, pixels.clone(), 0);
assert_eq!(img.width, 2);
assert_eq!(img.height, 2);
assert_eq!(img.pixels, pixels);
assert_eq!(img.page_index, 0);
}
#[test]
fn rendered_image_clone() {
let pixels = vec![0u8; 4];
let img = RenderedImage::new(1, 1, ImageFormat::Png, pixels, 5);
let cloned = img.clone();
assert_eq!(img.width, cloned.width);
assert_eq!(img.height, cloned.height);
assert_eq!(img.page_index, cloned.page_index);
}
#[test]
fn rendered_image_debug() {
let pixels = vec![0u8; 4];
let img = RenderedImage::new(1, 1, ImageFormat::Png, pixels, 0);
let dbg = format!("{:?}", img);
assert!(dbg.contains("RenderedImage"));
}
#[test]
fn rendered_image_to_dynamic_image() {
let pixels = vec![128u8; 4 * 2 * 2];
let img = RenderedImage::new(2, 2, ImageFormat::Png, pixels, 0);
let dynamic = img.to_dynamic_image();
assert_eq!(dynamic.width(), 2);
assert_eq!(dynamic.height(), 2);
}
#[test]
fn rendered_image_to_png_bytes() {
let pixels = vec![255u8; 4 * 2 * 2];
let img = RenderedImage::new(2, 2, ImageFormat::Png, pixels, 0);
let png = img.to_png_bytes().unwrap();
assert!(!png.is_empty());
assert_eq!(&png[..4], &[0x89, 0x50, 0x4E, 0x47]);
}
#[test]
fn rendered_image_save_and_load() {
let dir = std::env::temp_dir();
let path = dir.join("easypdf_render_test.png");
let pixels = vec![200u8; 4 * 3 * 3];
let img = RenderedImage::new(3, 3, ImageFormat::Png, pixels, 0);
img.save(&path).unwrap();
assert!(path.exists());
let _ = std::fs::remove_file(&path);
}
struct MockRenderer;
impl PdfRenderer for MockRenderer {
fn render_page(&self, page_index: usize, config: &RenderConfig) -> Result<RenderedImage> {
let pixels = vec![255u8; 4 * 10 * 10];
Ok(RenderedImage::new(
10,
10,
config.format,
pixels,
page_index,
))
}
fn name(&self) -> &'static str {
"mock"
}
}
#[test]
fn mock_renderer_render_page() {
let renderer = MockRenderer;
let config = RenderConfig::default();
let img = renderer.render_page(0, &config).unwrap();
assert_eq!(img.width, 10);
assert_eq!(img.page_index, 0);
}
#[test]
fn mock_renderer_name() {
let renderer = MockRenderer;
assert_eq!(renderer.name(), "mock");
}
#[test]
fn mock_renderer_max_dpi_default() {
let renderer = MockRenderer;
assert_eq!(renderer.max_dpi(), 600);
}
#[test]
fn mock_renderer_supports_vector_default() {
let renderer = MockRenderer;
assert!(!renderer.supports_vector());
}
#[test]
fn mock_renderer_render_pages() {
let renderer = MockRenderer;
let config = RenderConfig::default();
let images = renderer.render_pages(0..3, &config).unwrap();
assert_eq!(images.len(), 3);
assert_eq!(images[0].page_index, 0);
assert_eq!(images[2].page_index, 2);
}
#[test]
fn mock_renderer_render_page_to_path() {
let renderer = MockRenderer;
let config = RenderConfig::default();
let dir = std::env::temp_dir();
let path = dir.join("easypdf_render_page_to_path_test.png");
renderer.render_page_to_path(0, &config, &path).unwrap();
assert!(path.exists());
let _ = std::fs::remove_file(&path);
}
}