#![forbid(unsafe_code)]
#![deny(missing_docs)]
use crate::renderer::Renderer;
use hayro_interpret::Device;
use hayro_interpret::FillRule;
use hayro_interpret::InterpreterCache;
use hayro_interpret::InterpreterSettings;
use hayro_interpret::hayro_syntax::Pdf;
use hayro_interpret::hayro_syntax::page::Page;
use hayro_interpret::util::{RectExt, TransformExt};
use hayro_interpret::{BlendMode, Context};
use hayro_interpret::{ClipPath, interpret_page};
use kurbo::{Affine, Rect, Shape};
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::RangeInclusive;
use std::rc::Rc;
pub use hayro_interpret;
pub use hayro_interpret::hayro_syntax;
pub use vello_cpu;
use vello_cpu::color::AlphaColor;
use vello_cpu::color::Srgb;
use vello_cpu::color::palette::css::TRANSPARENT;
use vello_cpu::color::palette::css::WHITE;
use vello_cpu::{Level, Pixmap, RenderMode};
mod renderer;
#[derive(Clone, Default)]
pub struct RenderCache<'a> {
pub(crate) interpreter_cache: InterpreterCache<'a>,
pub(crate) outline_cache: Rc<RefCell<HashMap<u128, Rc<kurbo::BezPath>>>>,
}
impl<'a> RenderCache<'a> {
pub fn new() -> Self {
Self {
interpreter_cache: InterpreterCache::new(),
outline_cache: Rc::new(RefCell::new(HashMap::new())),
}
}
}
#[derive(Clone, Copy)]
pub struct RenderSettings {
pub x_scale: f32,
pub y_scale: f32,
pub width: Option<u16>,
pub height: Option<u16>,
pub bg_color: AlphaColor<Srgb>,
}
impl Default for RenderSettings {
fn default() -> Self {
Self {
x_scale: 1.0,
y_scale: 1.0,
width: None,
height: None,
bg_color: TRANSPARENT,
}
}
}
pub fn render<'a>(
page: &'a Page<'a>,
cache: &RenderCache<'a>,
interpreter_settings: &InterpreterSettings,
render_settings: &RenderSettings,
) -> Pixmap {
let (x_scale, y_scale) = (render_settings.x_scale, render_settings.y_scale);
let (width, height) = page.render_dimensions();
let (scaled_width, scaled_height) = ((width * x_scale) as f64, (height * y_scale) as f64);
let initial_transform = Affine::scale_non_uniform(x_scale as f64, y_scale as f64)
* page.initial_transform(true).to_kurbo();
let (pix_width, pix_height) = (
render_settings.width.unwrap_or(scaled_width.floor() as u16),
render_settings
.height
.unwrap_or(scaled_height.floor() as u16),
);
let mut state = Context::new(
initial_transform,
Rect::new(0.0, 0.0, pix_width as f64, pix_height as f64),
&cache.interpreter_cache,
page.xref(),
interpreter_settings.clone(),
);
let vc_settings = vello_cpu::RenderSettings {
level: Level::new(),
num_threads: 0,
render_mode: RenderMode::OptimizeSpeed,
};
let mut device = Renderer::new(pix_width, pix_height, vc_settings, cache);
device.ctx.set_paint(render_settings.bg_color);
device
.ctx
.fill_rect(&Rect::new(0.0, 0.0, pix_width as f64, pix_height as f64));
let mut clip_path = page.intersected_crop_box().to_kurbo().to_path(0.1);
clip_path.apply_affine(initial_transform);
device.push_clip_path(&ClipPath {
path: clip_path,
fill: FillRule::NonZero,
});
device.push_transparency_group(1.0, None, BlendMode::Normal);
interpret_page(page, &mut state, &mut device);
device.pop_transparency_group();
device.pop_clip_path();
let mut pixmap = Pixmap::new(pix_width, pix_height);
device.ctx.render_to_pixmap(&mut pixmap);
pixmap
}
#[doc(hidden)]
pub fn render_pdf(
pdf: &Pdf,
scale: f32,
settings: InterpreterSettings,
range: Option<RangeInclusive<usize>>,
) -> Option<Vec<Pixmap>> {
let cache = RenderCache::new();
let rendered = pdf
.pages()
.iter()
.enumerate()
.flat_map(|(idx, page)| {
if range.clone().is_some_and(|range| !range.contains(&idx)) {
return None;
}
let pixmap = render(
page,
&cache,
&settings,
&RenderSettings {
x_scale: scale,
y_scale: scale,
bg_color: WHITE,
..Default::default()
},
);
Some(pixmap)
})
.collect();
Some(rendered)
}
pub(crate) fn derive_settings(settings: &vello_cpu::RenderSettings) -> vello_cpu::RenderSettings {
vello_cpu::RenderSettings {
num_threads: 0,
..*settings
}
}