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 enum Type3Colour {
Imposed,
Declared,
}
impl Type3Colour {
#[must_use]
pub fn from_declared(declared: bool) -> Self {
if declared {
Self::Declared
} else {
Self::Imposed
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Type3Frame {
pub fill: Argb,
pub colour: Type3Colour,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Stop<'a> {
pub deadline: Option<&'a Deadline>,
pub cancel: Option<&'a Deadline>,
}
impl Stop<'_> {
#[must_use]
pub fn passed(&self) -> bool {
self.deadline.is_some_and(Deadline::passed) || self.cancel.is_some_and(Deadline::passed)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RunCtx<'a> {
pub stop: Stop<'a>,
pub cull_margin: f64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Depth(pub u32);
impl Depth {
#[must_use]
pub fn may_recurse(self) -> bool {
self.0 < MAX_RECURSION_DEPTH
}
#[must_use]
pub fn deeper(self) -> Self {
Self(self.0.saturating_add(1))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Inherited {
pub fill: Option<Argb>,
pub stroke: Option<Argb>,
}
impl Inherited {
pub const NONE: Self = Self {
fill: None,
stroke: None,
};
#[must_use]
pub fn both(colour: Argb) -> Self {
Self {
fill: Some(colour),
stroke: Some(colour),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Type3Ancestry<'a> {
pub frame: Option<Type3Frame>,
pub fonts: &'a [FontId],
}
impl Type3Ancestry<'_> {
#[must_use]
pub fn is_active(&self, font: FontId) -> bool {
self.fonts.contains(&font)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupNesting {
Outside,
Inside,
}
#[derive(Debug, Clone, Copy)]
pub struct Nesting {
pub transparency: Transparency,
pub group: GroupNesting,
}
impl Nesting {
#[must_use]
pub fn in_group(transparency: Transparency) -> Self {
Self {
transparency,
group: GroupNesting::Inside,
}
}
}
#[derive(Debug, Clone)]
pub struct RenderCtx<'a> {
pub run: &'a RunCtx<'a>,
pub opts: RenderOptions,
pub depth: Depth,
pub inherited: Inherited,
pub type3: Type3Ancestry<'a>,
pub nesting: Nesting,
}
impl<'a> RenderCtx<'a> {
#[must_use]
pub fn new(run: &'a RunCtx<'a>, opts: RenderOptions, transparency: Transparency) -> Self {
Self {
run,
opts,
depth: Depth::default(),
inherited: Inherited::NONE,
type3: Type3Ancestry::default(),
nesting: Nesting {
transparency,
group: GroupNesting::Outside,
},
}
}
#[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.deeper(),
..self.clone()
}
}
}
#[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,
pub(crate) type3_blues: crate::type3::BlueCache,
}
#[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 run = RunCtx::default();
let mut ctx = RenderCtx::new(&run, RenderOptions::default(), Transparency::default());
for _ in 0..MAX_RECURSION_DEPTH {
assert!(ctx.depth.may_recurse());
ctx = ctx.deeper();
}
assert!(!ctx.depth.may_recurse(), "the 65th level is refused");
}
#[test]
fn deeper_keeps_everything_but_the_depth() {
let run = RunCtx::default();
let ctx = RenderCtx {
inherited: Inherited::both(Argb::opaque(1, 2, 3)),
nesting: Nesting::in_group(Transparency::default()),
..RenderCtx::new(&run, RenderOptions::default(), Transparency::default())
};
let child = ctx.deeper();
assert_eq!(child.inherited, ctx.inherited);
assert_eq!(child.nesting.group, GroupNesting::Inside);
assert_eq!(child.depth, Depth(1));
}
#[test]
fn the_type3_guard_is_a_set_not_a_depth() {
let run = RunCtx::default();
let fonts = [FontId(7), FontId(9)];
let ctx = RenderCtx {
type3: Type3Ancestry {
fonts: &fonts,
..Type3Ancestry::default()
},
..RenderCtx::new(&run, RenderOptions::default(), Transparency::default())
};
assert!(ctx.type3.is_active(FontId(7)));
assert!(ctx.type3.is_active(FontId(9)));
assert!(!ctx.type3.is_active(FontId(8)));
}
}