Skip to main content

pdfium_render/pdf/document/page/
render_config.rs

1//! Defines the [PdfRenderConfig] struct, a builder-based approach to configuring
2//! the rendering of [PdfBitmap] objects from one or more [PdfPage] objects.
3
4use crate::bindgen::{
5    FPDF_ANNOT, FPDF_CONVERT_FILL_TO_STROKE, FPDF_DWORD, FPDF_GRAYSCALE, FPDF_LCD_TEXT,
6    FPDF_NO_NATIVETEXT, FPDF_PRINTING, FPDF_RENDER_FORCEHALFTONE, FPDF_RENDER_LIMITEDIMAGECACHE,
7    FPDF_RENDER_NO_SMOOTHIMAGE, FPDF_RENDER_NO_SMOOTHPATH, FPDF_RENDER_NO_SMOOTHTEXT,
8    FPDF_REVERSE_BYTE_ORDER, FS_MATRIX, FS_RECTF,
9};
10use crate::create_transform_setters;
11use crate::error::PdfiumError;
12use crate::pdf::bitmap::{PdfBitmap, PdfBitmapFormat, Pixels};
13use crate::pdf::color::PdfColor;
14use crate::pdf::document::page::field::PdfFormFieldType;
15use crate::pdf::document::page::PdfPageOrientation::{Landscape, Portrait};
16use crate::pdf::document::page::{PdfPage, PdfPageOrientation, PdfPageRenderRotation};
17use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
18use crate::pdf::points::PdfPoints;
19use std::os::raw::c_int;
20
21/// Configures the scaling, rotation, and rendering settings that should be applied to
22/// a [PdfPage] to create a [PdfBitmap] for that page. [PdfRenderConfig] can accommodate pages of
23/// different sizes while correctly maintaining each page's aspect ratio, automatically
24/// rotate portrait or landscape pages, generate page thumbnails, apply maximum pixel size
25/// constraints to the scaled width and height of the final rendering, highlight form fields
26/// with different colors, apply custom transforms to the page during rendering, and set
27/// internal Pdfium rendering flags.
28///
29/// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
30/// a custom transformation matrix, but not both at the same time. Applying any transformation
31/// automatically disables rendering of form data. If you must render form data while simultaneously
32/// applying transformations, consider using the [PdfPage::flatten()] function to flatten the
33/// form elements and form data into the containing page.
34pub struct PdfRenderConfig {
35    use_auto_scaling: bool,
36    start_x: Pixels,
37    start_y: Pixels,
38    fixed_width: Option<Pixels>,
39    fixed_height: Option<Pixels>,
40    target_width: Option<Pixels>,
41    target_height: Option<Pixels>,
42    scale_width_factor: Option<f32>,
43    scale_height_factor: Option<f32>,
44    maximum_width: Option<Pixels>,
45    maximum_height: Option<Pixels>,
46    portrait_rotation: PdfPageRenderRotation,
47    portrait_rotation_do_rotate_constraints: bool,
48    landscape_rotation: PdfPageRenderRotation,
49    landscape_rotation_do_rotate_constraints: bool,
50    format: PdfBitmapFormat,
51    do_clear_bitmap_before_rendering: bool,
52    clear_color: PdfColor,
53    do_render_form_data: bool,
54    form_field_highlight: Option<Vec<(PdfFormFieldType, PdfColor)>>,
55    transformation_matrix: PdfMatrix,
56    clip_rect: Option<(Pixels, Pixels, Pixels, Pixels)>,
57
58    // The fields below set Pdfium's page rendering flags. Coverage for the
59    // FPDF_DEBUG_INFO and FPDF_NO_CATCH flags is omitted since they are obsolete.
60    do_set_flag_render_annotations: bool,     // Sets FPDF_ANNOT
61    do_set_flag_use_lcd_text_rendering: bool, // Sets FPDF_LCD_TEXT
62    do_set_flag_no_native_text: bool,         // Sets FPDF_NO_NATIVETEXT
63    do_set_flag_grayscale: bool,              // Sets FPDF_GRAYSCALE
64    do_set_flag_render_limited_image_cache: bool, // Sets FPDF_RENDER_LIMITEDIMAGECACHE
65    do_set_flag_render_force_half_tone: bool, // Sets FPDF_RENDER_FORCEHALFTONE
66    do_set_flag_render_for_printing: bool,    // Sets FPDF_PRINTING
67    do_set_flag_render_no_smooth_text: bool,  // Sets FPDF_RENDER_NO_SMOOTHTEXT
68    do_set_flag_render_no_smooth_image: bool, // Sets FPDF_RENDER_NO_SMOOTHIMAGE
69    do_set_flag_render_no_smooth_path: bool,  // Sets FPDF_RENDER_NO_SMOOTHPATH
70    do_set_flag_reverse_byte_order: bool,     // Sets FPDF_REVERSE_BYTE_ORDER
71    do_set_flag_convert_fill_to_stroke: bool, // Sets FPDF_CONVERT_FILL_TO_STROKE
72}
73
74impl PdfRenderConfig {
75    /// Creates a new [PdfRenderConfig] object with all settings initialized to their default values.
76    pub fn new() -> Self {
77        PdfRenderConfig {
78            use_auto_scaling: true,
79            start_x: 0,
80            start_y: 0,
81            fixed_width: None,
82            fixed_height: None,
83            target_width: None,
84            target_height: None,
85            scale_width_factor: None,
86            scale_height_factor: None,
87            maximum_width: None,
88            maximum_height: None,
89            portrait_rotation: PdfPageRenderRotation::None,
90            portrait_rotation_do_rotate_constraints: false,
91            landscape_rotation: PdfPageRenderRotation::None,
92            landscape_rotation_do_rotate_constraints: false,
93            format: PdfBitmapFormat::default(),
94            do_clear_bitmap_before_rendering: true,
95            clear_color: PdfColor::WHITE,
96            do_render_form_data: true,
97            form_field_highlight: None,
98            transformation_matrix: PdfMatrix::IDENTITY,
99            clip_rect: None,
100            do_set_flag_render_annotations: true,
101            do_set_flag_use_lcd_text_rendering: false,
102            do_set_flag_no_native_text: false,
103            do_set_flag_grayscale: false,
104            do_set_flag_render_limited_image_cache: false,
105            do_set_flag_render_force_half_tone: false,
106            do_set_flag_render_for_printing: false,
107            do_set_flag_render_no_smooth_text: false,
108            do_set_flag_render_no_smooth_image: false,
109            do_set_flag_render_no_smooth_path: false,
110            do_set_flag_convert_fill_to_stroke: false,
111
112            // We ask Pdfium to reverse its bitmap byte order from BGR8 to RGB8 in order
113            // to make working with Image::DynamicImage easier after version 0.24. See:
114            // https://github.com/ajrcarey/pdfium-render/issues/9
115            do_set_flag_reverse_byte_order: true,
116        }
117    }
118
119    /// Applies settings suitable for generating a thumbnail.
120    ///
121    /// * The source [PdfPage] will be rendered with a maximum width and height of the given
122    ///   pixel size.
123    /// * The page will not be rotated, irrespective of its orientation.
124    /// * Image quality settings will be reduced to improve performance.
125    /// * Annotations and user-filled form field data will not be rendered.
126    ///
127    /// These settings are applied to this [PdfRenderConfig] object immediately and can be
128    /// selectively overridden by later function calls. For instance, a later call to
129    /// [PdfRenderConfig::rotate()] can specify a custom rotation setting that will apply
130    /// to the thumbnail.
131    #[inline]
132    pub fn thumbnail(self, size: Pixels) -> Self {
133        self.set_target_size(size, size)
134            .set_maximum_width(size)
135            .set_maximum_height(size)
136            .rotate(PdfPageRenderRotation::None, false)
137            .use_print_quality(false)
138            .set_image_smoothing(false)
139            .render_annotations(false)
140            .render_form_data(false)
141    }
142
143    /// Sets the desired pixel width and height of a rendered [PdfPage] to the
144    /// width and height of the given [PdfBitmap]. No attempt will be made to scale or adjust
145    /// the aspect ratio to match the source page. Overrides any previous call to
146    /// [PdfRenderConfig::set_target_size], [PdfRenderConfig::set_target_width], or
147    /// [PdfRenderConfig::set_target_height].
148    #[inline]
149    pub fn set_fixed_size_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
150        self.set_fixed_size(bitmap.width(), bitmap.height())
151    }
152
153    /// Sets the desired pixel width and height of a rendered [PdfPage] to the given
154    /// pixel values. No attempt will be made to scale or adjust the aspect ratio to
155    /// match the source page. Overrides any previous call to [PdfRenderConfig::set_target_size],
156    /// [PdfRenderConfig::set_target_width], or [PdfRenderConfig::set_target_height].
157    #[inline]
158    pub fn set_fixed_size(self, width: Pixels, height: Pixels) -> Self {
159        self.set_fixed_width(width).set_fixed_height(height)
160    }
161
162    /// Sets the desired pixel width of a rendered [PdfPage] to the given value. Overrides
163    /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_width].
164    #[inline]
165    pub fn set_fixed_width(mut self, width: Pixels) -> Self {
166        self.use_auto_scaling = false;
167        self.fixed_width = Some(width);
168
169        self
170    }
171
172    /// Sets the desired pixel height of a rendered [PdfPage] to the given value. Overrides
173    /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_height].
174    #[inline]
175    pub fn set_fixed_height(mut self, height: Pixels) -> Self {
176        self.use_auto_scaling = false;
177        self.fixed_height = Some(height);
178
179        self
180    }
181
182    /// Converts the width and height of a [PdfPage] from points to pixels, scaling each
183    /// dimension to the given target pixel sizes. The aspect ratio of the source page
184    /// will not be maintained. Overrides any previous call to [PdfRenderConfig::set_fixed_width()]
185    /// or [PdfRenderConfig::set_fixed_height()].
186    #[inline]
187    pub fn set_target_size(self, width: Pixels, height: Pixels) -> Self {
188        self.set_target_width(width).set_target_height(height)
189    }
190
191    /// Converts the width of a [PdfPage] from points to pixels, scaling the source page
192    /// width to the given target pixel width. The aspect ratio of the source page
193    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
194    /// or [PdfRenderConfig::set_target_height()] that overrides it. Overrides any previous
195    /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
196    #[inline]
197    pub fn set_target_width(mut self, width: Pixels) -> Self {
198        self.use_auto_scaling = true;
199        self.target_width = Some(width);
200
201        self
202    }
203
204    /// Converts the height of a [PdfPage] from points to pixels, scaling the source page
205    /// height to the given target pixel height. The aspect ratio of the source page
206    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
207    /// or [PdfRenderConfig::set_target_width()] that overrides it. Overrides any previous
208    /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
209    #[inline]
210    pub fn set_target_height(mut self, height: Pixels) -> Self {
211        self.use_auto_scaling = true;
212        self.target_height = Some(height);
213
214        self
215    }
216
217    /// Applies settings to this [PdfRenderConfig] suitable for filling the given [PdfBitmap].
218    ///
219    /// The source page's dimensions will be scaled so that both width and height attempt
220    /// to fill, but do not exceed, the pixel dimensions of the bitmap. The aspect ratio
221    /// of the source page will be maintained. Landscape pages will be automatically rotated
222    /// by 90 degrees and will be scaled down if necessary to fit the bitmap width.
223    #[inline]
224    pub fn scale_page_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
225        self.scale_page_to_display_size(bitmap.width(), bitmap.height())
226    }
227
228    /// Applies settings to this [PdfRenderConfig] suitable for filling the given
229    /// screen display size.
230    ///
231    /// The source page's dimensions will be scaled so that both width and height attempt
232    /// to fill, but do not exceed, the given pixel dimensions. The aspect ratio of the
233    /// source page will be maintained. Landscape pages will be automatically rotated
234    /// by 90 degrees and will be scaled down if necessary to fit the display width.
235    #[inline]
236    pub fn scale_page_to_display_size(mut self, width: Pixels, height: Pixels) -> Self {
237        self.scale_width_factor = None;
238        self.scale_height_factor = None;
239
240        self.set_target_width(width)
241            .set_maximum_width(width)
242            .set_maximum_height(height)
243            .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true)
244    }
245
246    /// Converts the width and height of a [PdfPage] from points to pixels by applying
247    /// the given scale factor to both dimensions. The aspect ratio of the source page
248    /// will be maintained. Overrides any previous call to [PdfRenderConfig::scale_page_by_factor()],
249    /// [PdfRenderConfig::scale_page_width_by_factor()], or [PdfRenderConfig::scale_page_height_by_factor()].
250    #[inline]
251    pub fn scale_page_by_factor(self, scale: f32) -> Self {
252        let result = self.scale_page_width_by_factor(scale);
253
254        result.scale_page_height_by_factor(scale)
255    }
256
257    /// Converts the width of the [PdfPage] from points to pixels by applying the given
258    /// scale factor. The aspect ratio of the source page will not be maintained if a
259    /// different scale factor is applied to the height. Overrides any previous call to
260    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
261    /// or [PdfRenderConfig::scale_page_height_by_factor()].
262    #[inline]
263    pub fn scale_page_width_by_factor(mut self, scale: f32) -> Self {
264        self.scale_width_factor = Some(scale);
265
266        self
267    }
268
269    /// Converts the height of the [PdfPage] from points to pixels by applying the given
270    /// scale factor. The aspect ratio of the source page will not be maintained if a
271    /// different scale factor is applied to the width. Overrides any previous call to
272    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
273    /// or [PdfRenderConfig::scale_page_height_by_factor()].
274    #[inline]
275    pub fn scale_page_height_by_factor(mut self, scale: f32) -> Self {
276        self.scale_height_factor = Some(scale);
277
278        self
279    }
280
281    /// Specifies that the final pixel width of the [PdfPage] will not exceed the given maximum.
282    #[inline]
283    pub fn set_maximum_width(mut self, width: Pixels) -> Self {
284        self.maximum_width = Some(width);
285
286        self
287    }
288
289    /// Specifies that the final pixel height of the [PdfPage] will not exceed the given maximum.
290    #[inline]
291    pub fn set_maximum_height(mut self, height: Pixels) -> Self {
292        self.maximum_height = Some(height);
293
294        self
295    }
296
297    /// Applies the given clockwise rotation setting to the [PdfPage] during rendering, irrespective
298    /// of its orientation. If the given flag is set to `true` then any maximum
299    /// constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
300    /// will be rotated so it becomes a constraint on the final pixel height, and any
301    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
302    /// will be rotated so it becomes a constraint on the final pixel width.
303    #[inline]
304    pub fn rotate(self, rotation: PdfPageRenderRotation, do_rotate_constraints: bool) -> Self {
305        self.rotate_if_portrait(rotation, do_rotate_constraints)
306            .rotate_if_landscape(rotation, do_rotate_constraints)
307    }
308
309    /// Applies the given clockwise rotation settings to the [PdfPage] during rendering, if the page
310    /// is in portrait orientation. If the given flag is set to `true` and the given
311    /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
312    /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
313    /// will be rotated so it becomes a constraint on the final pixel height and any
314    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
315    /// will be rotated so it becomes a constraint on the final pixel width.
316    #[inline]
317    pub fn rotate_if_portrait(
318        mut self,
319        rotation: PdfPageRenderRotation,
320        do_rotate_constraints: bool,
321    ) -> Self {
322        self.portrait_rotation = rotation;
323
324        if rotation == PdfPageRenderRotation::Degrees90
325            || rotation == PdfPageRenderRotation::Degrees270
326        {
327            self.portrait_rotation_do_rotate_constraints = do_rotate_constraints;
328        }
329
330        self
331    }
332
333    /// Applies the given rotation settings to the [PdfPage] during rendering, if the page
334    /// is in landscape orientation. If the given flag is set to `true` and the given
335    /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
336    /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
337    /// will be rotated so it becomes a constraint on the final pixel height and any
338    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
339    /// will be rotated so it becomes a constraint on the final pixel width.
340    #[inline]
341    pub fn rotate_if_landscape(
342        mut self,
343        rotation: PdfPageRenderRotation,
344        do_rotate_constraints: bool,
345    ) -> Self {
346        self.landscape_rotation = rotation;
347
348        if rotation == PdfPageRenderRotation::Degrees90
349            || rotation == PdfPageRenderRotation::Degrees270
350        {
351            self.landscape_rotation_do_rotate_constraints = do_rotate_constraints;
352        }
353
354        self
355    }
356
357    /// Sets the pixel format that will be used during rendering of the [PdfPage].
358    /// The default is [PdfBitmapFormat::BGRA].
359    #[inline]
360    pub fn set_format(mut self, format: PdfBitmapFormat) -> Self {
361        self.format = format;
362
363        self
364    }
365
366    /// Controls whether the destination bitmap should be cleared by setting every pixel to a
367    /// known color value before rendering the [PdfPage]. The default is `true`.
368    /// The color used during clearing can be customised by calling [PdfRenderConfig::set_clear_color()].
369    #[inline]
370    pub fn clear_before_rendering(mut self, do_clear: bool) -> Self {
371        self.do_clear_bitmap_before_rendering = do_clear;
372
373        self
374    }
375
376    /// Sets the color applied to every pixel in the destination bitmap when clearing the bitmap
377    /// before rendering the [PdfPage]. The default is [PdfColor::WHITE]. This setting
378    /// has no effect if [PdfRenderConfig::clear_before_rendering()] is set to `false`.
379    #[inline]
380    pub fn set_clear_color(mut self, color: PdfColor) -> Self {
381        self.clear_color = color;
382
383        self
384    }
385
386    /// Controls whether form data widgets and user-supplied form data should be included
387    /// during rendering of the [PdfPage]. The default is `true`.
388    ///
389    /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
390    /// a custom transformation matrix, but not both at the same time. Applying any transformation
391    /// automatically sets this value to `false`, disabling rendering of form data.
392    #[inline]
393    pub fn render_form_data(mut self, do_render: bool) -> Self {
394        self.do_render_form_data = do_render;
395
396        self
397    }
398
399    /// Controls whether user-supplied annotations should be included during rendering of
400    /// the [PdfPage]. The default is `true`.
401    #[inline]
402    pub fn render_annotations(mut self, do_render: bool) -> Self {
403        self.do_set_flag_render_annotations = do_render;
404
405        self
406    }
407
408    /// Controls whether text rendering should be optimized for LCD display.
409    /// The default is `false`.
410    /// Has no effect if anti-aliasing of text has been disabled by a call to
411    /// `PdfRenderConfig::set_text_smoothing(false)`.
412    #[inline]
413    pub fn use_lcd_text_rendering(mut self, do_set_flag: bool) -> Self {
414        self.do_set_flag_use_lcd_text_rendering = do_set_flag;
415
416        self
417    }
418
419    /// Controls whether platform text rendering should be disabled on platforms that support it.
420    /// The alternative is for Pdfium to render all text internally, which may give more
421    /// consistent rendering results across platforms but may also be slower.
422    /// The default is `false`.
423    #[inline]
424    pub fn disable_native_text_rendering(mut self, do_set_flag: bool) -> Self {
425        self.do_set_flag_no_native_text = do_set_flag;
426
427        self
428    }
429
430    /// Controls whether rendering output should be grayscale rather than full color.
431    /// The default is `false`.
432    #[inline]
433    pub fn use_grayscale_rendering(mut self, do_set_flag: bool) -> Self {
434        self.do_set_flag_grayscale = do_set_flag;
435
436        self
437    }
438
439    /// Controls whether Pdfium should limit its image cache size during rendering.
440    /// A smaller cache size may result in lower memory usage at the cost of slower rendering.
441    /// The default is `false`.
442    #[inline]
443    pub fn limit_render_image_cache_size(mut self, do_set_flag: bool) -> Self {
444        self.do_set_flag_render_limited_image_cache = do_set_flag;
445
446        self
447    }
448
449    /// Controls whether Pdfium should always use halftone for image stretching.
450    /// Halftone image stretching is often higher quality than linear image stretching
451    /// but is much slower. The default is `false`.
452    #[inline]
453    pub fn force_half_tone(mut self, do_set_flag: bool) -> Self {
454        self.do_set_flag_render_force_half_tone = do_set_flag;
455
456        self
457    }
458
459    /// Controls whether Pdfium should render for printing. The default is `false`.
460    ///
461    /// Certain PDF files may stipulate different quality settings for on-screen display
462    /// compared to printing. For these files, changing this setting to `true` will result
463    /// in a higher quality rendered bitmap but slower performance. For PDF files that do
464    /// not stipulate different quality settings, changing this setting will have no effect.
465    #[inline]
466    pub fn use_print_quality(mut self, do_set_flag: bool) -> Self {
467        self.do_set_flag_render_for_printing = do_set_flag;
468
469        self
470    }
471
472    /// Controls whether rendered text should be anti-aliased.
473    /// The default is `true`.
474    /// The enabling of LCD-optimized text rendering via a call to
475    /// `PdfiumBitmapConfig::use_lcd_text_rendering(true)` has no effect if this flag
476    /// is set to `false`.
477    #[inline]
478    pub fn set_text_smoothing(mut self, do_set_flag: bool) -> Self {
479        self.do_set_flag_render_no_smooth_text = !do_set_flag;
480
481        self
482    }
483
484    /// Controls whether rendered images should be anti-aliased.
485    /// The default is `true`.
486    #[inline]
487    pub fn set_image_smoothing(mut self, do_set_flag: bool) -> Self {
488        self.do_set_flag_render_no_smooth_image = !do_set_flag;
489
490        self
491    }
492
493    /// Controls whether rendered vector paths should be anti-aliased.
494    /// The default is `true`.
495    #[inline]
496    pub fn set_path_smoothing(mut self, do_set_flag: bool) -> Self {
497        self.do_set_flag_render_no_smooth_path = !do_set_flag;
498
499        self
500    }
501
502    /// Controls whether the byte order of generated image data should be reversed
503    /// during rendering. The default is `true`, so that Pdfium returns pixel data as
504    /// four-channel RGBA rather than its default of four-channel BGRA.
505    ///
506    /// There should generally be no need to change this flag unless you want to do raw
507    /// image processing and specifically need the pixel data returned by the
508    /// [PdfBitmap::as_raw_bytes()] function to be in BGR8 format.
509    #[inline]
510    pub fn set_reverse_byte_order(mut self, do_set_flag: bool) -> Self {
511        self.do_set_flag_reverse_byte_order = do_set_flag;
512
513        self
514    }
515
516    /// Controls whether rendered vector fill paths need to be stroked.
517    /// The default is `false`.
518    #[inline]
519    pub fn render_fills_as_strokes(mut self, do_set_flag: bool) -> Self {
520        self.do_set_flag_convert_fill_to_stroke = do_set_flag;
521
522        self
523    }
524
525    /// Highlights all rendered form fields with the given color.
526    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
527    #[inline]
528    pub fn highlight_all_form_fields(self, color: PdfColor) -> Self {
529        self.highlight_form_fields_of_type(PdfFormFieldType::Unknown, color)
530    }
531
532    /// Highlights all rendered push button form fields with the given color.
533    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
534    #[inline]
535    pub fn highlight_button_form_fields(self, color: PdfColor) -> Self {
536        self.highlight_form_fields_of_type(PdfFormFieldType::PushButton, color)
537    }
538
539    /// Highlights all rendered checkbox form fields with the given color.
540    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
541    #[inline]
542    pub fn highlight_checkbox_form_fields(self, color: PdfColor) -> Self {
543        self.highlight_form_fields_of_type(PdfFormFieldType::Checkbox, color)
544    }
545
546    /// Highlights all rendered radio button form fields with the given color.
547    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
548    #[inline]
549    pub fn highlight_radio_button_form_fields(self, color: PdfColor) -> Self {
550        self.highlight_form_fields_of_type(PdfFormFieldType::RadioButton, color)
551    }
552
553    /// Highlights all rendered combobox form fields with the given color.
554    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
555    #[inline]
556    pub fn highlight_combobox_form_fields(self, color: PdfColor) -> Self {
557        self.highlight_form_fields_of_type(PdfFormFieldType::ComboBox, color)
558    }
559
560    /// Highlights all rendered listbox form fields with the given color.
561    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
562    #[inline]
563    pub fn highlight_listbox_form_fields(self, color: PdfColor) -> Self {
564        self.highlight_form_fields_of_type(PdfFormFieldType::ListBox, color)
565    }
566
567    /// Highlights all rendered text entry form fields with the given color.
568    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
569    #[inline]
570    pub fn highlight_text_form_fields(self, color: PdfColor) -> Self {
571        self.highlight_form_fields_of_type(PdfFormFieldType::Text, color)
572    }
573
574    /// Highlights all rendered signature form fields with the given color.
575    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
576    #[inline]
577    pub fn highlight_signature_form_fields(self, color: PdfColor) -> Self {
578        self.highlight_form_fields_of_type(PdfFormFieldType::Signature, color)
579    }
580
581    /// Highlights all rendered form fields matching the given type with the given color.
582    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
583    #[inline]
584    pub fn highlight_form_fields_of_type(
585        mut self,
586        form_field_type: PdfFormFieldType,
587        color: PdfColor,
588    ) -> Self {
589        if let Some(form_field_highlight) = self.form_field_highlight.as_mut() {
590            form_field_highlight.push((form_field_type, color));
591        } else {
592            self.form_field_highlight = Some(vec![(form_field_type, color)]);
593        }
594
595        self
596    }
597
598    create_transform_setters!(
599        Self,
600        Result<Self, PdfiumError>,
601        "the [PdfPage] during rendering",
602        "the [PdfPage] during rendering.",
603        "the [PdfPage] during rendering,",
604        "Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
605            a custom transformation matrix, but not both at the same time. Applying any transformation
606            automatically disables rendering of form data. If you must render form data while simultaneously
607            applying transformations, consider using the [PdfPage::flatten()] function to flatten the
608            form elements and form data into the containing page."
609    );
610
611    // The internal implementation of the transform() function used by the create_transform_setters!() macro.
612    fn transform_impl(
613        mut self,
614        a: PdfMatrixValue,
615        b: PdfMatrixValue,
616        c: PdfMatrixValue,
617        d: PdfMatrixValue,
618        e: PdfMatrixValue,
619        f: PdfMatrixValue,
620    ) -> Result<Self, PdfiumError> {
621        let result = self
622            .transformation_matrix
623            .multiply(PdfMatrix::new(a, b, c, d, e, f));
624
625        if result.determinant() == 0.0 {
626            Err(PdfiumError::InvalidTransformationMatrix)
627        } else {
628            self.transformation_matrix = result;
629            self.do_render_form_data = false;
630
631            Ok(self)
632        }
633    }
634
635    // The internal implementation of the reset_matrix() function used by the create_transform_setters!() macro.
636    fn reset_matrix_impl(mut self, matrix: PdfMatrix) -> Result<Self, PdfiumError> {
637        self.transformation_matrix = matrix;
638
639        Ok(self)
640    }
641
642    /// Clips rendering output to the given pixel coordinates. Pdfium will not render outside
643    /// the clipping area; any existing image data in the destination [PdfBitmap] will remain
644    /// intact.
645    ///
646    /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ clipping rendering
647    /// output, but not both at the same time. Applying a clipping rectangle automatically disables
648    /// rendering of form data. If you must render form data while simultaneously applying a
649    /// clipping rectangle, consider using the [PdfPage::flatten()] function to flatten the
650    /// form elements and form data into the containing page.
651    #[inline]
652    pub fn clip(mut self, left: Pixels, top: Pixels, right: Pixels, bottom: Pixels) -> Self {
653        self.clip_rect = Some((left, top, right, bottom));
654        self.do_render_form_data = false;
655
656        self
657    }
658
659    /// Sets the position of the page's top-left corner within the destination [PdfBitmap],
660    /// in bitmap pixel coordinates.
661    ///
662    /// The default is `(0, 0)`, which renders the page's top-left corner at the bitmap's
663    /// top-left corner. Negative offsets push the page off the bitmap's top-left edge;
664    /// positive offsets push it down and right. The rendered page is clipped to the
665    /// destination bitmap's own width and height, so a strip-sized destination bitmap
666    /// combined with a negative `y` offset can be used to render a horizontal strip from
667    /// a large page without having to render the page in its entirety.
668    ///
669    /// To render rows `[y_offset, y_offset + strip_height)` of a page into a strip-sized
670    /// destination bitmap, call `set_origin(0, -y_offset)` and pass a destination bitmap
671    /// with a height of `strip_height`.
672    ///
673    /// Pdfium's rendering pipeline _either_ rendering with form data _or_ rendering with
674    /// a custom transformation matrix, but not both at the same time. Since `set_origin()`
675    /// affects rendering with form data, it is disabled automatically on applying any
676    /// transformation.
677    #[inline]
678    pub fn set_origin(mut self, left: Pixels, top: Pixels) -> Self {
679        self.start_x = left;
680        self.start_y = top;
681
682        self
683    }
684
685    /// Computes the pixel dimensions and rotation settings for the given [PdfPage]
686    /// based on the configuration of this [PdfRenderConfig].
687    #[inline]
688    pub(crate) fn apply_to_page(&self, page: &PdfPage) -> PdfPageRenderSettings {
689        let source_width = page.width();
690
691        let source_height = page.height();
692
693        let source_orientation =
694            PdfPageOrientation::from_width_and_height(source_width, source_height);
695
696        // Do we need to apply any rotation?
697
698        let (target_rotation, do_rotate_constraints) = if source_orientation == Portrait
699            && self.portrait_rotation != PdfPageRenderRotation::None
700        {
701            (
702                self.portrait_rotation,
703                self.portrait_rotation_do_rotate_constraints,
704            )
705        } else if source_orientation == Landscape
706            && self.landscape_rotation != PdfPageRenderRotation::None
707        {
708            (
709                self.landscape_rotation,
710                self.landscape_rotation_do_rotate_constraints,
711            )
712        } else {
713            (PdfPageRenderRotation::None, false)
714        };
715
716        let (output_width, output_height, width_scale, height_scale) = if self.use_auto_scaling {
717            // Compute output width and height based on target sizes and page dimensions.
718
719            let width_scale = if let Some(scale) = self.scale_width_factor {
720                Some(scale)
721            } else {
722                self.target_width
723                    .map(|target| (target as f32) / source_width.value)
724            };
725
726            let height_scale = if let Some(scale) = self.scale_height_factor {
727                Some(scale)
728            } else {
729                self.target_height
730                    .map(|target| (target as f32) / source_height.value)
731            };
732
733            // Maintain source aspect ratio if only one dimension's scale is set.
734
735            let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) =
736                match (width_scale, height_scale) {
737                    (Some(width_scale), Some(height_scale)) => {
738                        (width_scale == height_scale, width_scale, height_scale)
739                    }
740                    (Some(width_scale), None) => (true, width_scale, width_scale),
741                    (None, Some(height_scale)) => (true, height_scale, height_scale),
742                    (None, None) => {
743                        // Set default scale to 1.0 if neither dimension is specified.
744
745                        (false, 1.0, 1.0)
746                    }
747                };
748
749            // Apply constraints on maximum width and height, if any.
750
751            let (source_width, source_height, width_constraint, height_constraint) =
752                if do_rotate_constraints {
753                    (
754                        source_height,
755                        source_width,
756                        self.maximum_height,
757                        self.maximum_width,
758                    )
759                } else {
760                    (
761                        source_width,
762                        source_height,
763                        self.maximum_width,
764                        self.maximum_height,
765                    )
766                };
767
768            if let Some(maximum) = width_constraint {
769                let maximum = maximum as f32;
770
771                if source_width.value * width_scale > maximum {
772                    // Constrain the width, so it does not exceed the maximum.
773
774                    width_scale = maximum / source_width.value;
775
776                    if do_maintain_aspect_ratio {
777                        height_scale = width_scale;
778                    }
779                }
780            }
781
782            if let Some(maximum) = height_constraint {
783                let maximum = maximum as f32;
784
785                if source_height.value * height_scale > maximum {
786                    // Constrain the height, so it does not exceed the maximum.
787
788                    height_scale = maximum / source_height.value;
789
790                    if do_maintain_aspect_ratio {
791                        width_scale = height_scale;
792                    }
793                }
794            }
795
796            (
797                (source_width.value * width_scale).round() as c_int,
798                (source_height.value * height_scale).round() as c_int,
799                width_scale,
800                height_scale,
801            )
802        } else {
803            // Take output width and height directly from user's fixed settings.
804
805            (
806                self.fixed_width.unwrap_or(0) as c_int,
807                self.fixed_height.unwrap_or(0) as c_int,
808                self.scale_width_factor.unwrap_or(1.0),
809                self.scale_height_factor.unwrap_or(1.0),
810            )
811        };
812
813        // Compose render flags.
814
815        let mut render_flags = 0;
816
817        if self.do_set_flag_render_annotations {
818            render_flags |= FPDF_ANNOT;
819        }
820
821        if self.do_set_flag_use_lcd_text_rendering {
822            render_flags |= FPDF_LCD_TEXT;
823        }
824
825        if self.do_set_flag_no_native_text {
826            render_flags |= FPDF_NO_NATIVETEXT;
827        }
828
829        if self.do_set_flag_grayscale {
830            render_flags |= FPDF_GRAYSCALE;
831        }
832
833        if self.do_set_flag_render_limited_image_cache {
834            render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
835        }
836
837        if self.do_set_flag_render_force_half_tone {
838            render_flags |= FPDF_RENDER_FORCEHALFTONE;
839        }
840
841        if self.do_set_flag_render_for_printing {
842            render_flags |= FPDF_PRINTING;
843        }
844
845        if self.do_set_flag_render_no_smooth_text {
846            render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
847        }
848
849        if self.do_set_flag_render_no_smooth_image {
850            render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
851        }
852
853        if self.do_set_flag_render_no_smooth_path {
854            render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
855        }
856
857        if self.do_set_flag_reverse_byte_order {
858            render_flags |= FPDF_REVERSE_BYTE_ORDER;
859        }
860
861        if self.do_set_flag_convert_fill_to_stroke {
862            render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
863        }
864
865        // Pages can be rendered either _with_ transformation matrices and clipping
866        // but _without_ form data, or _with_ form data but _without_ transformation matrices
867        // and clipping. We need to be prepared for either option. If rendering of form data
868        // is disabled, then the scaled output width and height and any user-specified
869        // 90-degree rotation need to be applied to the transformation matrix now.
870
871        let transformation_matrix = if !self.do_render_form_data {
872            let result = if target_rotation != PdfPageRenderRotation::None {
873                // Translate the origin to the center of the page before rotating.
874
875                let (delta_x, delta_y) = match target_rotation {
876                    PdfPageRenderRotation::None => unreachable!(),
877                    PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
878                    PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
879                    PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
880                };
881
882                self.transformation_matrix
883                    .translate(delta_x, delta_y)
884                    .and_then(|result| {
885                        result.rotate_clockwise_degrees(target_rotation.as_degrees())
886                    })
887            } else {
888                Ok(self.transformation_matrix)
889            };
890
891            result.and_then(|result| result.scale(width_scale, height_scale))
892        } else {
893            Ok(PdfMatrix::identity())
894        };
895
896        PdfPageRenderSettings {
897            start_x: self.start_x as c_int,
898            start_y: self.start_y as c_int,
899            width: output_width,
900            height: output_height,
901            format: self.format.as_pdfium() as c_int,
902            rotate: target_rotation.as_pdfium(),
903            do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
904            clear_color: self.clear_color.as_pdfium_color(),
905            do_render_form_data: self.do_render_form_data,
906            form_field_highlight: if !self.do_render_form_data
907                || self.form_field_highlight.is_none()
908            {
909                None
910            } else {
911                Some(
912                    self.form_field_highlight
913                        .as_ref()
914                        .unwrap()
915                        .iter()
916                        .map(|(form_field_type, color)| {
917                            (
918                                form_field_type.as_pdfium() as c_int,
919                                color.as_pdfium_color_with_alpha(),
920                            )
921                        })
922                        .collect::<Vec<_>>(),
923                )
924            },
925            matrix: transformation_matrix
926                .unwrap_or(PdfMatrix::IDENTITY)
927                .as_pdfium(),
928            clipping: if let Some((left, top, right, bottom)) = self.clip_rect {
929                FS_RECTF {
930                    left: left as f32,
931                    top: top as f32,
932                    right: right as f32,
933                    bottom: bottom as f32,
934                }
935            } else {
936                FS_RECTF {
937                    left: 0.0,
938                    top: 0.0,
939                    right: output_width as f32,
940                    bottom: output_height as f32,
941                }
942            },
943            render_flags: render_flags as c_int,
944            is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
945        }
946    }
947}
948
949impl Default for PdfRenderConfig {
950    #[inline]
951    fn default() -> Self {
952        PdfRenderConfig::new()
953    }
954}
955
956/// Finalized rendering settings, ready to be passed to a Pdfium rendering function.
957/// Generated by calling [PdfRenderConfig::apply_to_page()].
958#[derive(Debug, Clone)]
959pub(crate) struct PdfPageRenderSettings {
960    pub(crate) start_x: c_int,
961    pub(crate) start_y: c_int,
962    pub(crate) width: c_int,
963    pub(crate) height: c_int,
964    pub(crate) format: c_int,
965    pub(crate) rotate: c_int,
966    pub(crate) do_clear_bitmap_before_rendering: bool,
967    pub(crate) clear_color: FPDF_DWORD,
968    pub(crate) do_render_form_data: bool,
969    pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
970    pub(crate) matrix: FS_MATRIX,
971    pub(crate) clipping: FS_RECTF,
972    pub(crate) render_flags: c_int,
973    pub(crate) is_reversed_byte_order_flag_set: bool,
974}
975
976#[cfg(test)]
977mod tests {
978    use crate::prelude::*;
979    use crate::utils::test::test_bind_to_pdfium; // Temporary until PdfParagraph is included in the prelude.
980
981    #[test]
982    fn test_set_origin_default_settings_zero() -> Result<(), PdfiumError> {
983        let render_settings = get_render_settings_from_config(PdfRenderConfig::new())?;
984
985        assert_eq!(render_settings.start_x, 0);
986        assert_eq!(render_settings.start_y, 0);
987
988        Ok(())
989    }
990
991    #[test]
992    fn test_set_origin_negative_offsets_pass_through() -> Result<(), PdfiumError> {
993        let render_settings =
994            get_render_settings_from_config(PdfRenderConfig::new().set_origin(-50, -400))?;
995
996        assert_eq!(render_settings.start_x, -50);
997        assert_eq!(render_settings.start_y, -400);
998
999        Ok(())
1000    }
1001
1002    #[test]
1003    fn test_set_origin_positive_offsets_pass_through() -> Result<(), PdfiumError> {
1004        let render_settings =
1005            get_render_settings_from_config(PdfRenderConfig::new().set_origin(75, 125))?;
1006
1007        assert_eq!(render_settings.start_x, 75);
1008        assert_eq!(render_settings.start_y, 125);
1009
1010        Ok(())
1011    }
1012
1013    #[test]
1014    fn test_set_origin_strips_stitch_to_full_page() -> Result<(), PdfiumError> {
1015        // Compare a full page render of a sample page against a render assembled
1016        // from multiple stripped renders. The results should be identical.
1017
1018        let pdfium = test_bind_to_pdfium();
1019
1020        let document = pdfium.load_pdf_from_file("./test/image-test.pdf", None)?;
1021        let page = document.pages().first()?;
1022
1023        // `image-test.pdf` is single-page A4: 595 × 842 points at 72 DPI outputs to
1024        // 595 × 842 pixels when rendered with `set_target_size(595, 842)`.
1025
1026        let target_width: Pixels = 1000;
1027        let target_height: Pixels = 800;
1028        let strips: i32 = 4; // The number of render passes, with each pass outputting a single strip
1029        let strip_height: Pixels = target_height / strips as Pixels;
1030
1031        assert_eq!(strip_height * strips, target_height);
1032
1033        // First, create a full page render of the target page.
1034
1035        let full_bitmap =
1036            page.render_with_config(&PdfRenderConfig::new().set_fixed_size(target_width, target_height))?;
1037        let full_bytes = full_bitmap.as_image()?.to_rgba8().into_raw();
1038        let row_bytes = (target_width as usize) * 4;
1039
1040        assert_eq!(full_bytes.len(), row_bytes * (target_height as usize));
1041
1042        // Next, render the page in strips...
1043
1044        let mut stitched: Vec<u8> = Vec::with_capacity(full_bytes.len());
1045
1046        for i in 0..strips {
1047            let mut strip_bitmap = PdfBitmap::empty(target_width, strip_height, full_bitmap.format()?)?;
1048
1049            page.render_into_bitmap_with_config(
1050                &mut strip_bitmap,
1051                &&PdfRenderConfig::new()
1052                    .set_fixed_size(target_width, target_height)
1053                    .set_origin(0, -(i * strip_height)),
1054            )?;
1055
1056            // ... joining each strip in memory to build a complete rendered image.
1057
1058            stitched.extend_from_slice(strip_bitmap.as_image()?.to_rgba8().as_raw());
1059        }
1060
1061        // The render output assembled from the strips should exactly match the full page render.
1062
1063        println!("{}, {}, {}, {}", strip_height, strip_height * strips, target_height, row_bytes);
1064        assert_eq!(stitched.len(), full_bytes.len());
1065
1066        let mut sums = [0u64; 4]; // Track per-channel mean drift between stitched and full-page renders.
1067        let pixel_count = full_bytes.len() / 4;
1068
1069        for px in 0..pixel_count {
1070            for ch in 0..4 {
1071                let a = stitched[px * 4 + ch] as i32;
1072                let b = full_bytes[px * 4 + ch] as i32;
1073                sums[ch] += (a - b).unsigned_abs() as u64;
1074            }
1075        }
1076
1077        let n = pixel_count as f64;
1078        let max_drift = (0..4).map(|ch| sums[ch] as f64 / n).fold(0.0_f64, f64::max);
1079
1080        assert!(
1081            max_drift < 5.0,
1082            "stitched-vs-full per-channel mean drift {:.3}/255 exceeds tolerance",
1083            max_drift
1084        );
1085
1086        Ok(())
1087    }
1088
1089    #[test]
1090    fn test_fixed_size_render_config() -> Result<(), PdfiumError> {
1091        let render_settings =
1092            get_render_settings_from_config(PdfRenderConfig::new().set_fixed_size(2000, 2000))?;
1093
1094        assert_eq!(render_settings.width, 2000);
1095        assert_eq!(render_settings.height, 2000);
1096
1097        // Applying scaling does not affect the rendered bitmap size.
1098
1099        let render_settings = get_render_settings_from_config(
1100            PdfRenderConfig::new()
1101                .set_fixed_size(2000, 2000)
1102                .scale_page_by_factor(5.0),
1103        )?;
1104
1105        assert_eq!(render_settings.width, 2000);
1106        assert_eq!(render_settings.height, 2000);
1107
1108        Ok(())
1109    }
1110
1111    #[test]
1112    fn test_target_size_render_config() -> Result<(), PdfiumError> {
1113        let render_settings = get_render_settings_from_config(
1114            PdfRenderConfig::new().scale_page_to_display_size(2000, 2000),
1115        )?;
1116
1117        assert_eq!(render_settings.width, 1414);
1118        assert_eq!(render_settings.height, 2000);
1119
1120        // Applying scaling does affected the rendered bitmap size.
1121
1122        let render_settings = get_render_settings_from_config(
1123            PdfRenderConfig::new()
1124                .set_target_size(2000, 2000)
1125                .scale_page_by_factor(5.0),
1126        )?;
1127
1128        assert_eq!(render_settings.width, 2976);
1129        assert_eq!(render_settings.height, 4209);
1130
1131        Ok(())
1132    }
1133
1134    fn get_render_settings_from_config(
1135        config: PdfRenderConfig,
1136    ) -> Result<PdfPageRenderSettings, PdfiumError> {
1137        let pdfium = test_bind_to_pdfium();
1138
1139        let mut document = pdfium.create_new_pdf()?;
1140        let page = document
1141            .pages_mut()
1142            .create_page_at_start(PdfPagePaperSize::Portrait(PdfPagePaperStandardSize::A4))?;
1143
1144        Ok(config.apply_to_page(&page))
1145    }
1146}