Skip to main content

fop_pdf_renderer/
lib.rs

1//! fop-pdf-renderer — Pure Rust PDF-to-image renderer
2//!
3//! Renders PDF pages to raster images (PNG/RGBA) without any C dependencies.
4//! Designed to work with PDFs generated by fop-render, enabling self-contained
5//! testing and verification.
6//!
7//! # Quick Start
8//!
9//! ```no_run
10//! use fop_pdf_renderer::PdfRenderer;
11//!
12//! let pdf_data = std::fs::read("output.pdf").unwrap();
13//! let renderer = PdfRenderer::from_bytes(&pdf_data).unwrap();
14//!
15//! println!("Pages: {}", renderer.page_count());
16//!
17//! // Render page 0 at 150 DPI
18//! let image = renderer.render_page(0, 150.0).unwrap();
19//!
20//! // Save as PNG
21//! renderer.save_as_png(0, "output.png", 150.0).unwrap();
22//! ```
23
24pub mod content;
25pub mod error;
26pub mod font;
27pub mod graphics;
28pub mod image;
29pub mod parser;
30pub mod rasterizer;
31pub mod text;
32
33pub use error::{PdfRenderError, Result};
34pub use rasterizer::RasterPage;
35
36/// High-level PDF renderer
37///
38/// Parses a PDF document and provides page rendering capabilities.
39pub struct PdfRenderer {
40    doc: parser::PdfDocument,
41}
42
43impl PdfRenderer {
44    /// Parse a PDF document from bytes
45    pub fn from_bytes(data: &[u8]) -> Result<Self> {
46        let doc = parser::PdfDocument::from_bytes(data)?;
47        Ok(Self { doc })
48    }
49
50    /// Number of pages in the document
51    pub fn page_count(&self) -> usize {
52        self.doc.page_count()
53    }
54
55    /// Render a single page at the given DPI
56    ///
57    /// Returns a `RasterPage` with RGBA pixels.
58    pub fn render_page(&self, page_index: usize, dpi: f32) -> Result<RasterPage> {
59        let page = self.doc.get_page(page_index)?;
60        let mut rasterizer = rasterizer::PageRasterizer::new(&self.doc);
61        rasterizer.render(&page, dpi)
62    }
63
64    /// Render a page and save it as a PNG file
65    pub fn save_as_png(&self, page_index: usize, path: &str, dpi: f32) -> Result<()> {
66        let page = self.render_page(page_index, dpi)?;
67        page.save_png(path)
68    }
69
70    /// Render all pages and return them as PNG bytes
71    pub fn render_all_pages(&self, dpi: f32) -> Result<Vec<Vec<u8>>> {
72        let mut pages = Vec::new();
73        for i in 0..self.page_count() {
74            let page = self.render_page(i, dpi)?;
75            pages.push(page.to_png()?);
76        }
77        Ok(pages)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_empty_pdf_returns_error() {
87        let result = PdfRenderer::from_bytes(b"not a pdf");
88        assert!(result.is_err());
89    }
90
91    #[test]
92    fn test_minimal_pdf() {
93        // A minimal valid PDF structure
94        let pdf = b"%PDF-1.4\n\
95            1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n\
96            2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n\
97            3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n\
98            xref\n0 4\n0000000000 65535 f \n0000000009 00000 n \n0000000058 00000 n \n0000000115 00000 n \n\
99            trailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n200\n%%EOF\n";
100
101        let renderer = PdfRenderer::from_bytes(pdf);
102        // May succeed or fail depending on xref accuracy, but should not panic
103        if let Ok(r) = renderer {
104            assert_eq!(r.page_count(), 1);
105        }
106    }
107}