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