graphitepdf-renderer 0.4.0

Minimal renderer backend abstraction for GraphitePDF.
Documentation
mod support;

use std::io::Error as IoError;

use graphitepdf_font::{FontSource, StandardFont};
use graphitepdf_layout::{
    Document, EdgeInsets, LayoutEngine, LayoutMetadata, LayoutStyle, Node, Page,
};
use graphitepdf_primitives::{Color, Pt, Size};
use graphitepdf_renderer::{RenderCommand, RenderEngine, RendererSession};
use graphitepdf_textkit::{TextBlock, TextSpan};
use support::output_path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let document = sample_document()?;

    let layout = LayoutEngine::new().layout_document(&document)?;
    let rendered = RenderEngine::new().build(&layout)?;
    let command_count: usize = rendered.pages.iter().map(|page| page.commands.len()).sum();
    let text_count = rendered
        .pages
        .iter()
        .flat_map(|page| page.commands.iter())
        .filter(|command| matches!(command, RenderCommand::DrawText(_)))
        .count();

    let path = output_path("pipeline")?;
    let mut session = RendererSession::new(document);
    let snapshot = session.render_snapshot()?;
    if snapshot.document().pages.len() != rendered.pages.len() {
        return Err(IoError::other(
            "renderer session page count diverged from explicit render build",
        )
        .into());
    }

    session.save(&path)?;

    println!(
        "Rendered layout -> render -> renderer pipeline: {} page(s), {} command(s), {} text command(s).",
        layout.pages().len(),
        command_count,
        text_count
    );
    println!("Saved PDF to {}", path.display());
    println!(
        "Set GRAPHITEPDF_OUTPUT for an exact file path or GRAPHITEPDF_OUTPUT_DIR to override the default .artifacts destination."
    );

    Ok(())
}

fn sample_document() -> Result<Document, Box<dyn std::error::Error>> {
    let title = TextBlock::from(TextSpan::new("GraphitePDF split pipeline")?);
    let body = TextBlock::from(TextSpan::new(
        "This example lays out a document, builds render commands, and writes the final PDF through the renderer session.",
    )?);

    Ok(Document::new()
        .with_metadata(LayoutMetadata {
            title: Some(String::from("GraphitePDF Pipeline Example")),
            author: Some(String::from("graphitepdf")),
            subject: Some(String::from("layout -> render -> renderer example")),
            keywords: vec![
                String::from("graphitepdf"),
                String::from("layout"),
                String::from("render"),
                String::from("renderer"),
                String::from("example"),
            ],
            creator: Some(String::from("graphitepdf-renderer example")),
            producer: Some(String::from("graphitepdf")),
        })
        .with_page(
            Page::new([Node::view([
                Node::box_node().with_style(
                    LayoutStyle::new()
                        .with_width(Pt::new(452.0))
                        .with_height(Pt::new(92.0))
                        .with_background_color(Color::rgb(0xe0, 0xec, 0xff)),
                ),
                Node::text(title).with_style(
                    LayoutStyle::new()
                        .with_font_family("Helvetica")
                        .with_font_source(FontSource::standard(StandardFont::HelveticaBold))
                        .with_font_size(Pt::new(20.0))
                        .with_line_height(Pt::new(24.0))
                        .with_color(Color::rgb(0x17, 0x2b, 0x4d)),
                ),
                Node::text(body).with_style(
                    LayoutStyle::new()
                        .with_font_family("Helvetica")
                        .with_font_source(FontSource::standard(StandardFont::Helvetica))
                        .with_font_size(Pt::new(12.0))
                        .with_line_height(Pt::new(16.0))
                        .with_color(Color::rgb(0x2d, 0x37, 0x48)),
                ),
            ])
            .with_style(
                LayoutStyle::new()
                    .with_padding(EdgeInsets::all(Pt::new(20.0)))
                    .with_background_color(Color::rgb(0xf8, 0xfa, 0xfc)),
            )])
            .with_size(Size::new(512.0, 256.0))
            .with_style(
                LayoutStyle::new()
                    .with_padding(EdgeInsets::all(Pt::new(28.0)))
                    .with_background_color(Color::rgb(0xff, 0xff, 0xff)),
            ),
        ))
}