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) = self.compute_target_rotation(source_orientation);
623
624        let (output_width, output_height, width_scale, height_scale) =
625            self.compute_output_dimensions(source_width, source_height, do_rotate_constraints);
626
627        let render_flags = self.compute_render_flags();
628
629        let transformation_matrix =
630            self.compute_transformation_matrix(target_rotation, source_width, source_height, width_scale, height_scale);
631
632        PdfPageRenderSettings {
633            width: output_width,
634            height: output_height,
635            format: self.format.as_pdfium() as c_int,
636            rotate: target_rotation.as_pdfium(),
637            do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
638            clear_color: self.clear_color.as_pdfium_color(),
639            do_render_form_data: self.do_render_form_data,
640            form_field_highlight: None,
641            matrix: transformation_matrix.unwrap_or(PdfMatrix::IDENTITY).as_pdfium(),
642            clipping: self.compute_clipping_rect(output_width, output_height),
643            render_flags: render_flags as c_int,
644            is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
645        }
646    }
647
648    /// Determines the clockwise rotation that should be applied during rendering, and whether
649    /// any maximum width/height constraints should be swapped to account for that rotation,
650    /// based on the source page's orientation. ~keep
651    fn compute_target_rotation(&self, source_orientation: PdfPageOrientation) -> (PdfPageRenderRotation, bool) {
652        if source_orientation == Portrait && self.portrait_rotation != PdfPageRenderRotation::None {
653            (self.portrait_rotation, self.portrait_rotation_do_rotate_constraints)
654        } else if source_orientation == Landscape && self.landscape_rotation != PdfPageRenderRotation::None {
655            (self.landscape_rotation, self.landscape_rotation_do_rotate_constraints)
656        } else {
657            (PdfPageRenderRotation::None, false)
658        }
659    }
660
661    /// Computes the output pixel width and height and the width/height scale factors used to
662    /// reach them, taking fixed sizing, target sizing, explicit scale factors, and maximum
663    /// width/height constraints into account. ~keep
664    fn compute_output_dimensions(
665        &self,
666        source_width: PdfPoints,
667        source_height: PdfPoints,
668        do_rotate_constraints: bool,
669    ) -> (c_int, c_int, f32, f32) {
670        if !self.use_auto_scaling {
671            return (
672                self.fixed_width.unwrap_or(0) as c_int,
673                self.fixed_height.unwrap_or(0) as c_int,
674                self.scale_width_factor.unwrap_or(1.0),
675                self.scale_height_factor.unwrap_or(1.0),
676            );
677        }
678
679        let width_scale = if let Some(scale) = self.scale_width_factor {
680            Some(scale)
681        } else {
682            self.target_width.map(|target| (target as f32) / source_width.value)
683        };
684
685        let height_scale = if let Some(scale) = self.scale_height_factor {
686            Some(scale)
687        } else {
688            self.target_height.map(|target| (target as f32) / source_height.value)
689        };
690
691        let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) = match (width_scale, height_scale) {
692            (Some(width_scale), Some(height_scale)) => (width_scale == height_scale, width_scale, height_scale),
693            (Some(width_scale), None) => (true, width_scale, width_scale),
694            (None, Some(height_scale)) => (true, height_scale, height_scale),
695            (None, None) => (false, 1.0, 1.0),
696        };
697
698        let (source_width, source_height, width_constraint, height_constraint) = if do_rotate_constraints {
699            (source_height, source_width, self.maximum_height, self.maximum_width)
700        } else {
701            (source_width, source_height, self.maximum_width, self.maximum_height)
702        };
703
704        if let Some(maximum) = width_constraint {
705            let maximum = maximum as f32;
706
707            if source_width.value * width_scale > maximum {
708                width_scale = maximum / source_width.value;
709
710                if do_maintain_aspect_ratio {
711                    height_scale = width_scale;
712                }
713            }
714        }
715
716        if let Some(maximum) = height_constraint {
717            let maximum = maximum as f32;
718
719            if source_height.value * height_scale > maximum {
720                height_scale = maximum / source_height.value;
721
722                if do_maintain_aspect_ratio {
723                    width_scale = height_scale;
724                }
725            }
726        }
727
728        (
729            (source_width.value * width_scale).round() as c_int,
730            (source_height.value * height_scale).round() as c_int,
731            width_scale,
732            height_scale,
733        )
734    }
735
736    /// Combines this configuration's boolean rendering flags into a single Pdfium render
737    /// flags bitmask. ~keep
738    fn compute_render_flags(&self) -> u32 {
739        let mut render_flags = 0;
740
741        if self.do_set_flag_render_annotations {
742            render_flags |= FPDF_ANNOT;
743        }
744
745        if self.do_set_flag_use_lcd_text_rendering {
746            render_flags |= FPDF_LCD_TEXT;
747        }
748
749        if self.do_set_flag_no_native_text {
750            render_flags |= FPDF_NO_NATIVETEXT;
751        }
752
753        if self.do_set_flag_grayscale {
754            render_flags |= FPDF_GRAYSCALE;
755        }
756
757        if self.do_set_flag_render_limited_image_cache {
758            render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
759        }
760
761        if self.do_set_flag_render_force_half_tone {
762            render_flags |= FPDF_RENDER_FORCEHALFTONE;
763        }
764
765        if self.do_set_flag_render_for_printing {
766            render_flags |= FPDF_PRINTING;
767        }
768
769        if self.do_set_flag_render_no_smooth_text {
770            render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
771        }
772
773        if self.do_set_flag_render_no_smooth_image {
774            render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
775        }
776
777        if self.do_set_flag_render_no_smooth_path {
778            render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
779        }
780
781        if self.do_set_flag_reverse_byte_order {
782            render_flags |= FPDF_REVERSE_BYTE_ORDER;
783        }
784
785        if self.do_set_flag_convert_fill_to_stroke {
786            render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
787        }
788
789        render_flags
790    }
791
792    /// Computes the transformation matrix that should be applied during rendering: identity
793    /// when form data rendering is enabled (Pdfium does not support both at once), otherwise
794    /// this configuration's transformation matrix combined with any rotation and scaling. ~keep
795    fn compute_transformation_matrix(
796        &self,
797        target_rotation: PdfPageRenderRotation,
798        source_width: PdfPoints,
799        source_height: PdfPoints,
800        width_scale: f32,
801        height_scale: f32,
802    ) -> Result<PdfMatrix, PdfiumError> {
803        if self.do_render_form_data {
804            return Ok(PdfMatrix::identity());
805        }
806
807        let result = if target_rotation != PdfPageRenderRotation::None {
808            let (delta_x, delta_y) = match target_rotation {
809                PdfPageRenderRotation::None => unreachable!(),
810                PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
811                PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
812                PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
813            };
814
815            self.transformation_matrix
816                .translate(delta_x, delta_y)
817                .and_then(|result| result.rotate_clockwise_degrees(target_rotation.as_degrees()))
818        } else {
819            Ok(self.transformation_matrix)
820        };
821
822        result.and_then(|result| result.scale(width_scale, height_scale))
823    }
824
825    /// Computes the pixel clipping rectangle to apply during rendering: the explicit clip
826    /// region set via [PdfRenderConfig::clip()], or the full output bounds otherwise. ~keep
827    fn compute_clipping_rect(&self, output_width: c_int, output_height: c_int) -> FS_RECTF {
828        if let Some((left, top, right, bottom)) = self.clip_rect {
829            FS_RECTF {
830                left: left as f32,
831                top: top as f32,
832                right: right as f32,
833                bottom: bottom as f32,
834            }
835        } else {
836            FS_RECTF {
837                left: 0.0,
838                top: 0.0,
839                right: output_width as f32,
840                bottom: output_height as f32,
841            }
842        }
843    }
844}
845
846impl Default for PdfRenderConfig {
847    #[inline]
848    fn default() -> Self {
849        PdfRenderConfig::new()
850    }
851}
852
853/// Finalized rendering settings, ready to be passed to a Pdfium rendering function.
854/// Generated by calling [PdfRenderConfig::apply_to_page()].
855#[derive(Debug, Clone)]
856pub(crate) struct PdfPageRenderSettings {
857    pub(crate) width: c_int,
858    pub(crate) height: c_int,
859    pub(crate) format: c_int,
860    pub(crate) rotate: c_int,
861    pub(crate) do_clear_bitmap_before_rendering: bool,
862    pub(crate) clear_color: FPDF_DWORD,
863    pub(crate) do_render_form_data: bool,
864    pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
865    pub(crate) matrix: FS_MATRIX,
866    pub(crate) clipping: FS_RECTF,
867    pub(crate) render_flags: c_int,
868    pub(crate) is_reversed_byte_order_flag_set: bool,
869}
870
871#[cfg(test)]
872mod tests {
873    use crate::prelude::*;
874    use crate::utils::test::test_bind_to_pdfium;
875
876    #[test]
877    fn test_fixed_size_render_config() -> Result<(), PdfiumError> {
878        let render_settings = get_render_settings_from_config(PdfRenderConfig::new().set_fixed_size(2000, 2000))?;
879
880        assert_eq!(render_settings.width, 2000);
881        assert_eq!(render_settings.height, 2000);
882
883        let render_settings = get_render_settings_from_config(
884            PdfRenderConfig::new()
885                .set_fixed_size(2000, 2000)
886                .scale_page_by_factor(5.0),
887        )?;
888
889        assert_eq!(render_settings.width, 2000);
890        assert_eq!(render_settings.height, 2000);
891
892        Ok(())
893    }
894
895    #[test]
896    fn test_target_size_render_config() -> Result<(), PdfiumError> {
897        let render_settings =
898            get_render_settings_from_config(PdfRenderConfig::new().scale_page_to_display_size(2000, 2000))?;
899
900        assert_eq!(render_settings.width, 1414);
901        assert_eq!(render_settings.height, 2000);
902
903        let render_settings = get_render_settings_from_config(
904            PdfRenderConfig::new()
905                .set_target_size(2000, 2000)
906                .scale_page_by_factor(5.0),
907        )?;
908
909        assert_eq!(render_settings.width, 2976);
910        assert_eq!(render_settings.height, 4209);
911
912        Ok(())
913    }
914
915    fn get_render_settings_from_config(config: PdfRenderConfig) -> Result<PdfPageRenderSettings, PdfiumError> {
916        let pdfium = test_bind_to_pdfium();
917
918        let mut document = pdfium.create_new_pdf()?;
919        let page = document
920            .pages_mut()
921            .create_page_at_start(PdfPagePaperSize::Portrait(PdfPagePaperStandardSize::A4))?;
922
923        Ok(config.apply_to_page(&page))
924    }
925}