fullbleed 2.5.0

Deterministic HTML/CSS-to-PDF engine in Rust for transactional document generation and AI agent workflows.
Documentation
use crate::Canvas;
use crate::doc_context::DocContext;
use crate::frame::Frame;
use crate::types::{PagePresentation, Rect, Size};
use std::sync::Arc;

#[derive(Clone, Copy)]
pub struct FrameSpec {
    pub rect: Rect,
}

pub type OnPageCallback = Arc<dyn Fn(&mut Canvas, &DocContext) + Send + Sync>;

#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum PageSelector {
    Sequence,
    Any,
    First,
    Left,
    Right,
    BlankLeft,
    BlankRight,
    NamedAny(u64),
    NamedFirst(u64),
    NamedLeft(u64),
    NamedRight(u64),
    NamedBlankLeft(u64),
    NamedBlankRight(u64),
}

#[derive(Clone)]
pub struct PageTemplate {
    pub name: String,
    pub page_size: Size,
    frames: Vec<FrameSpec>,
    on_page: Option<OnPageCallback>,
    on_page_finalize: Option<OnPageCallback>,
    page_counter_reset: Option<i32>,
    page_counter_increment: Option<i32>,
    page_presentation: PagePresentation,
    selector: PageSelector,
}

impl PageTemplate {
    pub fn new(name: impl Into<String>, page_size: Size) -> Self {
        Self {
            name: name.into(),
            page_size: page_size.quantized(),
            frames: Vec::new(),
            on_page: None,
            on_page_finalize: None,
            page_counter_reset: None,
            page_counter_increment: None,
            page_presentation: PagePresentation::default(),
            selector: PageSelector::Sequence,
        }
    }

    pub fn with_frame(mut self, rect: Rect) -> Self {
        self.frames.push(FrameSpec {
            rect: rect.quantized(),
        });
        self
    }

    pub fn set_on_page<F>(mut self, callback: F) -> Self
    where
        F: Fn(&mut Canvas, &DocContext) + Send + Sync + 'static,
    {
        self.on_page = Some(Arc::new(callback));
        self
    }

    pub(crate) fn append_on_page<F>(mut self, callback: F) -> Self
    where
        F: Fn(&mut Canvas, &DocContext) + Send + Sync + 'static,
    {
        let previous = self.on_page.take();
        self.on_page = Some(Arc::new(move |canvas, context| {
            if let Some(previous) = previous.as_ref() {
                previous(canvas, context);
            }
            callback(canvas, context);
        }));
        self
    }

    pub fn on_page(&self) -> Option<&OnPageCallback> {
        self.on_page.as_ref()
    }

    pub(crate) fn append_on_page_finalize<F>(mut self, callback: F) -> Self
    where
        F: Fn(&mut Canvas, &DocContext) + Send + Sync + 'static,
    {
        let previous = self.on_page_finalize.take();
        self.on_page_finalize = Some(Arc::new(move |canvas, context| {
            if let Some(previous) = previous.as_ref() {
                previous(canvas, context);
            }
            callback(canvas, context);
        }));
        self
    }

    pub(crate) fn on_page_finalize(&self) -> Option<&OnPageCallback> {
        self.on_page_finalize.as_ref()
    }

    pub(crate) fn with_page_counter(mut self, reset: Option<i32>, increment: Option<i32>) -> Self {
        self.page_counter_reset = reset;
        self.page_counter_increment = increment;
        self
    }

    pub(crate) fn page_counter_reset(&self) -> Option<i32> {
        self.page_counter_reset
    }

    pub(crate) fn page_counter_increment(&self) -> Option<i32> {
        self.page_counter_increment
    }

    pub(crate) fn with_page_presentation(mut self, presentation: PagePresentation) -> Self {
        self.page_presentation = presentation;
        self
    }

    pub(crate) fn page_presentation(&self) -> PagePresentation {
        self.page_presentation
    }

    pub(crate) fn with_page_selector(mut self, selector: PageSelector) -> Self {
        self.selector = selector;
        self
    }

    pub(crate) fn page_selector(&self) -> PageSelector {
        self.selector
    }

    pub(crate) fn primary_frame_rect(&self) -> Option<Rect> {
        self.frames.first().map(|frame| frame.rect)
    }

    pub fn instantiate_frames(&self) -> Vec<Frame> {
        self.frames
            .iter()
            .map(|spec| Frame::new(spec.rect))
            .collect()
    }
}