graphitepdf 0.4.0

A Rust PDF rendering engine for layout, composition, and rendering pipelines.
Documentation
pub mod error {
    pub use graphitepdf_layout::error::*;
}

pub use error::*;
pub use graphitepdf_layout::{
    Document, EdgeInsets, LayoutContent, LayoutDocument, LayoutMetadata, LayoutNode, LayoutPage,
    LayoutPipelineStep, MathFragment, Node, NodeKind, ORDERED_PIPELINE, Page, SafeFont,
    SafeLayoutDocument, SafeLayoutNode, SafeLayoutPage, SafeLayoutStyle, SafeNodeKind,
};

use crate::document;
use graphitepdf_font::FontStore;
use graphitepdf_primitives::Size;
use graphitepdf_textkit::{TextBlock, TextEngine, TextEngineConfig};

pub trait LayoutDocumentSource {
    fn to_layout_document(&self) -> Document;
}

impl LayoutDocumentSource for document::Document {
    fn to_layout_document(&self) -> Document {
        Document::from(self)
    }
}

impl LayoutDocumentSource for Document {
    fn to_layout_document(&self) -> Document {
        self.clone()
    }
}

#[derive(Default)]
pub struct LayoutEngine {
    inner: graphitepdf_layout::LayoutEngine,
}

impl LayoutEngine {
    pub fn new() -> Self {
        Self {
            inner: graphitepdf_layout::LayoutEngine::new(),
        }
    }

    pub fn with_text_engine_config(mut self, config: TextEngineConfig) -> Self {
        self.inner = self.inner.with_text_engine_config(config);
        self
    }

    pub fn with_default_page_size(mut self, size: Size) -> Result<Self> {
        self.inner = self.inner.with_default_page_size(size)?;
        Ok(self)
    }

    pub fn text_engine(&self) -> &TextEngine {
        self.inner.text_engine()
    }

    pub fn font_store(&self) -> &FontStore {
        self.inner.font_store()
    }

    pub fn font_store_mut(&mut self) -> &mut FontStore {
        self.inner.font_store_mut()
    }

    pub fn layout_text_block(&self, page_size: Size, block: TextBlock) -> Result<LayoutDocument> {
        self.inner.layout_text_block(page_size, block)
    }

    pub fn layout_document<T>(&self, document: &T) -> Result<SafeLayoutDocument>
    where
        T: LayoutDocumentSource + ?Sized,
    {
        self.inner.layout_document(&document.to_layout_document())
    }

    pub fn as_core(&self) -> &graphitepdf_layout::LayoutEngine {
        &self.inner
    }

    pub fn into_core(self) -> graphitepdf_layout::LayoutEngine {
        self.inner
    }
}

impl From<graphitepdf_layout::LayoutEngine> for LayoutEngine {
    fn from(value: graphitepdf_layout::LayoutEngine) -> Self {
        Self { inner: value }
    }
}

impl From<LayoutEngine> for graphitepdf_layout::LayoutEngine {
    fn from(value: LayoutEngine) -> Self {
        value.inner
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::document::{
        Document as CompatDocument, ImageNode, Node as CompatNode, NodeKind, TextNode,
    };
    use crate::style::{Style, StyleValue, Stylesheet, StylesheetContainer};
    use graphitepdf_font::{FontStyle, FontWeight as FontVariantWeight};
    use graphitepdf_image::RemoteImageSource;

    #[test]
    fn layouts_compat_documents_through_split_layout_engine() {
        let container = StylesheetContainer::new(612.0, 792.0);
        let stylesheet = Stylesheet::new(StyleValue::Object(
            [
                ("fontFamily".to_string(), "Inter".into()),
                ("fontStyle".to_string(), "italic".into()),
                ("fontWeight".to_string(), 700.into()),
            ]
            .into_iter()
            .collect(),
        ));
        let page = CompatNode::new(
            NodeKind::View {
                children: vec![CompatNode::from_stylesheet(
                    NodeKind::Text(TextNode::new("Styled")),
                    &container,
                    &stylesheet,
                )],
            },
            Style::default(),
        );
        let document = CompatDocument::new().add_page(page);

        let layout = LayoutEngine::new()
            .layout_document(&document)
            .expect("document should layout");
        let descriptor = layout.pages()[0].nodes()[0]
            .font_descriptor()
            .expect("text node should expose font descriptor");

        assert_eq!(descriptor.family(), "Inter");
        assert_eq!(descriptor.font_style(), FontStyle::Italic);
        assert_eq!(descriptor.font_weight(), FontVariantWeight::BOLD);
    }

    #[test]
    fn keeps_image_sources_when_facading_the_split_layout_engine() {
        let page = CompatNode::new(
            NodeKind::View {
                children: vec![CompatNode::new(
                    NodeKind::Image(ImageNode::new(RemoteImageSource::new(
                        "https://example.com/image.png",
                    ))),
                    Style::default(),
                )],
            },
            Style::default(),
        );
        let document = CompatDocument::new().add_page(page);

        let layout = LayoutEngine::new()
            .layout_document(&document)
            .expect("document should layout");

        let node = &layout.pages()[0].nodes()[0];
        assert!(matches!(
            node.kind,
            SafeNodeKind::ImageSource { ref source }
                if *source == graphitepdf_image::ImageSource::from(RemoteImageSource::new(
                    "https://example.com/image.png",
                ))
        ));
    }
}