Skip to main content

oxml_pdf/
lib.rs

1//! PDF renderer for format-neutral OOXML layout output.
2//!
3//! Converts `LayoutResult` (positioned page frames with glyph runs, lines,
4//! rectangles, and images) into a PDF document.
5
6mod font;
7mod image;
8pub mod raster;
9mod writer;
10
11use oxml_layout::LayoutResult;
12
13/// Render a laid-out document to PDF bytes.
14///
15/// The `LayoutResult` must contain all pages, fonts, metadata, and outlines
16/// produced by a format-specific layout engine.
17pub fn render_to_pdf(layout: &LayoutResult) -> Vec<u8> {
18    writer::write_pdf(layout)
19}
20
21/// Render a single page to PNG bytes.
22///
23/// # Arguments
24/// * `layout` - The format-neutral layout result to rasterise
25/// * `page_index` - 0-based page index
26/// * `dpi` - Resolution (72 = 1:1, 150 = standard, 300 = high quality)
27pub fn render_page_to_png(layout: &LayoutResult, page_index: usize, dpi: f64) -> Option<Vec<u8>> {
28    raster::render_page_to_png(layout, page_index, dpi)
29}
30
31/// Render all pages to PNG bytes.
32pub fn render_all_pages(layout: &LayoutResult, dpi: f64) -> Vec<Vec<u8>> {
33    raster::render_all_pages(layout, dpi)
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39    use oxml_layout::{
40        Color, DocumentMetadata, LayoutResult, OutlineEntry, PageFrame, Point, PositionedElement,
41        Rect,
42    };
43
44    fn layout_with(elements: Vec<PositionedElement>) -> LayoutResult {
45        LayoutResult::new(
46            vec![PageFrame::new(1, 612.0, 792.0, elements)],
47            vec![],
48            None,
49            vec![],
50        )
51    }
52
53    #[test]
54    fn render_empty_layout() {
55        let pdf = render_to_pdf(&layout_with(vec![]));
56        assert!(pdf.starts_with(b"%PDF"));
57        assert!(String::from_utf8_lossy(&pdf).contains("%%EOF"));
58    }
59
60    #[test]
61    fn render_with_lines_and_rects() {
62        let elements = vec![
63            PositionedElement::Line {
64                start: Point { x: 72.0, y: 72.0 },
65                end: Point { x: 540.0, y: 72.0 },
66                width: 1.0,
67                color: Color::BLACK,
68                dash_pattern: None,
69            },
70            PositionedElement::FilledRect {
71                rect: Rect {
72                    x: 72.0,
73                    y: 100.0,
74                    width: 468.0,
75                    height: 20.0,
76                },
77                color: Color {
78                    r: 0.9,
79                    g: 0.9,
80                    b: 0.9,
81                    a: 1.0,
82                },
83            },
84        ];
85        let pdf = render_to_pdf(&layout_with(elements));
86        assert!(pdf.starts_with(b"%PDF"));
87        assert!(pdf.len() > 100);
88    }
89
90    #[test]
91    fn render_with_metadata() {
92        let layout = LayoutResult::new(
93            vec![PageFrame::new(1, 612.0, 792.0, vec![])],
94            vec![],
95            Some(DocumentMetadata {
96                title: Some("Test Title".to_owned()),
97                author: Some("Test Author".to_owned()),
98                subject: None,
99                keywords: None,
100                creator: Some("oxml-pdf".to_owned()),
101            }),
102            vec![],
103        );
104        let pdf = render_to_pdf(&layout);
105        let pdf = String::from_utf8_lossy(&pdf);
106        assert!(pdf.contains("Test Title"));
107        assert!(pdf.contains("Test Author"));
108    }
109
110    #[test]
111    fn render_with_link_annotation() {
112        let layout = layout_with(vec![PositionedElement::LinkAnnotation {
113            rect: Rect {
114                x: 72.0,
115                y: 100.0,
116                width: 100.0,
117                height: 15.0,
118            },
119            url: "https://example.com".to_owned(),
120        }]);
121        let pdf = render_to_pdf(&layout);
122        let pdf = String::from_utf8_lossy(&pdf);
123        assert!(pdf.contains("example.com"));
124    }
125
126    #[test]
127    fn render_with_outlines() {
128        let layout = LayoutResult::new(
129            vec![PageFrame::new(1, 612.0, 792.0, vec![])],
130            vec![],
131            None,
132            vec![
133                OutlineEntry {
134                    title: "Chapter 1".to_owned(),
135                    level: 1,
136                    page_index: 0,
137                    y_position: 72.0,
138                },
139                OutlineEntry {
140                    title: "Section 1.1".to_owned(),
141                    level: 2,
142                    page_index: 0,
143                    y_position: 200.0,
144                },
145            ],
146        );
147        let pdf = render_to_pdf(&layout);
148        let pdf = String::from_utf8_lossy(&pdf);
149        assert!(pdf.contains("Chapter 1"));
150        assert!(pdf.contains("Section 1.1"));
151    }
152}