Skip to main content

pdfium_render/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(clippy::doc_nested_refdefs)]
3// Vendored fork: suppress warnings from internal modules not exposed in the prelude.
4#![allow(dead_code)]
5#![allow(deprecated)]
6
7mod bindgen {
8    #![allow(non_upper_case_globals)]
9    #![allow(non_camel_case_types)]
10    #![allow(non_snake_case)]
11    #![allow(dead_code)]
12
13    include!("bindgen/pdfium_7678.rs");
14
15    pub(crate) type size_t = usize;
16}
17
18mod bindings;
19mod error;
20mod font_provider;
21mod pdf;
22mod pdfium;
23mod utils;
24
25/// A prelude for conveniently importing public `pdfium-render` definitions.
26///
27/// Only re-exports types actually used by the kreuzberg crate.
28/// Removed modules: form fields (field/), appearance_mode, annotation subtypes
29/// popup/redacted/widget/xfa_widget/variable_text. Form highlighting functions
30/// (highlight_*_form_fields) removed from PdfRenderConfig.
31///
32/// Usage:
33/// ```
34/// use pdfium_render::prelude::*;
35/// ```
36pub mod prelude {
37    pub use crate::{
38        bindings::*,
39        error::*,
40        font_provider::FontDescriptor,
41        // Bitmap & rendering
42        pdf::bitmap::*,
43        pdf::color::*,
44        pdf::color_space::*,
45        // Document
46        pdf::document::fonts::*,
47        pdf::document::metadata::*,
48        // Annotations (read-only, used by kreuzberg annotations.rs)
49        pdf::document::page::annotation::attachment_points::*,
50        pdf::document::page::annotation::circle::*,
51        pdf::document::page::annotation::free_text::*,
52        pdf::document::page::annotation::highlight::*,
53        pdf::document::page::annotation::ink::*,
54        pdf::document::page::annotation::link::*,
55        pdf::document::page::annotation::objects::*,
56        pdf::document::page::annotation::square::*,
57        pdf::document::page::annotation::squiggly::*,
58        pdf::document::page::annotation::stamp::*,
59        pdf::document::page::annotation::strikeout::*,
60        pdf::document::page::annotation::text::*,
61        pdf::document::page::annotation::underline::*,
62        pdf::document::page::annotation::unsupported::*,
63        pdf::document::page::annotation::{PdfPageAnnotation, PdfPageAnnotationCommon, PdfPageAnnotationType},
64        pdf::document::page::annotations::*,
65        // Page core
66        pdf::document::page::boundaries::*,
67        pdf::document::page::extraction::*,
68        pdf::document::page::links::*,
69        // Page objects
70        pdf::document::page::object::content_mark::*,
71        pdf::document::page::object::content_marks::*,
72        pdf::document::page::object::group::*,
73        pdf::document::page::object::image::*,
74        pdf::document::page::object::path::*,
75        pdf::document::page::object::shading::*,
76        pdf::document::page::object::text::*,
77        pdf::document::page::object::unsupported::*,
78        pdf::document::page::object::x_object_form::*,
79        pdf::document::page::object::{
80            PdfPageObject, PdfPageObjectBlendMode, PdfPageObjectCommon, PdfPageObjectLineCap, PdfPageObjectLineJoin,
81            PdfPageObjectType,
82        },
83        pdf::document::page::objects::common::*,
84        pdf::document::page::objects::*,
85        pdf::document::page::paragraph::*,
86        pdf::document::page::render_config::*,
87        pdf::document::page::size::*,
88        pdf::document::page::struct_element::*,
89        pdf::document::page::struct_tree::*,
90        // Text extraction
91        pdf::document::page::text::char::*,
92        pdf::document::page::text::chars::*,
93        pdf::document::page::text::search::*,
94        pdf::document::page::text::segment::*,
95        pdf::document::page::text::segments::*,
96        pdf::document::page::text::*,
97        pdf::document::page::{PdfPage, PdfPageContentRegenerationStrategy, PdfPageOrientation, PdfPageRenderRotation},
98        pdf::document::pages::*,
99        pdf::document::permissions::*,
100        pdf::document::{PdfDocument, PdfDocumentVersion},
101        // Fonts & geometry
102        pdf::font::glyph::*,
103        pdf::font::glyphs::*,
104        pdf::font::*,
105        pdf::link::*,
106        pdf::matrix::*,
107        pdf::path::clip_path::*,
108        pdf::path::segment::*,
109        pdf::path::segments::*,
110        pdf::points::*,
111        pdf::quad_points::*,
112        pdf::rect::*,
113        // Pdfium initialization
114        pdfium::*,
115    };
116}
117
118#[cfg(test)]
119mod tests {
120    use crate::prelude::*;
121    use crate::utils::test::{test_bind_to_pdfium, test_fixture_path};
122    use image_025::ImageFormat;
123    use std::fs::File;
124    use std::path::Path;
125
126    #[test]
127    #[cfg(not(pdfium_use_static))]
128    fn test_readme_example() -> Result<(), PdfiumError> {
129        // Runs the code in the main example at the top of README.md.
130
131        fn export_pdf_to_jpegs(path: &impl AsRef<Path>, password: Option<&str>) -> Result<(), PdfiumError> {
132            let pdfium = Pdfium;
133
134            let document = pdfium.load_pdf_from_file(path, password)?;
135
136            let render_config = PdfRenderConfig::new()
137                .set_target_width(2000)
138                .set_maximum_height(2000)
139                .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true);
140
141            for (index, page) in document.pages().iter().enumerate() {
142                page.render_with_config(&render_config)?
143                    .as_image()?
144                    .into_rgb8()
145                    .save_with_format(format!("test-page-{}.jpg", index), ImageFormat::Jpeg)
146                    .map_err(|_| PdfiumError::ImageError)?;
147            }
148
149            Ok(())
150        }
151
152        export_pdf_to_jpegs(&test_fixture_path("export-test.pdf"), None)
153    }
154
155    #[test]
156    #[cfg(not(pdfium_use_static))]
157    fn test_dynamic_bindings() -> Result<(), PdfiumError> {
158        let pdfium = Pdfium;
159
160        let document = pdfium.load_pdf_from_file(&test_fixture_path("form-test.pdf"), None)?;
161
162        let render_config = PdfRenderConfig::new()
163            .set_target_width(2000)
164            .set_maximum_height(2000)
165            .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true)
166            .render_form_data(true)
167            .render_annotations(true);
168
169        for (index, page) in document.pages().iter().enumerate() {
170            let result = page
171                .render_with_config(&render_config)?
172                .as_image()?
173                .into_rgb8()
174                .save_with_format(format!("form-test-page-{}.jpg", index), ImageFormat::Jpeg);
175
176            assert!(result.is_ok());
177        }
178
179        Ok(())
180    }
181
182    #[test]
183    #[cfg(pdfium_use_static)]
184    fn test_static_bindings() {
185        // Simply checks that the static bindings contain no compilation errors.
186
187        Pdfium::bind_to_statically_linked_library().unwrap();
188    }
189
190    #[test]
191    fn test_reader_lifetime() -> Result<(), PdfiumError> {
192        // Confirms that a reader given to Pdfium::load_pdf_from_reader() does not need
193        // a lifetime longer than that of the PdfDocument it is used to create.
194
195        let pdfium = test_bind_to_pdfium();
196
197        let filenames = ["form-test.pdf", "annotations-test.pdf"];
198
199        for filename in filenames {
200            let path = test_fixture_path(filename);
201            let page_count = {
202                let reader = File::open(&path).map_err(PdfiumError::IoError)?;
203
204                let document = pdfium.load_pdf_from_reader(reader, None)?;
205
206                document.pages().len()
207
208                // reader will be dropped here, immediately after document.
209            };
210
211            println!("{} has {} pages", path.display(), page_count);
212        }
213
214        Ok(())
215    }
216
217    #[test]
218    #[cfg(not(pdfium_use_static))]
219    fn test_custom_font_paths_with_text_rendering() -> Result<(), PdfiumError> {
220        // Use system font paths that exist on Ubuntu CI
221        let config = PdfiumConfig::new().set_user_font_paths(vec!["/usr/share/fonts/truetype/".to_string()]);
222
223        let bindings = Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
224            .or_else(|_| Pdfium::bind_to_system_library());
225
226        match bindings {
227            Ok(bindings) => {
228                let pdfium = Pdfium::new_with_config(bindings, &config);
229
230                // Create a document and actually use text to verify fonts work
231                let mut document = pdfium.create_new_pdf()?;
232                let mut page = document.pages_mut().create_page_at_end(PdfPagePaperSize::a4())?;
233
234                // Use a built-in font and create text object
235                let font = document.fonts_mut().helvetica();
236                let _text_obj = page.objects_mut().create_text_object(
237                    PdfPoints::new(100.0),
238                    PdfPoints::new(700.0),
239                    "Testing custom font paths",
240                    font,
241                    PdfPoints::new(12.0),
242                )?;
243
244                // Verify text object was created successfully
245                assert!(page.objects().iter().count() > 0);
246
247                Ok(())
248            }
249            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => {
250                // Already initialized in another test, that's ok for CI
251                Ok(())
252            }
253            Err(e) => Err(e),
254        }
255    }
256
257    #[test]
258    #[cfg(not(pdfium_use_static))]
259    fn test_empty_font_paths() -> Result<(), PdfiumError> {
260        let config = PdfiumConfig::new(); // No custom paths
261
262        let bindings = Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
263            .or_else(|_| Pdfium::bind_to_system_library());
264
265        match bindings {
266            Ok(bindings) => {
267                let pdfium = Pdfium::new_with_config(bindings, &config);
268                let document = pdfium.create_new_pdf()?;
269                assert_eq!(document.pages().len(), 0);
270                Ok(())
271            }
272            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Ok(()),
273            Err(e) => Err(e),
274        }
275    }
276
277    #[test]
278    #[cfg(not(pdfium_use_static))]
279    fn test_font_paths_with_null_bytes() -> Result<(), PdfiumError> {
280        // Path with null byte should be safely ignored
281        let config = PdfiumConfig::new().set_user_font_paths(vec![
282            "/usr/share\0/fonts".to_string(),         // Contains null byte
283            "/usr/share/fonts/truetype/".to_string(), // Valid path
284        ]);
285
286        let bindings = Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
287            .or_else(|_| Pdfium::bind_to_system_library());
288
289        match bindings {
290            Ok(bindings) => {
291                // Should not crash, null-byte path should be skipped
292                let pdfium = Pdfium::new_with_config(bindings, &config);
293                let document = pdfium.create_new_pdf()?;
294                assert_eq!(document.pages().len(), 0);
295                Ok(())
296            }
297            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Ok(()),
298            Err(e) => Err(e),
299        }
300    }
301
302    #[test]
303    #[cfg(not(pdfium_use_static))]
304    fn test_font_paths_nonexistent() -> Result<(), PdfiumError> {
305        // Non-existent paths should not crash Pdfium
306        let config = PdfiumConfig::new().set_user_font_paths(vec![
307            "/this/path/does/not/exist".to_string(),
308            "/another/fake/path".to_string(),
309        ]);
310
311        let bindings = Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
312            .or_else(|_| Pdfium::bind_to_system_library());
313
314        match bindings {
315            Ok(bindings) => {
316                // Should not crash, Pdfium should handle gracefully
317                let pdfium = Pdfium::new_with_config(bindings, &config);
318                let document = pdfium.create_new_pdf()?;
319                assert_eq!(document.pages().len(), 0);
320                Ok(())
321            }
322            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Ok(()),
323            Err(e) => Err(e),
324        }
325    }
326
327    #[test]
328    #[cfg(not(pdfium_use_static))]
329    fn test_default_config_uses_simple_initialization() -> Result<(), PdfiumError> {
330        // Test that default config (no font paths, no font provider) uses FPDF_InitLibrary()
331        // rather than FPDF_InitLibraryWithConfig() to avoid potential overhead.
332        // This is a behavioral test - we just verify it doesn't crash and works correctly.
333
334        let config = PdfiumConfig::new(); // Empty config
335
336        let bindings = Pdfium::bind_to_library(Pdfium::pdfium_platform_library_name_at_path("./"))
337            .or_else(|_| Pdfium::bind_to_system_library());
338
339        match bindings {
340            Ok(bindings) => {
341                let pdfium = Pdfium::new_with_config(bindings, &config);
342                let document = pdfium.create_new_pdf()?;
343                assert_eq!(document.pages().len(), 0);
344                Ok(())
345            }
346            Err(PdfiumError::PdfiumLibraryBindingsAlreadyInitialized) => Ok(()),
347            Err(e) => Err(e),
348        }
349    }
350}