use pdfrum_common::Deadline;
use pdfrum_font::{FontId, GlyphCache};
use pdfrum_page::Transparency;
use crate::color::Argb;
use crate::options::RenderOptions;
pub const MAX_RECURSION_DEPTH: u32 = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Type3Frame {
pub fill: Argb,
pub colored: bool,
}
#[derive(Debug, Clone)]
pub struct RenderCtx<'a> {
pub opts: RenderOptions,
pub depth: u32,
pub initial_fill: Option<Argb>,
pub initial_stroke: Option<Argb>,
pub type3: Option<Type3Frame>,
pub type3_fonts: &'a [FontId],
pub transparency: Transparency,
pub in_group: bool,
pub deadline: Option<&'a Deadline>,
}
impl RenderCtx<'_> {
#[must_use]
pub fn new(opts: RenderOptions, transparency: Transparency) -> Self {
Self {
opts,
depth: 0,
initial_fill: None,
initial_stroke: None,
type3: None,
type3_fonts: &[],
transparency,
in_group: false,
deadline: None,
}
}
#[must_use]
pub fn out_of_time(&self) -> bool {
self.deadline.is_some_and(Deadline::passed)
}
#[must_use]
pub fn may_recurse(&self) -> bool {
self.depth < MAX_RECURSION_DEPTH
}
#[must_use]
pub fn deeper(&self) -> Self {
crate::walkprofile::alloc_items(
crate::walkprofile::Site::CtxClone,
1,
core::mem::size_of::<Self>(),
);
Self {
depth: self.depth.saturating_add(1),
..self.clone()
}
}
#[must_use]
pub fn type3_font_is_active(&self, font: FontId) -> bool {
self.type3_fonts.contains(&font)
}
}
#[derive(Debug, Default)]
pub struct RenderCaches {
pub(crate) glyphs: GlyphCache,
pub(crate) glyph_bitmaps: crate::glyph::BitmapCache,
pub(crate) images: crate::imagecache::RenderedImageCache,
pub(crate) zero_area: crate::zero_area::Scratch,
pub(crate) placed_glyphs: Vec<crate::text::PlacedGlyph>,
pub(crate) glyph_blit: GlyphBlitScratch,
}
#[derive(Debug)]
pub(crate) struct GlyphBlitScratch {
pub(crate) coverage: Vec<u8>,
pub(crate) pixels: crate::Pixmap,
}
impl Default for GlyphBlitScratch {
fn default() -> Self {
Self {
coverage: Vec::new(),
pixels: crate::Pixmap::new(0, 0),
}
}
}
impl RenderCaches {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[doc(hidden)]
#[must_use]
pub fn glyph_buffer_capacity(&self) -> usize {
self.placed_glyphs.capacity()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn depth_cap_is_64() {
let mut ctx = RenderCtx::new(RenderOptions::default(), Transparency::default());
for _ in 0..MAX_RECURSION_DEPTH {
assert!(ctx.may_recurse());
ctx = ctx.deeper();
}
assert!(!ctx.may_recurse(), "the 65th level is refused");
}
#[test]
fn deeper_keeps_everything_but_the_depth() {
let ctx = RenderCtx {
initial_fill: Some(Argb::opaque(1, 2, 3)),
in_group: true,
..RenderCtx::new(RenderOptions::default(), Transparency::default())
};
let child = ctx.deeper();
assert_eq!(child.initial_fill, ctx.initial_fill);
assert!(child.in_group);
assert_eq!(child.depth, 1);
}
#[test]
fn the_type3_guard_is_a_set_not_a_depth() {
let fonts = [FontId(7), FontId(9)];
let ctx = RenderCtx {
type3_fonts: &fonts,
..RenderCtx::new(RenderOptions::default(), Transparency::default())
};
assert!(ctx.type3_font_is_active(FontId(7)));
assert!(ctx.type3_font_is_active(FontId(9)));
assert!(!ctx.type3_font_is_active(FontId(8)));
}
}