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