use alloc::sync::Arc;
use alloc::vec::Vec;
extern crate alloc;
use hayro::hayro_interpret::InterpreterSettings;
use hayro::hayro_syntax::Pdf;
use hayro::vello_cpu::color::AlphaColor;
use hayro::vello_cpu::color::Srgb;
use rgb::Rgba;
use zenpixels::PixelBuffer;
use crate::error::{PdfError, Result};
#[derive(Clone, Debug, Default)]
pub enum PageSelection {
#[default]
All,
Single(u32),
Range { start: u32, end: u32 },
Pages(Vec<u32>),
}
#[derive(Clone, Copy, Debug)]
pub enum RenderBounds {
Scale(f32),
Dpi(f32),
FitWidth(u32),
FitHeight(u32),
FitBox { width: u32, height: u32 },
Exact { width: u32, height: u32 },
}
impl Default for RenderBounds {
fn default() -> Self {
Self::Scale(1.0)
}
}
#[derive(Clone, Copy, Debug)]
pub struct RenderLimits {
pub max_pages: usize,
pub max_pixels_per_page: u64,
}
impl Default for RenderLimits {
fn default() -> Self {
Self {
max_pages: 1000,
max_pixels_per_page: 100_000_000,
}
}
}
impl RenderLimits {
#[must_use]
pub fn unlimited() -> Self {
Self {
max_pages: usize::MAX,
max_pixels_per_page: u64::MAX,
}
}
}
#[derive(Clone, Debug)]
pub struct PdfConfig {
pub pages: PageSelection,
pub bounds: RenderBounds,
pub background: [u8; 4],
pub render_annotations: bool,
pub limits: RenderLimits,
}
impl Default for PdfConfig {
fn default() -> Self {
Self {
pages: PageSelection::default(),
bounds: RenderBounds::default(),
background: [255, 255, 255, 255],
render_annotations: true,
limits: RenderLimits::default(),
}
}
}
pub struct RenderedPage {
pub index: u32,
pub buffer: PixelBuffer<Rgba<u8>>,
pub source_width_pt: f32,
pub source_height_pt: f32,
}
pub fn page_count(data: &[u8]) -> Result<u32> {
let pdf = open_pdf(data)?;
Ok(pdf.pages().len() as u32)
}
pub fn page_dimensions(data: &[u8], page_index: u32) -> Result<(f32, f32)> {
let pdf = open_pdf(data)?;
let pages = pdf.pages();
let count = pages.len() as u32;
if page_index >= count {
return Err(PdfError::PageOutOfRange {
index: page_index,
count,
});
}
Ok(pages[page_index as usize].render_dimensions())
}
pub fn render_page(data: &[u8], page_index: u32, bounds: &RenderBounds) -> Result<RenderedPage> {
let config = PdfConfig {
pages: PageSelection::Single(page_index),
bounds: *bounds,
..PdfConfig::default()
};
let mut pages = render_pages(data, &config)?;
Ok(pages.remove(0))
}
pub fn render_pages(data: &[u8], config: &PdfConfig) -> Result<Vec<RenderedPage>> {
let pdf = open_pdf(data)?;
render_pages_inner(&pdf, config)
}
pub(crate) fn render_pages_owned(data: Vec<u8>, config: &PdfConfig) -> Result<Vec<RenderedPage>> {
let pdf = open_pdf_owned(data)?;
render_pages_inner(&pdf, config)
}
fn render_pages_inner(pdf: &Pdf, config: &PdfConfig) -> Result<Vec<RenderedPage>> {
let pages = pdf.pages();
let count = pages.len() as u32;
let indices = resolve_page_indices(&config.pages, count)?;
if indices.len() > config.limits.max_pages {
return Err(PdfError::TooManyPages {
requested: indices.len(),
limit: config.limits.max_pages,
});
}
let interp = InterpreterSettings {
render_annotations: config.render_annotations,
..InterpreterSettings::default()
};
let bg = AlphaColor::<Srgb>::from_rgba8(
config.background[0],
config.background[1],
config.background[2],
config.background[3],
);
let mut results = Vec::with_capacity(indices.len());
for idx in indices {
let page = &pages[idx as usize];
let (page_w, page_h) = page.render_dimensions();
if !page_w.is_finite() || !page_h.is_finite() || page_w <= 0.0 || page_h <= 0.0 {
return Err(PdfError::ZeroDimensions { page: idx });
}
let settings = compute_render_settings(
&config.bounds,
page_w,
page_h,
idx,
bg,
config.limits.max_pixels_per_page,
)?;
let pixmap = hayro::render(page, &interp, &settings);
let w = pixmap.width() as u32;
let h = pixmap.height() as u32;
let buffer = pixmap_to_buffer(pixmap, w, h)?;
results.push(RenderedPage {
index: idx,
buffer,
source_width_pt: page_w,
source_height_pt: page_h,
});
}
Ok(results)
}
fn open_pdf(data: &[u8]) -> Result<Pdf> {
open_pdf_owned(data.to_vec())
}
pub(crate) fn open_pdf_owned(data: Vec<u8>) -> Result<Pdf> {
let arc_data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(data);
Pdf::new(arc_data).map_err(|e| PdfError::InvalidPdf(format!("{e:?}")))
}
fn resolve_page_indices(selection: &PageSelection, count: u32) -> Result<Vec<u32>> {
match selection {
PageSelection::All => Ok((0..count).collect()),
PageSelection::Single(i) => {
if *i >= count {
return Err(PdfError::PageOutOfRange { index: *i, count });
}
Ok(vec![*i])
}
PageSelection::Range { start, end } => {
if *start >= count {
return Err(PdfError::PageOutOfRange {
index: *start,
count,
});
}
if *end >= count {
return Err(PdfError::PageOutOfRange { index: *end, count });
}
if start > end {
return Ok(Vec::new());
}
Ok((*start..=*end).collect())
}
PageSelection::Pages(indices) => {
for &i in indices {
if i >= count {
return Err(PdfError::PageOutOfRange { index: i, count });
}
}
Ok(indices.clone())
}
}
}
fn compute_render_settings(
bounds: &RenderBounds,
page_w: f32,
page_h: f32,
page_idx: u32,
bg: AlphaColor<Srgb>,
max_pixels: u64,
) -> Result<hayro::RenderSettings> {
let (x_scale, y_scale, width, height) = match *bounds {
RenderBounds::Scale(s) => (s, s, None, None),
RenderBounds::Dpi(dpi) => {
let s = dpi / 72.0;
(s, s, None, None)
}
RenderBounds::FitWidth(target_w) => {
let s = target_w as f32 / page_w;
(s, s, None, None)
}
RenderBounds::FitHeight(target_h) => {
let s = target_h as f32 / page_h;
(s, s, None, None)
}
RenderBounds::FitBox { width, height } => {
let s = (width as f32 / page_w).min(height as f32 / page_h);
(s, s, None, None)
}
RenderBounds::Exact { width, height } => {
let sx = width as f32 / page_w;
let sy = height as f32 / page_h;
(sx, sy, Some(width), Some(height))
}
};
if !x_scale.is_finite() || !y_scale.is_finite() || x_scale <= 0.0 || y_scale <= 0.0 {
return Err(PdfError::ZeroDimensions { page: page_idx });
}
let raw_w = width.map_or_else(|| page_w * x_scale, |w| w as f32);
let raw_h = height.map_or_else(|| page_h * y_scale, |h| h as f32);
if !raw_w.is_finite() || !raw_h.is_finite() || raw_w < 1.0 || raw_h < 1.0 {
return Err(PdfError::ZeroDimensions { page: page_idx });
}
let final_w = raw_w.floor() as u32;
let final_h = raw_h.floor() as u32;
if final_w == 0 || final_h == 0 {
return Err(PdfError::ZeroDimensions { page: page_idx });
}
if final_w > u16::MAX as u32 || final_h > u16::MAX as u32 {
return Err(PdfError::DimensionOverflow {
width: final_w,
height: final_h,
});
}
let total_pixels = u64::from(final_w) * u64::from(final_h);
if total_pixels > max_pixels {
return Err(PdfError::PixelLimitExceeded {
pixels: total_pixels,
limit: max_pixels,
});
}
Ok(hayro::RenderSettings {
x_scale,
y_scale,
width: width.map(|w| w as u16),
height: height.map(|h| h as u16),
bg_color: bg,
})
}
fn pixmap_to_buffer(
pixmap: hayro::vello_cpu::Pixmap,
w: u32,
h: u32,
) -> Result<PixelBuffer<Rgba<u8>>> {
let pixels: Vec<Rgba<u8>> = pixmap
.take_unpremultiplied()
.into_iter()
.map(|p| Rgba {
r: p.r,
g: p.g,
b: p.b,
a: p.a,
})
.collect();
Ok(PixelBuffer::from_pixels(pixels, w, h)?)
}