Skip to main content

fop_render/
lib.rs

1//! Rendering backends for FOP
2//!
3//! Transforms area trees into output formats (PDF, SVG, PostScript, images, text, etc.)
4
5pub mod image;
6pub mod parallel;
7pub mod pdf;
8pub mod ps;
9#[cfg(feature = "raster")]
10pub mod raster;
11pub mod svg;
12pub mod text;
13
14pub use fop_types::{FopError, Result};
15pub use image::{ImageFormat, ImageInfo, ImagePlacement};
16pub use parallel::ParallelRenderer;
17pub use pdf::{
18    EncryptionAlgorithm, EncryptionDict, FontConfig, PdfBuiltinFont, PdfCompliance, PdfDocument,
19    PdfGraphics, PdfPermissions, PdfRenderer, PdfSecurity, PdfValidator, SimpleDocumentBuilder,
20    ValidationResult,
21};
22pub use ps::{PsDocument, PsRenderer};
23#[cfg(feature = "raster")]
24pub use raster::{RasterFormat, RasterRenderer};
25pub use svg::{SvgDocument, SvgGraphics, SvgRenderer};
26pub use text::TextRenderer;
27
28#[cfg(test)]
29mod tests {
30    use fop_core::FoTreeBuilder;
31    use fop_layout::{AreaTree, LayoutEngine};
32    use std::io::Cursor;
33
34    /// Build a minimal area tree from a simple XSL-FO document.
35    fn minimal_area_tree() -> AreaTree {
36        let fo_xml = r##"<?xml version="1.0"?>
37<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
38  <fo:layout-master-set>
39    <fo:simple-page-master master-name="A4"
40      page-width="210mm" page-height="297mm"
41      margin-top="20mm" margin-bottom="20mm"
42      margin-left="20mm" margin-right="20mm">
43      <fo:region-body/>
44    </fo:simple-page-master>
45  </fo:layout-master-set>
46  <fo:page-sequence master-reference="A4">
47    <fo:flow flow-name="xsl-region-body">
48      <fo:block>Hello, world!</fo:block>
49    </fo:flow>
50  </fo:page-sequence>
51</fo:root>"##;
52        let builder = FoTreeBuilder::new();
53        let fo_tree = builder
54            .parse(Cursor::new(fo_xml))
55            .expect("test: should succeed");
56        let engine = LayoutEngine::new();
57        engine.layout(&fo_tree).expect("test: should succeed")
58    }
59
60    // ── PDF rendering ─────────────────────────────────────────────────────────
61
62    #[test]
63    fn test_pdf_render_produces_bytes() {
64        let area_tree = minimal_area_tree();
65        let renderer = crate::PdfRenderer::new();
66        let doc = renderer.render(&area_tree).expect("test: should succeed");
67        let bytes = doc.to_bytes().expect("test: should succeed");
68        assert!(!bytes.is_empty(), "PDF output must not be empty");
69        assert!(
70            bytes.starts_with(b"%PDF-"),
71            "PDF must start with %PDF- header"
72        );
73    }
74
75    #[test]
76    fn test_pdf_render_has_one_page() {
77        let area_tree = minimal_area_tree();
78        let renderer = crate::PdfRenderer::new();
79        let doc = renderer.render(&area_tree).expect("test: should succeed");
80        assert_eq!(
81            doc.pages.len(),
82            1,
83            "single page-sequence must produce 1 PDF page"
84        );
85    }
86
87    #[test]
88    fn test_pdf_render_has_eof() {
89        let area_tree = minimal_area_tree();
90        let renderer = crate::PdfRenderer::new();
91        let bytes = renderer
92            .render(&area_tree)
93            .expect("test: should succeed")
94            .to_bytes()
95            .expect("test: should succeed");
96        let content = String::from_utf8_lossy(&bytes);
97        assert!(content.contains("%%EOF"), "PDF must end with %%EOF");
98    }
99
100    #[test]
101    fn test_pdf_render_contains_text() {
102        let area_tree = minimal_area_tree();
103        let renderer = crate::PdfRenderer::new();
104        let bytes = renderer
105            .render(&area_tree)
106            .expect("test: should succeed")
107            .to_bytes()
108            .expect("test: should succeed");
109        let content = String::from_utf8_lossy(&bytes);
110        assert!(
111            content.contains("Hello, world!"),
112            "PDF must contain the rendered text"
113        );
114    }
115
116    // ── SVG rendering ─────────────────────────────────────────────────────────
117
118    #[test]
119    fn test_svg_render_produces_string() {
120        let area_tree = minimal_area_tree();
121        let renderer = crate::SvgRenderer::new();
122        let result = renderer
123            .render_to_svg(&area_tree)
124            .expect("test: should succeed");
125        assert!(!result.is_empty(), "SVG output must not be empty");
126    }
127
128    #[test]
129    fn test_svg_render_has_xml_declaration() {
130        let area_tree = minimal_area_tree();
131        let renderer = crate::SvgRenderer::new();
132        let result = renderer
133            .render_to_svg(&area_tree)
134            .expect("test: should succeed");
135        assert!(
136            result.starts_with("<?xml"),
137            "SVG output must start with XML declaration"
138        );
139    }
140
141    #[test]
142    fn test_svg_render_has_svg_element() {
143        let area_tree = minimal_area_tree();
144        let renderer = crate::SvgRenderer::new();
145        let result = renderer
146            .render_to_svg(&area_tree)
147            .expect("test: should succeed");
148        assert!(
149            result.contains("<svg"),
150            "SVG output must contain <svg element"
151        );
152    }
153
154    #[test]
155    fn test_svg_render_contains_text() {
156        let area_tree = minimal_area_tree();
157        let renderer = crate::SvgRenderer::new();
158        let result = renderer
159            .render_to_svg(&area_tree)
160            .expect("test: should succeed");
161        assert!(
162            result.contains("Hello, world!"),
163            "SVG output must contain the rendered text"
164        );
165    }
166
167    #[test]
168    fn test_svg_render_pages_produces_one_page() {
169        let area_tree = minimal_area_tree();
170        let renderer = crate::SvgRenderer::new();
171        let pages = renderer
172            .render_to_svg_pages(&area_tree)
173            .expect("test: should succeed");
174        assert_eq!(
175            pages.len(),
176            1,
177            "single page-sequence must produce 1 SVG page"
178        );
179    }
180
181    #[test]
182    fn test_svg_render_each_page_is_valid_svg() {
183        let area_tree = minimal_area_tree();
184        let renderer = crate::SvgRenderer::new();
185        let pages = renderer
186            .render_to_svg_pages(&area_tree)
187            .expect("test: should succeed");
188        for (i, page) in pages.iter().enumerate() {
189            assert!(
190                page.contains("<svg"),
191                "SVG page {i} must contain <svg element"
192            );
193            assert!(page.contains("</svg>"), "SVG page {i} must close </svg>");
194        }
195    }
196
197    // ── PostScript rendering ──────────────────────────────────────────────────
198
199    #[test]
200    fn test_ps_render_produces_string() {
201        let area_tree = minimal_area_tree();
202        let renderer = crate::PsRenderer::new();
203        let result = renderer
204            .render_to_ps(&area_tree)
205            .expect("test: should succeed");
206        assert!(!result.is_empty(), "PostScript output must not be empty");
207    }
208
209    #[test]
210    fn test_ps_render_has_ps_header() {
211        let area_tree = minimal_area_tree();
212        let renderer = crate::PsRenderer::new();
213        let result = renderer
214            .render_to_ps(&area_tree)
215            .expect("test: should succeed");
216        assert!(
217            result.starts_with("%!PS-Adobe-3.0"),
218            "PostScript output must start with %!PS-Adobe-3.0"
219        );
220    }
221
222    #[test]
223    fn test_ps_render_has_eof() {
224        let area_tree = minimal_area_tree();
225        let renderer = crate::PsRenderer::new();
226        let result = renderer
227            .render_to_ps(&area_tree)
228            .expect("test: should succeed");
229        assert!(
230            result.contains("%%EOF"),
231            "PostScript output must contain %%EOF"
232        );
233    }
234
235    #[test]
236    fn test_ps_render_has_one_page() {
237        let area_tree = minimal_area_tree();
238        let renderer = crate::PsRenderer::new();
239        let result = renderer
240            .render_to_ps(&area_tree)
241            .expect("test: should succeed");
242        assert!(
243            result.contains("%%Pages: 1"),
244            "single page-sequence must produce 1 PostScript page"
245        );
246    }
247
248    // ── Text rendering ────────────────────────────────────────────────────────
249
250    #[test]
251    fn test_text_render_produces_string() {
252        let area_tree = minimal_area_tree();
253        let renderer = crate::TextRenderer::new();
254        let result = renderer
255            .render_to_text(&area_tree)
256            .expect("test: should succeed");
257        assert!(!result.is_empty(), "text output must not be empty");
258    }
259
260    #[test]
261    fn test_text_render_contains_content() {
262        let area_tree = minimal_area_tree();
263        let renderer = crate::TextRenderer::new();
264        let result = renderer
265            .render_to_text(&area_tree)
266            .expect("test: should succeed");
267        assert!(
268            result.contains("Hello, world!"),
269            "text output must contain the rendered text"
270        );
271    }
272
273    // ── Renderer constructors ─────────────────────────────────────────────────
274
275    #[test]
276    fn test_all_renderers_constructible() {
277        let _pdf = crate::PdfRenderer::new();
278        let _svg = crate::SvgRenderer::new();
279        let _ps = crate::PsRenderer::new();
280        let _text = crate::TextRenderer::new();
281        // all renderers must be constructible without panicking
282    }
283
284    #[test]
285    fn test_pdf_renderer_with_system_fonts_constructible() {
286        let renderer = crate::PdfRenderer::with_system_fonts();
287        let _ = renderer;
288    }
289}