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::{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#[cfg(doc)]
22use crate::pdf::bitmap::PdfBitmap;
23
24// TODO: AJRC - 29/7/22 - remove deprecated PdfBitmapConfig struct in 0.9.0 as part of tracking issue
25// https://github.com/ajrcarey/pdfium-render/issues/36
26#[deprecated(
27    since = "0.7.12",
28    note = "This struct has been renamed to better reflect its purpose. Use the PdfRenderConfig struct instead."
29)]
30#[doc(hidden)]
31pub struct PdfBitmapConfig {}
32
33#[allow(deprecated)]
34impl PdfBitmapConfig {
35    /// Creates a new [PdfRenderConfig] object with all settings initialized with their default values.
36    #[deprecated(
37        since = "0.7.12",
38        note = "This struct has been renamed to better reflect its purpose. Use the PdfRenderConfig::new() function instead."
39    )]
40    #[inline]
41    #[doc(hidden)]
42    #[allow(clippy::new_ret_no_self)]
43    pub fn new() -> PdfRenderConfig {
44        PdfRenderConfig::new()
45    }
46
47    #[deprecated(
48        since = "0.7.12",
49        note = "This struct has been renamed to better reflect its purpose. Use the PdfRenderConfig::default() function instead."
50    )]
51    #[inline]
52    #[doc(hidden)]
53    #[allow(clippy::should_implement_trait)]
54    pub fn default() -> PdfRenderConfig {
55        PdfRenderConfig::default()
56    }
57}
58
59/// Configures the scaling, rotation, and rendering settings that should be applied to
60/// a [PdfPage] to create a [PdfBitmap] for that page. [PdfRenderConfig] can accommodate pages of
61/// different sizes while correctly maintaining each page's aspect ratio, automatically
62/// rotate portrait or landscape pages, generate page thumbnails, apply maximum and
63/// minimum pixel sizes to the scaled width and height of the final bitmap, highlight form fields
64/// with different colors, apply custom transforms to the page during rendering, and set
65/// internal Pdfium rendering flags.
66///
67/// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
68/// a custom transformation matrix, but not both at the same time. Applying any transformation
69/// automatically disables rendering of form data. If you must render form data while simultaneously
70/// applying transformations, consider using the [PdfPage::flatten()] function to flatten the
71/// form elements and form data into the containing page.
72pub struct PdfRenderConfig {
73    target_width: Option<Pixels>,
74    target_height: Option<Pixels>,
75    scale_width_factor: Option<f32>,
76    scale_height_factor: Option<f32>,
77    maximum_width: Option<Pixels>,
78    maximum_height: Option<Pixels>,
79    portrait_rotation: PdfPageRenderRotation,
80    portrait_rotation_do_rotate_constraints: bool,
81    landscape_rotation: PdfPageRenderRotation,
82    landscape_rotation_do_rotate_constraints: bool,
83    format: PdfBitmapFormat,
84    do_clear_bitmap_before_rendering: bool,
85    clear_color: PdfColor,
86    do_render_form_data: bool,
87    form_field_highlight: Option<Vec<(PdfFormFieldType, PdfColor)>>,
88    transformation_matrix: PdfMatrix,
89    clip_rect: Option<(Pixels, Pixels, Pixels, Pixels)>,
90
91    // The fields below set Pdfium's page rendering flags. Coverage for the
92    // FPDF_DEBUG_INFO and FPDF_NO_CATCH flags is omitted since they are obsolete.
93    do_set_flag_render_annotations: bool,     // Sets FPDF_ANNOT
94    do_set_flag_use_lcd_text_rendering: bool, // Sets FPDF_LCD_TEXT
95    do_set_flag_no_native_text: bool,         // Sets FPDF_NO_NATIVETEXT
96    do_set_flag_grayscale: bool,              // Sets FPDF_GRAYSCALE
97    do_set_flag_render_limited_image_cache: bool, // Sets FPDF_RENDER_LIMITEDIMAGECACHE
98    do_set_flag_render_force_half_tone: bool, // Sets FPDF_RENDER_FORCEHALFTONE
99    do_set_flag_render_for_printing: bool,    // Sets FPDF_PRINTING
100    do_set_flag_render_no_smooth_text: bool,  // Sets FPDF_RENDER_NO_SMOOTHTEXT
101    do_set_flag_render_no_smooth_image: bool, // Sets FPDF_RENDER_NO_SMOOTHIMAGE
102    do_set_flag_render_no_smooth_path: bool,  // Sets FPDF_RENDER_NO_SMOOTHPATH
103    do_set_flag_reverse_byte_order: bool,     // Sets FPDF_REVERSE_BYTE_ORDER
104    do_set_flag_convert_fill_to_stroke: bool, // Sets FPDF_CONVERT_FILL_TO_STROKE
105}
106
107impl PdfRenderConfig {
108    /// Creates a new [PdfRenderConfig] object with all settings initialized with their default values.
109    pub fn new() -> Self {
110        PdfRenderConfig {
111            target_width: None,
112            target_height: None,
113            scale_width_factor: None,
114            scale_height_factor: None,
115            maximum_width: None,
116            maximum_height: None,
117            portrait_rotation: PdfPageRenderRotation::None,
118            portrait_rotation_do_rotate_constraints: false,
119            landscape_rotation: PdfPageRenderRotation::None,
120            landscape_rotation_do_rotate_constraints: false,
121            format: PdfBitmapFormat::default(),
122            do_clear_bitmap_before_rendering: true,
123            clear_color: PdfColor::WHITE,
124            do_render_form_data: true,
125            form_field_highlight: None,
126            transformation_matrix: PdfMatrix::IDENTITY,
127            clip_rect: None,
128            do_set_flag_render_annotations: true,
129            do_set_flag_use_lcd_text_rendering: false,
130            do_set_flag_no_native_text: false,
131            do_set_flag_grayscale: false,
132            do_set_flag_render_limited_image_cache: false,
133            do_set_flag_render_force_half_tone: false,
134            do_set_flag_render_for_printing: false,
135            do_set_flag_render_no_smooth_text: false,
136            do_set_flag_render_no_smooth_image: false,
137            do_set_flag_render_no_smooth_path: false,
138            do_set_flag_convert_fill_to_stroke: false,
139
140            // We ask Pdfium to reverse its bitmap byte order from BGR8 to RGB8 in order
141            // to make working with Image::DynamicImage easier after version 0.24. See:
142            // https://github.com/ajrcarey/pdfium-render/issues/9
143            do_set_flag_reverse_byte_order: true,
144        }
145    }
146
147    /// Applies settings suitable for generating a thumbnail.
148    ///
149    /// * The source [PdfPage] will be rendered with a maximum width and height of the given
150    ///   pixel size.
151    /// * The page will not be rotated, irrespective of its orientation.
152    /// * Image quality settings will be reduced to improve performance.
153    /// * Annotations and user-filled form field data will not be rendered.
154    ///
155    /// These settings are applied to this [PdfRenderConfig] object immediately and can be
156    /// selectively overridden by later function calls. For instance, a later call to
157    /// [PdfRenderConfig::rotate()] can specify a custom rotation setting that will apply
158    /// to the thumbnail.
159    #[inline]
160    pub fn thumbnail(self, size: Pixels) -> Self {
161        self.set_target_size(size, size)
162            .set_maximum_width(size)
163            .set_maximum_height(size)
164            .rotate(PdfPageRenderRotation::None, false)
165            .use_print_quality(false)
166            .set_image_smoothing(false)
167            .render_annotations(false)
168            .render_form_data(false)
169    }
170
171    /// Converts the width and height of a [PdfPage] from points to pixels, scaling each
172    /// dimension to the given target pixel sizes. The aspect ratio of the source page
173    /// will not be maintained.
174    #[inline]
175    pub fn set_target_size(self, width: Pixels, height: Pixels) -> Self {
176        self.set_target_width(width).set_target_height(height)
177    }
178
179    /// Converts the width of a [PdfPage] from points to pixels, scaling the source page
180    /// width to the given target pixel width. The aspect ratio of the source page
181    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
182    /// or [PdfRenderConfig::set_target_height()] that overrides it.
183    #[inline]
184    pub fn set_target_width(mut self, width: Pixels) -> Self {
185        self.target_width = Some(width);
186
187        self
188    }
189
190    /// Converts the height of a [PdfPage] from points to pixels, scaling the source page
191    /// height to the given target pixel height. The aspect ratio of the source page
192    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
193    /// or [PdfRenderConfig::set_target_width()] that overrides it.
194    #[inline]
195    pub fn set_target_height(mut self, height: Pixels) -> Self {
196        self.target_height = Some(height);
197
198        self
199    }
200
201    /// Applies settings to this [PdfRenderConfig] suitable for filling the given
202    /// screen display size.
203    ///
204    /// The source page's dimensions will be scaled so that both width and height attempt
205    /// to fill, but do not exceed, the given pixel dimensions. The aspect ratio of the
206    /// source page will be maintained. Landscape pages will be automatically rotated
207    /// by 90 degrees and will be scaled down if necessary to fit the display width.
208    #[inline]
209    pub fn scale_page_to_display_size(mut self, width: Pixels, height: Pixels) -> Self {
210        self.scale_width_factor = None;
211        self.scale_height_factor = None;
212
213        self.set_target_width(width)
214            .set_maximum_width(width)
215            .set_maximum_height(height)
216            .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true)
217    }
218
219    /// Converts the width and height of a [PdfPage] from points to pixels by applying
220    /// the given scale factor to both dimensions. The aspect ratio of the source page
221    /// will be maintained. Overrides any previous call to [PdfRenderConfig::scale_page_by_factor()],
222    /// [PdfRenderConfig::scale_page_width_by_factor()], or [PdfRenderConfig::scale_page_height_by_factor()].
223    #[inline]
224    pub fn scale_page_by_factor(self, scale: f32) -> Self {
225        let result = self.scale_page_width_by_factor(scale);
226
227        result.scale_page_height_by_factor(scale)
228    }
229
230    /// Converts the width of the [PdfPage] from points to pixels by applying the given
231    /// scale factor. The aspect ratio of the source page will not be maintained if a
232    /// different scale factor is applied to the height. Overrides any previous call to
233    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
234    /// or [PdfRenderConfig::scale_page_height_by_factor()].
235    #[inline]
236    pub fn scale_page_width_by_factor(mut self, scale: f32) -> Self {
237        self.scale_width_factor = Some(scale);
238
239        self
240    }
241
242    /// Converts the height of the [PdfPage] from points to pixels by applying the given
243    /// scale factor. The aspect ratio of the source page will not be maintained if a
244    /// different scale factor is applied to the width. Overrides any previous call to
245    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
246    /// or [PdfRenderConfig::scale_page_height_by_factor()].
247    #[inline]
248    pub fn scale_page_height_by_factor(mut self, scale: f32) -> Self {
249        self.scale_height_factor = Some(scale);
250
251        self
252    }
253
254    /// Specifies that the final pixel width of the [PdfPage] will not exceed the given maximum.
255    #[inline]
256    pub fn set_maximum_width(mut self, width: Pixels) -> Self {
257        self.maximum_width = Some(width);
258
259        self
260    }
261
262    /// Specifies that the final pixel height of the [PdfPage] will not exceed the given maximum.
263    #[inline]
264    pub fn set_maximum_height(mut self, height: Pixels) -> Self {
265        self.maximum_height = Some(height);
266
267        self
268    }
269
270    /// Applies the given clockwise rotation setting to the [PdfPage] during rendering, irrespective
271    /// of its orientation. If the given flag is set to `true` then any maximum
272    /// constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
273    /// will be rotated so it becomes a constraint on the final pixel height, and any
274    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
275    /// will be rotated so it becomes a constraint on the final pixel width.
276    #[inline]
277    pub fn rotate(self, rotation: PdfPageRenderRotation, do_rotate_constraints: bool) -> Self {
278        self.rotate_if_portrait(rotation, do_rotate_constraints)
279            .rotate_if_landscape(rotation, do_rotate_constraints)
280    }
281
282    // TODO: AJRC - 30/7/22 - remove deprecated rotate_if_portait() function in 0.9.0 as part
283    // of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
284    /// Applies the given clockwise rotation settings to the [PdfPage] during rendering, if the page
285    /// is in portrait orientation. If the given flag is set to `true` and the given
286    /// rotation setting is [PdfBitmapRotation::Degrees90] or [PdfBitmapRotation::Degrees270]
287    /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
288    /// will be rotated so it becomes a constraint on the final pixel height and any
289    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
290    /// will be rotated so it becomes a constraint on the final pixel width.
291    #[deprecated(
292        since = "0.7.12",
293        note = "This function has been renamed to correct a typo. Use the PdfRenderConfig::rotate_if_portrait() function instead."
294    )]
295    #[doc(hidden)]
296    #[inline]
297    pub fn rotate_if_portait(
298        self,
299        rotation: PdfPageRenderRotation,
300        do_rotate_constraints: bool,
301    ) -> Self {
302        self.rotate_if_portrait(rotation, do_rotate_constraints)
303    }
304
305    /// Applies the given clockwise rotation settings to the [PdfPage] during rendering, if the page
306    /// is in portrait orientation. If the given flag is set to `true` and the given
307    /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
308    /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
309    /// will be rotated so it becomes a constraint on the final pixel height and any
310    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
311    /// will be rotated so it becomes a constraint on the final pixel width.
312    #[inline]
313    pub fn rotate_if_portrait(
314        mut self,
315        rotation: PdfPageRenderRotation,
316        do_rotate_constraints: bool,
317    ) -> Self {
318        self.portrait_rotation = rotation;
319
320        if rotation == PdfPageRenderRotation::Degrees90
321            || rotation == PdfPageRenderRotation::Degrees270
322        {
323            self.portrait_rotation_do_rotate_constraints = do_rotate_constraints;
324        }
325
326        self
327    }
328
329    /// Applies the given rotation settings to the [PdfPage] during rendering, if the page
330    /// is in landscape orientation. If the given flag is set to `true` and the given
331    /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
332    /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
333    /// will be rotated so it becomes a constraint on the final pixel height and any
334    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
335    /// will be rotated so it becomes a constraint on the final pixel width.
336    #[inline]
337    pub fn rotate_if_landscape(
338        mut self,
339        rotation: PdfPageRenderRotation,
340        do_rotate_constraints: bool,
341    ) -> Self {
342        self.landscape_rotation = rotation;
343
344        if rotation == PdfPageRenderRotation::Degrees90
345            || rotation == PdfPageRenderRotation::Degrees270
346        {
347            self.landscape_rotation_do_rotate_constraints = do_rotate_constraints;
348        }
349
350        self
351    }
352
353    /// Sets the pixel format that will be used during rendering of the [PdfPage].
354    /// The default is [PdfBitmapFormat::BGRA].
355    #[inline]
356    pub fn set_format(mut self, format: PdfBitmapFormat) -> Self {
357        self.format = format;
358
359        self
360    }
361
362    /// Controls whether the destination bitmap should be cleared by setting every pixel to a
363    /// known color value before rendering the [PdfPage]. The default is `true`.
364    /// The color used during clearing can be customised by calling [PdfRenderConfig::set_clear_color()].
365    #[inline]
366    pub fn clear_before_rendering(mut self, do_clear: bool) -> Self {
367        self.do_clear_bitmap_before_rendering = do_clear;
368
369        self
370    }
371
372    /// Sets the color applied to every pixel in the destination bitmap when clearing the bitmap
373    /// before rendering the [PdfPage]. The default is [PdfColor::WHITE]. This setting
374    /// has no effect if [PdfRenderConfig::clear_before_rendering()] is set to `false`.
375    #[inline]
376    pub fn set_clear_color(mut self, color: PdfColor) -> Self {
377        self.clear_color = color;
378
379        self
380    }
381
382    /// Controls whether form data widgets and user-supplied form data should be included
383    /// during rendering of the [PdfPage]. The default is `true`.
384    ///
385    /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
386    /// a custom transformation matrix, but not both at the same time. Applying any transformation
387    /// automatically sets this value to `false`, disabling rendering of form data.
388    #[inline]
389    pub fn render_form_data(mut self, do_render: bool) -> Self {
390        self.do_render_form_data = do_render;
391
392        self
393    }
394
395    /// Controls whether user-supplied annotations should be included during rendering of
396    /// the [PdfPage]. The default is `true`.
397    #[inline]
398    pub fn render_annotations(mut self, do_render: bool) -> Self {
399        self.do_set_flag_render_annotations = do_render;
400
401        self
402    }
403
404    /// Controls whether text rendering should be optimized for LCD display.
405    /// The default is `false`.
406    /// Has no effect if anti-aliasing of text has been disabled by a call to
407    /// `PdfRenderConfig::set_text_smoothing(false)`.
408    #[inline]
409    pub fn use_lcd_text_rendering(mut self, do_set_flag: bool) -> Self {
410        self.do_set_flag_use_lcd_text_rendering = do_set_flag;
411
412        self
413    }
414
415    /// Controls whether platform text rendering should be disabled on platforms that support it.
416    /// The alternative is for Pdfium to render all text internally, which may give more
417    /// consistent rendering results across platforms but may also be slower.
418    /// The default is `false`.
419    #[inline]
420    pub fn disable_native_text_rendering(mut self, do_set_flag: bool) -> Self {
421        self.do_set_flag_no_native_text = do_set_flag;
422
423        self
424    }
425
426    /// Controls whether rendering output should be grayscale rather than full color.
427    /// The default is `false`.
428    #[inline]
429    pub fn use_grayscale_rendering(mut self, do_set_flag: bool) -> Self {
430        self.do_set_flag_grayscale = do_set_flag;
431
432        self
433    }
434
435    /// Controls whether Pdfium should limit its image cache size during rendering.
436    /// A smaller cache size may result in lower memory usage at the cost of slower rendering.
437    /// The default is `false`.
438    #[inline]
439    pub fn limit_render_image_cache_size(mut self, do_set_flag: bool) -> Self {
440        self.do_set_flag_render_limited_image_cache = do_set_flag;
441
442        self
443    }
444
445    /// Controls whether Pdfium should always use halftone for image stretching.
446    /// Halftone image stretching is often higher quality than linear image stretching
447    /// but is much slower. The default is `false`.
448    #[inline]
449    pub fn force_half_tone(mut self, do_set_flag: bool) -> Self {
450        self.do_set_flag_render_force_half_tone = do_set_flag;
451
452        self
453    }
454
455    /// Controls whether Pdfium should render for printing. The default is `false`.
456    ///
457    /// Certain PDF files may stipulate different quality settings for on-screen display
458    /// compared to printing. For these files, changing this setting to `true` will result
459    /// in a higher quality rendered bitmap but slower performance. For PDF files that do
460    /// not stipulate different quality settings, changing this setting will have no effect.
461    #[inline]
462    pub fn use_print_quality(mut self, do_set_flag: bool) -> Self {
463        self.do_set_flag_render_for_printing = do_set_flag;
464
465        self
466    }
467
468    /// Controls whether rendered text should be anti-aliased.
469    /// The default is `true`.
470    /// The enabling of LCD-optimized text rendering via a call to
471    /// `PdfiumBitmapConfig::use_lcd_text_rendering(true)` has no effect if this flag
472    /// is set to `false`.
473    #[inline]
474    pub fn set_text_smoothing(mut self, do_set_flag: bool) -> Self {
475        self.do_set_flag_render_no_smooth_text = !do_set_flag;
476
477        self
478    }
479
480    /// Controls whether rendered images should be anti-aliased.
481    /// The default is `true`.
482    #[inline]
483    pub fn set_image_smoothing(mut self, do_set_flag: bool) -> Self {
484        self.do_set_flag_render_no_smooth_image = !do_set_flag;
485
486        self
487    }
488
489    /// Controls whether rendered vector paths should be anti-aliased.
490    /// The default is `true`.
491    #[inline]
492    pub fn set_path_smoothing(mut self, do_set_flag: bool) -> Self {
493        self.do_set_flag_render_no_smooth_path = !do_set_flag;
494
495        self
496    }
497
498    /// Controls whether the byte order of generated image data should be reversed
499    /// during rendering. The default is `true`, so that Pdfium returns pixel data as
500    /// four-channel RGBA rather than its default of four-channel BGRA.
501    ///
502    /// There should generally be no need to change this flag unless you want to do raw
503    /// image processing and specifically need the pixel data returned by the
504    /// [PdfBitmap::as_raw_bytes()] function to be in BGR8 format.
505    #[inline]
506    pub fn set_reverse_byte_order(mut self, do_set_flag: bool) -> Self {
507        self.do_set_flag_reverse_byte_order = do_set_flag;
508
509        self
510    }
511
512    /// Controls whether rendered vector fill paths need to be stroked.
513    /// The default is `false`.
514    #[inline]
515    pub fn render_fills_as_strokes(mut self, do_set_flag: bool) -> Self {
516        self.do_set_flag_convert_fill_to_stroke = do_set_flag;
517
518        self
519    }
520
521    /// Highlights all rendered form fields with the given color.
522    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
523    #[inline]
524    pub fn highlight_all_form_fields(self, color: PdfColor) -> Self {
525        self.highlight_form_fields_of_type(PdfFormFieldType::Unknown, color)
526    }
527
528    /// Highlights all rendered push button form fields with the given color.
529    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
530    #[inline]
531    pub fn highlight_button_form_fields(self, color: PdfColor) -> Self {
532        self.highlight_form_fields_of_type(PdfFormFieldType::PushButton, color)
533    }
534
535    /// Highlights all rendered checkbox form fields with the given color.
536    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
537    #[inline]
538    pub fn highlight_checkbox_form_fields(self, color: PdfColor) -> Self {
539        self.highlight_form_fields_of_type(PdfFormFieldType::Checkbox, color)
540    }
541
542    /// Highlights all rendered radio button form fields with the given color.
543    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
544    #[inline]
545    pub fn highlight_radio_button_form_fields(self, color: PdfColor) -> Self {
546        self.highlight_form_fields_of_type(PdfFormFieldType::RadioButton, color)
547    }
548
549    /// Highlights all rendered combobox form fields with the given color.
550    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
551    #[inline]
552    pub fn highlight_combobox_form_fields(self, color: PdfColor) -> Self {
553        self.highlight_form_fields_of_type(PdfFormFieldType::ComboBox, color)
554    }
555
556    /// Highlights all rendered listbox form fields with the given color.
557    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
558    #[inline]
559    pub fn highlight_listbox_form_fields(self, color: PdfColor) -> Self {
560        self.highlight_form_fields_of_type(PdfFormFieldType::ListBox, color)
561    }
562
563    /// Highlights all rendered text entry form fields with the given color.
564    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
565    #[inline]
566    pub fn highlight_text_form_fields(self, color: PdfColor) -> Self {
567        self.highlight_form_fields_of_type(PdfFormFieldType::Text, color)
568    }
569
570    /// Highlights all rendered signature form fields with the given color.
571    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
572    #[inline]
573    pub fn highlight_signature_form_fields(self, color: PdfColor) -> Self {
574        self.highlight_form_fields_of_type(PdfFormFieldType::Signature, color)
575    }
576
577    /// Highlights all rendered form fields matching the given type with the given color.
578    /// Note that specifying a solid color with no opacity will overprint any user data in the field.
579    #[inline]
580    pub fn highlight_form_fields_of_type(
581        mut self,
582        form_field_type: PdfFormFieldType,
583        color: PdfColor,
584    ) -> Self {
585        if let Some(form_field_highlight) = self.form_field_highlight.as_mut() {
586            form_field_highlight.push((form_field_type, color));
587        } else {
588            self.form_field_highlight = Some(vec![(form_field_type, color)]);
589        }
590
591        self
592    }
593
594    create_transform_setters!(
595        Self,
596        Result<Self, PdfiumError>,
597        "the [PdfPage] during rendering",
598        "the [PdfPage] during rendering.",
599        "the [PdfPage] during rendering,",
600        "Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
601            a custom transformation matrix, but not both at the same time. Applying any transformation
602            automatically disables rendering of form data. If you must render form data while simultaneously
603            applying transformations, consider using the [PdfPage::flatten()] function to flatten the
604            form elements and form data into the containing page."
605    );
606
607    // The internal implementation of the transform() function used by the create_transform_setters!() macro.
608    fn transform_impl(
609        mut self,
610        a: PdfMatrixValue,
611        b: PdfMatrixValue,
612        c: PdfMatrixValue,
613        d: PdfMatrixValue,
614        e: PdfMatrixValue,
615        f: PdfMatrixValue,
616    ) -> Result<Self, PdfiumError> {
617        let result = self
618            .transformation_matrix
619            .multiply(PdfMatrix::new(a, b, c, d, e, f));
620
621        if result.determinant() == 0.0 {
622            Err(PdfiumError::InvalidTransformationMatrix)
623        } else {
624            self.transformation_matrix = result;
625            self.do_render_form_data = false;
626
627            Ok(self)
628        }
629    }
630
631    // The internal implementation of the reset_matrix() function used by the create_transform_setters!() macro.
632    fn reset_matrix_impl(mut self, matrix: PdfMatrix) -> Result<Self, PdfiumError> {
633        self.transformation_matrix = matrix;
634
635        Ok(self)
636    }
637
638    /// Clips rendering output to the given pixel coordinates. Pdfium will not render outside
639    /// the clipping area; any existing image data in the destination [PdfBitmap] will remain
640    /// intact.
641    ///
642    /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ clipping rendering
643    /// output, but not both at the same time. Applying a clipping rectangle automatically disables
644    /// rendering of form data. If you must render form data while simultaneously applying a
645    /// clipping rectangle, consider using the [PdfPage::flatten()] function to flatten the
646    /// form elements and form data into the containing page.
647    #[inline]
648    pub fn clip(mut self, left: Pixels, top: Pixels, right: Pixels, bottom: Pixels) -> Self {
649        self.clip_rect = Some((left, top, right, bottom));
650        self.do_render_form_data = false;
651
652        self
653    }
654
655    /// Computes the pixel dimensions and rotation settings for the given [PdfPage]
656    /// based on the configuration of this [PdfRenderConfig].
657    #[inline]
658    pub(crate) fn apply_to_page(&self, page: &PdfPage) -> PdfPageRenderSettings {
659        let source_width = page.width();
660
661        let source_height = page.height();
662
663        let source_orientation =
664            PdfPageOrientation::from_width_and_height(source_width, source_height);
665
666        // Do we need to apply any rotation?
667
668        let (target_rotation, do_rotate_constraints) = if source_orientation == Portrait
669            && self.portrait_rotation != PdfPageRenderRotation::None
670        {
671            (
672                self.portrait_rotation,
673                self.portrait_rotation_do_rotate_constraints,
674            )
675        } else if source_orientation == Landscape
676            && self.landscape_rotation != PdfPageRenderRotation::None
677        {
678            (
679                self.landscape_rotation,
680                self.landscape_rotation_do_rotate_constraints,
681            )
682        } else {
683            (PdfPageRenderRotation::None, false)
684        };
685
686        let width_scale = if let Some(scale) = self.scale_width_factor {
687            Some(scale)
688        } else {
689            self.target_width
690                .map(|target| (target as f32) / source_width.value)
691        };
692
693        let height_scale = if let Some(scale) = self.scale_height_factor {
694            Some(scale)
695        } else {
696            self.target_height
697                .map(|target| (target as f32) / source_height.value)
698        };
699
700        // Maintain source aspect ratio if only one dimension's scale is set.
701
702        let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) =
703            match (width_scale, height_scale) {
704                (Some(width_scale), Some(height_scale)) => {
705                    (width_scale == height_scale, width_scale, height_scale)
706                }
707                (Some(width_scale), None) => (true, width_scale, width_scale),
708                (None, Some(height_scale)) => (true, height_scale, height_scale),
709                (None, None) => {
710                    // Set default scale to 1.0 if neither dimension is specified.
711
712                    (false, 1.0, 1.0)
713                }
714            };
715
716        // Apply constraints on maximum width and height, if any.
717
718        let (source_width, source_height, width_constraint, height_constraint) =
719            if do_rotate_constraints {
720                (
721                    source_height,
722                    source_width,
723                    self.maximum_height,
724                    self.maximum_width,
725                )
726            } else {
727                (
728                    source_width,
729                    source_height,
730                    self.maximum_width,
731                    self.maximum_height,
732                )
733            };
734
735        if let Some(maximum) = width_constraint {
736            let maximum = maximum as f32;
737
738            if source_width.value * width_scale > maximum {
739                // Constrain the width, so it does not exceed the maximum.
740
741                width_scale = maximum / source_width.value;
742
743                if do_maintain_aspect_ratio {
744                    height_scale = width_scale;
745                }
746            }
747        }
748
749        if let Some(maximum) = height_constraint {
750            let maximum = maximum as f32;
751
752            if source_height.value * height_scale > maximum {
753                // Constrain the height, so it does not exceed the maximum.
754
755                height_scale = maximum / source_height.value;
756
757                if do_maintain_aspect_ratio {
758                    width_scale = height_scale;
759                }
760            }
761        }
762
763        // Compose render flags.
764
765        let mut render_flags = 0;
766
767        if self.do_set_flag_render_annotations {
768            render_flags |= FPDF_ANNOT;
769        }
770
771        if self.do_set_flag_use_lcd_text_rendering {
772            render_flags |= FPDF_LCD_TEXT;
773        }
774
775        if self.do_set_flag_no_native_text {
776            render_flags |= FPDF_NO_NATIVETEXT;
777        }
778
779        if self.do_set_flag_grayscale {
780            render_flags |= FPDF_GRAYSCALE;
781        }
782
783        if self.do_set_flag_render_limited_image_cache {
784            render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
785        }
786
787        if self.do_set_flag_render_force_half_tone {
788            render_flags |= FPDF_RENDER_FORCEHALFTONE;
789        }
790
791        if self.do_set_flag_render_for_printing {
792            render_flags |= FPDF_PRINTING;
793        }
794
795        if self.do_set_flag_render_no_smooth_text {
796            render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
797        }
798
799        if self.do_set_flag_render_no_smooth_image {
800            render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
801        }
802
803        if self.do_set_flag_render_no_smooth_path {
804            render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
805        }
806
807        if self.do_set_flag_reverse_byte_order {
808            render_flags |= FPDF_REVERSE_BYTE_ORDER;
809        }
810
811        if self.do_set_flag_convert_fill_to_stroke {
812            render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
813        }
814
815        let output_width = (source_width.value * width_scale).round() as c_int;
816
817        let output_height = (source_height.value * height_scale).round() as c_int;
818
819        // Pages can be rendered either _with_ transformation matrices and clipping
820        // but _without_ form data, or _with_ form data but _without_ transformation matrices
821        // and clipping. We need to be prepared for either option. If rendering of form data
822        // is disabled, then the scaled output width and height and any user-specified
823        // 90-degree rotation need to be applied to the transformation matrix now.
824
825        let transformation_matrix = if !self.do_render_form_data {
826            let result = if target_rotation != PdfPageRenderRotation::None {
827                // Translate the origin to the center of the page before rotating.
828
829                let (delta_x, delta_y) = match target_rotation {
830                    PdfPageRenderRotation::None => unreachable!(),
831                    PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
832                    PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
833                    PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
834                };
835
836                self.transformation_matrix
837                    .translate(delta_x, delta_y)
838                    .and_then(|result| {
839                        result.rotate_clockwise_degrees(target_rotation.as_degrees())
840                    })
841            } else {
842                Ok(self.transformation_matrix)
843            };
844
845            result.and_then(|result| result.scale(width_scale, height_scale))
846        } else {
847            Ok(self.transformation_matrix)
848        };
849
850        PdfPageRenderSettings {
851            width: output_width,
852            height: output_height,
853            format: self.format.as_pdfium() as c_int,
854            rotate: target_rotation.as_pdfium(),
855            do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
856            clear_color: self.clear_color.as_pdfium_color(),
857            do_render_form_data: self.do_render_form_data,
858            form_field_highlight: if !self.do_render_form_data
859                || self.form_field_highlight.is_none()
860            {
861                None
862            } else {
863                Some(
864                    self.form_field_highlight
865                        .as_ref()
866                        .unwrap()
867                        .iter()
868                        .map(|(form_field_type, color)| {
869                            (
870                                form_field_type.as_pdfium() as c_int,
871                                color.as_pdfium_color_with_alpha(),
872                            )
873                        })
874                        .collect::<Vec<_>>(),
875                )
876            },
877            matrix: transformation_matrix
878                .unwrap_or(PdfMatrix::IDENTITY)
879                .as_pdfium(),
880            clipping: if let Some((left, top, right, bottom)) = self.clip_rect {
881                FS_RECTF {
882                    left: left as f32,
883                    top: top as f32,
884                    right: right as f32,
885                    bottom: bottom as f32,
886                }
887            } else {
888                FS_RECTF {
889                    left: 0.0,
890                    top: 0.0,
891                    right: output_width as f32,
892                    bottom: output_height as f32,
893                }
894            },
895            render_flags: render_flags as c_int,
896            is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
897        }
898    }
899}
900
901impl Default for PdfRenderConfig {
902    #[inline]
903    fn default() -> Self {
904        PdfRenderConfig::new()
905    }
906}
907
908/// Finalized rendering settings, ready to be passed to a Pdfium rendering function.
909/// Generated by calling [PdfRenderConfig::apply_to_page()].
910#[derive(Debug, Clone)]
911pub(crate) struct PdfPageRenderSettings {
912    pub(crate) width: c_int,
913    pub(crate) height: c_int,
914    pub(crate) format: c_int,
915    pub(crate) rotate: c_int,
916    pub(crate) do_clear_bitmap_before_rendering: bool,
917    pub(crate) clear_color: FPDF_DWORD,
918    pub(crate) do_render_form_data: bool,
919    pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
920    pub(crate) matrix: FS_MATRIX,
921    pub(crate) clipping: FS_RECTF,
922    pub(crate) render_flags: c_int,
923    pub(crate) is_reversed_byte_order_flag_set: bool,
924}