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    fixed_width: Option<Pixels>,
37    fixed_height: Option<Pixels>,
38    target_width: Option<Pixels>,
39    target_height: Option<Pixels>,
40    scale_width_factor: Option<f32>,
41    scale_height_factor: Option<f32>,
42    maximum_width: Option<Pixels>,
43    maximum_height: Option<Pixels>,
44    portrait_rotation: PdfPageRenderRotation,
45    portrait_rotation_do_rotate_constraints: bool,
46    landscape_rotation: PdfPageRenderRotation,
47    landscape_rotation_do_rotate_constraints: bool,
48    format: PdfBitmapFormat,
49    do_clear_bitmap_before_rendering: bool,
50    clear_color: PdfColor,
51    do_render_form_data: bool,
52    form_field_highlight: Option<Vec<(PdfFormFieldType, PdfColor)>>,
53    transformation_matrix: PdfMatrix,
54    clip_rect: Option<(Pixels, Pixels, Pixels, Pixels)>,
55
56    // The fields below set Pdfium's page rendering flags. Coverage for the
57    // FPDF_DEBUG_INFO and FPDF_NO_CATCH flags is omitted since they are obsolete.
58    do_set_flag_render_annotations: bool,     // Sets FPDF_ANNOT
59    do_set_flag_use_lcd_text_rendering: bool, // Sets FPDF_LCD_TEXT
60    do_set_flag_no_native_text: bool,         // Sets FPDF_NO_NATIVETEXT
61    do_set_flag_grayscale: bool,              // Sets FPDF_GRAYSCALE
62    do_set_flag_render_limited_image_cache: bool, // Sets FPDF_RENDER_LIMITEDIMAGECACHE
63    do_set_flag_render_force_half_tone: bool, // Sets FPDF_RENDER_FORCEHALFTONE
64    do_set_flag_render_for_printing: bool,    // Sets FPDF_PRINTING
65    do_set_flag_render_no_smooth_text: bool,  // Sets FPDF_RENDER_NO_SMOOTHTEXT
66    do_set_flag_render_no_smooth_image: bool, // Sets FPDF_RENDER_NO_SMOOTHIMAGE
67    do_set_flag_render_no_smooth_path: bool,  // Sets FPDF_RENDER_NO_SMOOTHPATH
68    do_set_flag_reverse_byte_order: bool,     // Sets FPDF_REVERSE_BYTE_ORDER
69    do_set_flag_convert_fill_to_stroke: bool, // Sets FPDF_CONVERT_FILL_TO_STROKE
70}
71
72impl PdfRenderConfig {
73    /// Creates a new [PdfRenderConfig] object with all settings initialized with their default values.
74    pub fn new() -> Self {
75        PdfRenderConfig {
76            use_auto_scaling: true,
77            fixed_width: None,
78            fixed_height: None,
79            target_width: None,
80            target_height: None,
81            scale_width_factor: None,
82            scale_height_factor: None,
83            maximum_width: None,
84            maximum_height: None,
85            portrait_rotation: PdfPageRenderRotation::None,
86            portrait_rotation_do_rotate_constraints: false,
87            landscape_rotation: PdfPageRenderRotation::None,
88            landscape_rotation_do_rotate_constraints: false,
89            format: PdfBitmapFormat::default(),
90            do_clear_bitmap_before_rendering: true,
91            clear_color: PdfColor::WHITE,
92            do_render_form_data: true,
93            form_field_highlight: None,
94            transformation_matrix: PdfMatrix::IDENTITY,
95            clip_rect: None,
96            do_set_flag_render_annotations: true,
97            do_set_flag_use_lcd_text_rendering: false,
98            do_set_flag_no_native_text: false,
99            do_set_flag_grayscale: false,
100            do_set_flag_render_limited_image_cache: false,
101            do_set_flag_render_force_half_tone: false,
102            do_set_flag_render_for_printing: false,
103            do_set_flag_render_no_smooth_text: false,
104            do_set_flag_render_no_smooth_image: false,
105            do_set_flag_render_no_smooth_path: false,
106            do_set_flag_convert_fill_to_stroke: false,
107
108            // We ask Pdfium to reverse its bitmap byte order from BGR8 to RGB8 in order
109            // to make working with Image::DynamicImage easier after version 0.24. See:
110            // https://github.com/ajrcarey/pdfium-render/issues/9
111            do_set_flag_reverse_byte_order: true,
112        }
113    }
114
115    /// Applies settings suitable for generating a thumbnail.
116    ///
117    /// * The source [PdfPage] will be rendered with a maximum width and height of the given
118    ///   pixel size.
119    /// * The page will not be rotated, irrespective of its orientation.
120    /// * Image quality settings will be reduced to improve performance.
121    /// * Annotations and user-filled form field data will not be rendered.
122    ///
123    /// These settings are applied to this [PdfRenderConfig] object immediately and can be
124    /// selectively overridden by later function calls. For instance, a later call to
125    /// [PdfRenderConfig::rotate()] can specify a custom rotation setting that will apply
126    /// to the thumbnail.
127    #[inline]
128    pub fn thumbnail(self, size: Pixels) -> Self {
129        self.set_target_size(size, size)
130            .set_maximum_width(size)
131            .set_maximum_height(size)
132            .rotate(PdfPageRenderRotation::None, false)
133            .use_print_quality(false)
134            .set_image_smoothing(false)
135            .render_annotations(false)
136            .render_form_data(false)
137    }
138
139    /// Sets the desired pixel width and height of a rendered [PdfPage] to the
140    /// width and height of the given [PdfBitmap]. No attempt will be made to scale or adjust
141    /// the aspect ratio to match the source page. Overrides any previous call to
142    /// [PdfRenderConfig::set_target_size], [PdfRenderConfig::set_target_width], or
143    /// [PdfRenderConfig::set_target_height].
144    #[inline]
145    pub fn set_fixed_size_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
146        self.set_fixed_size(bitmap.width(), bitmap.height())
147    }
148
149    /// Sets the desired pixel width and height of a rendered [PdfPage] to the given
150    /// pixel values. No attempt will be made to scale or adjust the aspect ratio to
151    /// match the source page. Overrides any previous call to [PdfRenderConfig::set_target_size],
152    /// [PdfRenderConfig::set_target_width], or [PdfRenderConfig::set_target_height].
153    #[inline]
154    pub fn set_fixed_size(self, width: Pixels, height: Pixels) -> Self {
155        self.set_fixed_width(width).set_fixed_height(height)
156    }
157
158    /// Sets the desired pixel width of a rendered [PdfPage] to the given value. Overrides
159    /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_width].
160    #[inline]
161    pub fn set_fixed_width(mut self, width: Pixels) -> Self {
162        self.use_auto_scaling = false;
163        self.fixed_width = Some(width);
164
165        self
166    }
167
168    /// Sets the desired pixel height of a rendered [PdfPage] to the given value. Overrides
169    /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_height].
170    #[inline]
171    pub fn set_fixed_height(mut self, height: Pixels) -> Self {
172        self.use_auto_scaling = false;
173        self.fixed_height = Some(height);
174
175        self
176    }
177
178    /// Converts the width and height of a [PdfPage] from points to pixels, scaling each
179    /// dimension to the given target pixel sizes. The aspect ratio of the source page
180    /// will not be maintained. Overrides any previous call to [PdfRenderConfig::set_fixed_width()]
181    /// or [PdfRenderConfig::set_fixed_height()].
182    #[inline]
183    pub fn set_target_size(self, width: Pixels, height: Pixels) -> Self {
184        self.set_target_width(width).set_target_height(height)
185    }
186
187    /// Converts the width of a [PdfPage] from points to pixels, scaling the source page
188    /// width to the given target pixel width. The aspect ratio of the source page
189    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
190    /// or [PdfRenderConfig::set_target_height()] that overrides it. Overrides any previous
191    /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
192    #[inline]
193    pub fn set_target_width(mut self, width: Pixels) -> Self {
194        self.use_auto_scaling = true;
195        self.target_width = Some(width);
196
197        self
198    }
199
200    /// Converts the height of a [PdfPage] from points to pixels, scaling the source page
201    /// height to the given target pixel height. The aspect ratio of the source page
202    /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
203    /// or [PdfRenderConfig::set_target_width()] that overrides it. Overrides any previous
204    /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
205    #[inline]
206    pub fn set_target_height(mut self, height: Pixels) -> Self {
207        self.use_auto_scaling = true;
208        self.target_height = Some(height);
209
210        self
211    }
212
213    /// Applies settings to this [PdfRenderConfig] suitable for filling the given [PdfBitmap].
214    ///
215    /// The source page's dimensions will be scaled so that both width and height attempt
216    /// to fill, but do not exceed, the pixel dimensions of the bitmap. The aspect ratio
217    /// of the source page will be maintained. Landscape pages will be automatically rotated
218    /// by 90 degrees and will be scaled down if necessary to fit the bitmap width.
219    #[inline]
220    pub fn scale_page_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
221        self.scale_page_to_display_size(bitmap.width(), bitmap.height())
222    }
223
224    /// Applies settings to this [PdfRenderConfig] suitable for filling the given
225    /// screen display size.
226    ///
227    /// The source page's dimensions will be scaled so that both width and height attempt
228    /// to fill, but do not exceed, the given pixel dimensions. The aspect ratio of the
229    /// source page will be maintained. Landscape pages will be automatically rotated
230    /// by 90 degrees and will be scaled down if necessary to fit the display width.
231    #[inline]
232    pub fn scale_page_to_display_size(mut self, width: Pixels, height: Pixels) -> Self {
233        self.scale_width_factor = None;
234        self.scale_height_factor = None;
235
236        self.set_target_width(width)
237            .set_maximum_width(width)
238            .set_maximum_height(height)
239            .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true)
240    }
241
242    /// Converts the width and height of a [PdfPage] from points to pixels by applying
243    /// the given scale factor to both dimensions. The aspect ratio of the source page
244    /// will be maintained. Overrides any previous call to [PdfRenderConfig::scale_page_by_factor()],
245    /// [PdfRenderConfig::scale_page_width_by_factor()], or [PdfRenderConfig::scale_page_height_by_factor()].
246    #[inline]
247    pub fn scale_page_by_factor(self, scale: f32) -> Self {
248        let result = self.scale_page_width_by_factor(scale);
249
250        result.scale_page_height_by_factor(scale)
251    }
252
253    /// Converts the width of the [PdfPage] from points to pixels by applying the given
254    /// scale factor. The aspect ratio of the source page will not be maintained if a
255    /// different scale factor is applied to the height. Overrides any previous call to
256    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
257    /// or [PdfRenderConfig::scale_page_height_by_factor()].
258    #[inline]
259    pub fn scale_page_width_by_factor(mut self, scale: f32) -> Self {
260        self.scale_width_factor = Some(scale);
261
262        self
263    }
264
265    /// Converts the height of the [PdfPage] from points to pixels by applying the given
266    /// scale factor. The aspect ratio of the source page will not be maintained if a
267    /// different scale factor is applied to the width. Overrides any previous call to
268    /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
269    /// or [PdfRenderConfig::scale_page_height_by_factor()].
270    #[inline]
271    pub fn scale_page_height_by_factor(mut self, scale: f32) -> Self {
272        self.scale_height_factor = Some(scale);
273
274        self
275    }
276
277    /// Specifies that the final pixel width of the [PdfPage] will not exceed the given maximum.
278    #[inline]
279    pub fn set_maximum_width(mut self, width: Pixels) -> Self {
280        self.maximum_width = Some(width);
281
282        self
283    }
284
285    /// Specifies that the final pixel height of the [PdfPage] will not exceed the given maximum.
286    #[inline]
287    pub fn set_maximum_height(mut self, height: Pixels) -> Self {
288        self.maximum_height = Some(height);
289
290        self
291    }
292
293    /// Applies the given clockwise rotation setting to the [PdfPage] during rendering, irrespective
294    /// of its orientation. If the given flag is set to `true` then any maximum
295    /// constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
296    /// will be rotated so it becomes a constraint on the final pixel height, and any
297    /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
298    /// will be rotated so it becomes a constraint on the final pixel width.
299    #[inline]
300    pub fn rotate(self, rotation: PdfPageRenderRotation, do_rotate_constraints: bool) -> Self {
301        self.rotate_if_portrait(rotation, do_rotate_constraints)
302            .rotate_if_landscape(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 (output_width, output_height, width_scale, height_scale) = if self.use_auto_scaling {
687            // Compute output width and height based on target sizes and page dimensions.
688
689            let width_scale = if let Some(scale) = self.scale_width_factor {
690                Some(scale)
691            } else {
692                self.target_width
693                    .map(|target| (target as f32) / source_width.value)
694            };
695
696            let height_scale = if let Some(scale) = self.scale_height_factor {
697                Some(scale)
698            } else {
699                self.target_height
700                    .map(|target| (target as f32) / source_height.value)
701            };
702
703            // Maintain source aspect ratio if only one dimension's scale is set.
704
705            let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) =
706                match (width_scale, height_scale) {
707                    (Some(width_scale), Some(height_scale)) => {
708                        (width_scale == height_scale, width_scale, height_scale)
709                    }
710                    (Some(width_scale), None) => (true, width_scale, width_scale),
711                    (None, Some(height_scale)) => (true, height_scale, height_scale),
712                    (None, None) => {
713                        // Set default scale to 1.0 if neither dimension is specified.
714
715                        (false, 1.0, 1.0)
716                    }
717                };
718
719            // Apply constraints on maximum width and height, if any.
720
721            let (source_width, source_height, width_constraint, height_constraint) =
722                if do_rotate_constraints {
723                    (
724                        source_height,
725                        source_width,
726                        self.maximum_height,
727                        self.maximum_width,
728                    )
729                } else {
730                    (
731                        source_width,
732                        source_height,
733                        self.maximum_width,
734                        self.maximum_height,
735                    )
736                };
737
738            if let Some(maximum) = width_constraint {
739                let maximum = maximum as f32;
740
741                if source_width.value * width_scale > maximum {
742                    // Constrain the width, so it does not exceed the maximum.
743
744                    width_scale = maximum / source_width.value;
745
746                    if do_maintain_aspect_ratio {
747                        height_scale = width_scale;
748                    }
749                }
750            }
751
752            if let Some(maximum) = height_constraint {
753                let maximum = maximum as f32;
754
755                if source_height.value * height_scale > maximum {
756                    // Constrain the height, so it does not exceed the maximum.
757
758                    height_scale = maximum / source_height.value;
759
760                    if do_maintain_aspect_ratio {
761                        width_scale = height_scale;
762                    }
763                }
764            }
765
766            (
767                (source_width.value * width_scale).round() as c_int,
768                (source_height.value * height_scale).round() as c_int,
769                width_scale,
770                height_scale,
771            )
772        } else {
773            // Take output width and height directly from user's fixed settings.
774
775            (
776                self.fixed_width.unwrap_or(0) as c_int,
777                self.fixed_height.unwrap_or(0) as c_int,
778                self.scale_width_factor.unwrap_or(1.0),
779                self.scale_height_factor.unwrap_or(1.0),
780            )
781        };
782
783        // Compose render flags.
784
785        let mut render_flags = 0;
786
787        if self.do_set_flag_render_annotations {
788            render_flags |= FPDF_ANNOT;
789        }
790
791        if self.do_set_flag_use_lcd_text_rendering {
792            render_flags |= FPDF_LCD_TEXT;
793        }
794
795        if self.do_set_flag_no_native_text {
796            render_flags |= FPDF_NO_NATIVETEXT;
797        }
798
799        if self.do_set_flag_grayscale {
800            render_flags |= FPDF_GRAYSCALE;
801        }
802
803        if self.do_set_flag_render_limited_image_cache {
804            render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
805        }
806
807        if self.do_set_flag_render_force_half_tone {
808            render_flags |= FPDF_RENDER_FORCEHALFTONE;
809        }
810
811        if self.do_set_flag_render_for_printing {
812            render_flags |= FPDF_PRINTING;
813        }
814
815        if self.do_set_flag_render_no_smooth_text {
816            render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
817        }
818
819        if self.do_set_flag_render_no_smooth_image {
820            render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
821        }
822
823        if self.do_set_flag_render_no_smooth_path {
824            render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
825        }
826
827        if self.do_set_flag_reverse_byte_order {
828            render_flags |= FPDF_REVERSE_BYTE_ORDER;
829        }
830
831        if self.do_set_flag_convert_fill_to_stroke {
832            render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
833        }
834
835        // Pages can be rendered either _with_ transformation matrices and clipping
836        // but _without_ form data, or _with_ form data but _without_ transformation matrices
837        // and clipping. We need to be prepared for either option. If rendering of form data
838        // is disabled, then the scaled output width and height and any user-specified
839        // 90-degree rotation need to be applied to the transformation matrix now.
840
841        let transformation_matrix = if !self.do_render_form_data {
842            let result = if target_rotation != PdfPageRenderRotation::None {
843                // Translate the origin to the center of the page before rotating.
844
845                let (delta_x, delta_y) = match target_rotation {
846                    PdfPageRenderRotation::None => unreachable!(),
847                    PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
848                    PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
849                    PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
850                };
851
852                self.transformation_matrix
853                    .translate(delta_x, delta_y)
854                    .and_then(|result| {
855                        result.rotate_clockwise_degrees(target_rotation.as_degrees())
856                    })
857            } else {
858                Ok(self.transformation_matrix)
859            };
860
861            result.and_then(|result| result.scale(width_scale, height_scale))
862        } else {
863            Ok(PdfMatrix::identity())
864        };
865
866        PdfPageRenderSettings {
867            width: output_width,
868            height: output_height,
869            format: self.format.as_pdfium() as c_int,
870            rotate: target_rotation.as_pdfium(),
871            do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
872            clear_color: self.clear_color.as_pdfium_color(),
873            do_render_form_data: self.do_render_form_data,
874            form_field_highlight: if !self.do_render_form_data
875                || self.form_field_highlight.is_none()
876            {
877                None
878            } else {
879                Some(
880                    self.form_field_highlight
881                        .as_ref()
882                        .unwrap()
883                        .iter()
884                        .map(|(form_field_type, color)| {
885                            (
886                                form_field_type.as_pdfium() as c_int,
887                                color.as_pdfium_color_with_alpha(),
888                            )
889                        })
890                        .collect::<Vec<_>>(),
891                )
892            },
893            matrix: transformation_matrix
894                .unwrap_or(PdfMatrix::IDENTITY)
895                .as_pdfium(),
896            clipping: if let Some((left, top, right, bottom)) = self.clip_rect {
897                FS_RECTF {
898                    left: left as f32,
899                    top: top as f32,
900                    right: right as f32,
901                    bottom: bottom as f32,
902                }
903            } else {
904                FS_RECTF {
905                    left: 0.0,
906                    top: 0.0,
907                    right: output_width as f32,
908                    bottom: output_height as f32,
909                }
910            },
911            render_flags: render_flags as c_int,
912            is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
913        }
914    }
915}
916
917impl Default for PdfRenderConfig {
918    #[inline]
919    fn default() -> Self {
920        PdfRenderConfig::new()
921    }
922}
923
924/// Finalized rendering settings, ready to be passed to a Pdfium rendering function.
925/// Generated by calling [PdfRenderConfig::apply_to_page()].
926#[derive(Debug, Clone)]
927pub(crate) struct PdfPageRenderSettings {
928    pub(crate) width: c_int,
929    pub(crate) height: c_int,
930    pub(crate) format: c_int,
931    pub(crate) rotate: c_int,
932    pub(crate) do_clear_bitmap_before_rendering: bool,
933    pub(crate) clear_color: FPDF_DWORD,
934    pub(crate) do_render_form_data: bool,
935    pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
936    pub(crate) matrix: FS_MATRIX,
937    pub(crate) clipping: FS_RECTF,
938    pub(crate) render_flags: c_int,
939    pub(crate) is_reversed_byte_order_flag_set: bool,
940}
941
942#[cfg(test)]
943mod tests {
944    use crate::prelude::*;
945    use crate::utils::test::test_bind_to_pdfium; // Temporary until PdfParagraph is included in the prelude.
946
947    #[test]
948    fn test_fixed_size_render_config() -> Result<(), PdfiumError> {
949        let render_settings =
950            get_render_settings_from_config(PdfRenderConfig::new().set_fixed_size(2000, 2000))?;
951
952        assert_eq!(render_settings.width, 2000);
953        assert_eq!(render_settings.height, 2000);
954
955        // Applying scaling does not affect the rendered bitmap size.
956
957        let render_settings = get_render_settings_from_config(
958            PdfRenderConfig::new()
959                .set_fixed_size(2000, 2000)
960                .scale_page_by_factor(5.0),
961        )?;
962
963        assert_eq!(render_settings.width, 2000);
964        assert_eq!(render_settings.height, 2000);
965
966        Ok(())
967    }
968
969    #[test]
970    fn test_target_size_render_config() -> Result<(), PdfiumError> {
971        let render_settings = get_render_settings_from_config(
972            PdfRenderConfig::new().scale_page_to_display_size(2000, 2000),
973        )?;
974
975        assert_eq!(render_settings.width, 1414);
976        assert_eq!(render_settings.height, 2000);
977
978        // Applying scaling does affected the rendered bitmap size.
979
980        let render_settings = get_render_settings_from_config(
981            PdfRenderConfig::new()
982                .set_target_size(2000, 2000)
983                .scale_page_by_factor(5.0),
984        )?;
985
986        assert_eq!(render_settings.width, 2976);
987        assert_eq!(render_settings.height, 4209);
988
989        Ok(())
990    }
991
992    fn get_render_settings_from_config(
993        config: PdfRenderConfig,
994    ) -> Result<PdfPageRenderSettings, PdfiumError> {
995        let pdfium = test_bind_to_pdfium();
996
997        let mut document = pdfium.create_new_pdf()?;
998        let page = document
999            .pages_mut()
1000            .create_page_at_start(PdfPagePaperSize::Portrait(PdfPagePaperStandardSize::A4))?;
1001
1002        Ok(config.apply_to_page(&page))
1003    }
1004}