Skip to main content

oxidoc_print/
lib.rs

1//! # oxidoc-print
2//!
3//! PDF rendering pipeline for the Oxidoc documentation engine.
4//!
5//! Converts RDX AST to publication-quality PDFs via the oxipdf rendering engine.
6//! Supports configurable typography, system font discovery, and debug visualization.
7
8pub mod config;
9pub mod error;
10pub mod fonts;
11pub mod render_utils;
12pub mod renderer;
13pub mod styles;
14
15use config::PrintConfig;
16use error::Result;
17use oxipdf::ir::config::RenderConfig;
18use oxipdf::ir::page_template::{PageMargins, PageTemplate};
19
20/// Render an RDX document to PDF bytes.
21///
22/// This is the primary entry point for the print pipeline:
23/// 1. Converts the RDX AST to an oxipdf `StyledTree` (IR)
24/// 2. Configures page layout (margins, size)
25/// 3. Invokes oxipdf to produce PDF bytes
26pub fn render_to_pdf(root: &rdx_ast::Root, config: &PrintConfig) -> Result<Vec<u8>> {
27    // Step 1: Build the IR tree from the RDX AST
28    let tree = renderer::render_to_tree(root, config)?;
29
30    // Step 2: Configure the PDF render
31    let page_width = config.page.size.width();
32    let page_height = config.page.size.height();
33
34    let margins = PageMargins {
35        top: config.page.margin_top,
36        bottom: config.page.margin_bottom,
37        left: config.page.margin_inner,
38        right: config.page.margin_outer,
39    };
40
41    let render_config = RenderConfig {
42        page_width,
43        page_height,
44        page_template: Some(PageTemplate {
45            margins,
46            ..PageTemplate::default()
47        }),
48        hyphenation: oxipdf::ir::config::HyphenationConfig {
49            enabled: true,
50            min_word_length: 5,
51            min_left: 2,
52            min_right: 2,
53        },
54        ..RenderConfig::default()
55    };
56
57    // Step 3: Build font provider — load fonts matching configured families
58    let families = [
59        config.fonts.body.as_str(),
60        config.fonts.heading.as_str(),
61        config.fonts.mono.as_str(),
62    ];
63    let font_provider = fonts::build_font_provider_for_families(&config.fonts.font_dirs, &families);
64
65    // Step 4: Render to PDF
66    let pdf_bytes = match font_provider {
67        Some(provider) => {
68            tracing::info!(
69                families = ?provider.available_families().len(),
70                "Using shaped rendering with system fonts"
71            );
72            oxipdf::render_paginated_shaped_doc(&tree, &render_config, provider.as_ref())?
73        }
74        None => {
75            tracing::warn!("No fonts found — using hardcoded metrics (output quality reduced)");
76            oxipdf::render_paginated_doc(&tree, &render_config)?
77        }
78    };
79
80    Ok(pdf_bytes)
81}
82
83/// Parse an RDX file and apply the print transform pipeline, then render to PDF.
84///
85/// This is a convenience function that combines parsing, transforms, and rendering.
86pub fn render_file_to_pdf(rdx_source: &str, config: &PrintConfig) -> Result<Vec<u8>> {
87    use rdx_transform::Transform;
88
89    let mut root = rdx_parser::parse(rdx_source);
90
91    // Apply shared transforms
92    let auto_slug = rdx_transform::AutoSlug::new();
93    let auto_number = rdx_transform::AutoNumber::new();
94
95    auto_slug.transform(&mut root, rdx_source);
96    auto_number.transform(&mut root, rdx_source);
97
98    // Apply print-specific transforms
99    let strip = rdx_transform::StripTarget {
100        target: "print".into(),
101    };
102    let fallback = rdx_transform::PrintFallback;
103
104    strip.transform(&mut root, rdx_source);
105    fallback.transform(&mut root, rdx_source);
106
107    // Resolve cross-references using the number registry from AutoNumber
108    let registry = auto_number.registry();
109    let cross_ref = rdx_transform::CrossRefResolve::new(
110        rdx_transform::NumberRegistry {
111            entries: registry.entries.clone(),
112        },
113        "print",
114    );
115    drop(registry);
116    cross_ref.transform(&mut root, rdx_source);
117
118    // Apply abbreviation expansion
119    let abbrev = rdx_transform::AbbreviationExpand;
120    abbrev.transform(&mut root, rdx_source);
121
122    render_to_pdf(&root, config)
123}