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