pdfium_render/pdf/document/page/render_config.rs
1//! Defines the [PdfRenderConfig] struct, a builder-based approach to configuring
2//! the rendering of [PdfBitmap] objects from one or more [PdfPage] objects.
3
4use crate::bindgen::{
5 FPDF_ANNOT, FPDF_CONVERT_FILL_TO_STROKE, FPDF_DWORD, FPDF_GRAYSCALE, FPDF_LCD_TEXT,
6 FPDF_NO_NATIVETEXT, FPDF_PRINTING, FPDF_RENDER_FORCEHALFTONE, FPDF_RENDER_LIMITEDIMAGECACHE,
7 FPDF_RENDER_NO_SMOOTHIMAGE, FPDF_RENDER_NO_SMOOTHPATH, FPDF_RENDER_NO_SMOOTHTEXT,
8 FPDF_REVERSE_BYTE_ORDER, FS_MATRIX, FS_RECTF,
9};
10use crate::create_transform_setters;
11use crate::error::PdfiumError;
12use crate::pdf::bitmap::{PdfBitmap, PdfBitmapFormat, Pixels};
13use crate::pdf::color::PdfColor;
14use crate::pdf::document::page::field::PdfFormFieldType;
15use crate::pdf::document::page::PdfPageOrientation::{Landscape, Portrait};
16use crate::pdf::document::page::{PdfPage, PdfPageOrientation, PdfPageRenderRotation};
17use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
18use crate::pdf::points::PdfPoints;
19use std::os::raw::c_int;
20
21/// Configures the scaling, rotation, and rendering settings that should be applied to
22/// a [PdfPage] to create a [PdfBitmap] for that page. [PdfRenderConfig] can accommodate pages of
23/// different sizes while correctly maintaining each page's aspect ratio, automatically
24/// rotate portrait or landscape pages, generate page thumbnails, apply maximum pixel size
25/// constraints to the scaled width and height of the final rendering, highlight form fields
26/// with different colors, apply custom transforms to the page during rendering, and set
27/// internal Pdfium rendering flags.
28///
29/// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
30/// a custom transformation matrix, but not both at the same time. Applying any transformation
31/// automatically disables rendering of form data. If you must render form data while simultaneously
32/// applying transformations, consider using the [PdfPage::flatten()] function to flatten the
33/// form elements and form data into the containing page.
34pub struct PdfRenderConfig {
35 use_auto_scaling: bool,
36 start_x: Pixels,
37 start_y: Pixels,
38 fixed_width: Option<Pixels>,
39 fixed_height: Option<Pixels>,
40 target_width: Option<Pixels>,
41 target_height: Option<Pixels>,
42 scale_width_factor: Option<f32>,
43 scale_height_factor: Option<f32>,
44 maximum_width: Option<Pixels>,
45 maximum_height: Option<Pixels>,
46 portrait_rotation: PdfPageRenderRotation,
47 portrait_rotation_do_rotate_constraints: bool,
48 landscape_rotation: PdfPageRenderRotation,
49 landscape_rotation_do_rotate_constraints: bool,
50 format: PdfBitmapFormat,
51 do_clear_bitmap_before_rendering: bool,
52 clear_color: PdfColor,
53 do_render_form_data: bool,
54 form_field_highlight: Option<Vec<(PdfFormFieldType, PdfColor)>>,
55 transformation_matrix: PdfMatrix,
56 clip_rect: Option<(Pixels, Pixels, Pixels, Pixels)>,
57
58 // The fields below set Pdfium's page rendering flags. Coverage for the
59 // FPDF_DEBUG_INFO and FPDF_NO_CATCH flags is omitted since they are obsolete.
60 do_set_flag_render_annotations: bool, // Sets FPDF_ANNOT
61 do_set_flag_use_lcd_text_rendering: bool, // Sets FPDF_LCD_TEXT
62 do_set_flag_no_native_text: bool, // Sets FPDF_NO_NATIVETEXT
63 do_set_flag_grayscale: bool, // Sets FPDF_GRAYSCALE
64 do_set_flag_render_limited_image_cache: bool, // Sets FPDF_RENDER_LIMITEDIMAGECACHE
65 do_set_flag_render_force_half_tone: bool, // Sets FPDF_RENDER_FORCEHALFTONE
66 do_set_flag_render_for_printing: bool, // Sets FPDF_PRINTING
67 do_set_flag_render_no_smooth_text: bool, // Sets FPDF_RENDER_NO_SMOOTHTEXT
68 do_set_flag_render_no_smooth_image: bool, // Sets FPDF_RENDER_NO_SMOOTHIMAGE
69 do_set_flag_render_no_smooth_path: bool, // Sets FPDF_RENDER_NO_SMOOTHPATH
70 do_set_flag_reverse_byte_order: bool, // Sets FPDF_REVERSE_BYTE_ORDER
71 do_set_flag_convert_fill_to_stroke: bool, // Sets FPDF_CONVERT_FILL_TO_STROKE
72}
73
74impl PdfRenderConfig {
75 /// Creates a new [PdfRenderConfig] object with all settings initialized to their default values.
76 pub fn new() -> Self {
77 PdfRenderConfig {
78 use_auto_scaling: true,
79 start_x: 0,
80 start_y: 0,
81 fixed_width: None,
82 fixed_height: None,
83 target_width: None,
84 target_height: None,
85 scale_width_factor: None,
86 scale_height_factor: None,
87 maximum_width: None,
88 maximum_height: None,
89 portrait_rotation: PdfPageRenderRotation::None,
90 portrait_rotation_do_rotate_constraints: false,
91 landscape_rotation: PdfPageRenderRotation::None,
92 landscape_rotation_do_rotate_constraints: false,
93 format: PdfBitmapFormat::default(),
94 do_clear_bitmap_before_rendering: true,
95 clear_color: PdfColor::WHITE,
96 do_render_form_data: true,
97 form_field_highlight: None,
98 transformation_matrix: PdfMatrix::IDENTITY,
99 clip_rect: None,
100 do_set_flag_render_annotations: true,
101 do_set_flag_use_lcd_text_rendering: false,
102 do_set_flag_no_native_text: false,
103 do_set_flag_grayscale: false,
104 do_set_flag_render_limited_image_cache: false,
105 do_set_flag_render_force_half_tone: false,
106 do_set_flag_render_for_printing: false,
107 do_set_flag_render_no_smooth_text: false,
108 do_set_flag_render_no_smooth_image: false,
109 do_set_flag_render_no_smooth_path: false,
110 do_set_flag_convert_fill_to_stroke: false,
111
112 // We ask Pdfium to reverse its bitmap byte order from BGR8 to RGB8 in order
113 // to make working with Image::DynamicImage easier after version 0.24. See:
114 // https://github.com/ajrcarey/pdfium-render/issues/9
115 do_set_flag_reverse_byte_order: true,
116 }
117 }
118
119 /// Applies settings suitable for generating a thumbnail.
120 ///
121 /// * The source [PdfPage] will be rendered with a maximum width and height of the given
122 /// pixel size.
123 /// * The page will not be rotated, irrespective of its orientation.
124 /// * Image quality settings will be reduced to improve performance.
125 /// * Annotations and user-filled form field data will not be rendered.
126 ///
127 /// These settings are applied to this [PdfRenderConfig] object immediately and can be
128 /// selectively overridden by later function calls. For instance, a later call to
129 /// [PdfRenderConfig::rotate()] can specify a custom rotation setting that will apply
130 /// to the thumbnail.
131 #[inline]
132 pub fn thumbnail(self, size: Pixels) -> Self {
133 self.set_target_size(size, size)
134 .set_maximum_width(size)
135 .set_maximum_height(size)
136 .rotate(PdfPageRenderRotation::None, false)
137 .use_print_quality(false)
138 .set_image_smoothing(false)
139 .render_annotations(false)
140 .render_form_data(false)
141 }
142
143 /// Sets the desired pixel width and height of a rendered [PdfPage] to the
144 /// width and height of the given [PdfBitmap]. No attempt will be made to scale or adjust
145 /// the aspect ratio to match the source page. Overrides any previous call to
146 /// [PdfRenderConfig::set_target_size], [PdfRenderConfig::set_target_width], or
147 /// [PdfRenderConfig::set_target_height].
148 #[inline]
149 pub fn set_fixed_size_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
150 self.set_fixed_size(bitmap.width(), bitmap.height())
151 }
152
153 /// Sets the desired pixel width and height of a rendered [PdfPage] to the given
154 /// pixel values. No attempt will be made to scale or adjust the aspect ratio to
155 /// match the source page. Overrides any previous call to [PdfRenderConfig::set_target_size],
156 /// [PdfRenderConfig::set_target_width], or [PdfRenderConfig::set_target_height].
157 #[inline]
158 pub fn set_fixed_size(self, width: Pixels, height: Pixels) -> Self {
159 self.set_fixed_width(width).set_fixed_height(height)
160 }
161
162 /// Sets the desired pixel width of a rendered [PdfPage] to the given value. Overrides
163 /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_width].
164 #[inline]
165 pub fn set_fixed_width(mut self, width: Pixels) -> Self {
166 self.use_auto_scaling = false;
167 self.fixed_width = Some(width);
168
169 self
170 }
171
172 /// Sets the desired pixel height of a rendered [PdfPage] to the given value. Overrides
173 /// any previous call to [PdfRenderConfig::set_target_size] or [PdfRenderConfig::set_target_height].
174 #[inline]
175 pub fn set_fixed_height(mut self, height: Pixels) -> Self {
176 self.use_auto_scaling = false;
177 self.fixed_height = Some(height);
178
179 self
180 }
181
182 /// Converts the width and height of a [PdfPage] from points to pixels, scaling each
183 /// dimension to the given target pixel sizes. The aspect ratio of the source page
184 /// will not be maintained. Overrides any previous call to [PdfRenderConfig::set_fixed_width()]
185 /// or [PdfRenderConfig::set_fixed_height()].
186 #[inline]
187 pub fn set_target_size(self, width: Pixels, height: Pixels) -> Self {
188 self.set_target_width(width).set_target_height(height)
189 }
190
191 /// Converts the width of a [PdfPage] from points to pixels, scaling the source page
192 /// width to the given target pixel width. The aspect ratio of the source page
193 /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
194 /// or [PdfRenderConfig::set_target_height()] that overrides it. Overrides any previous
195 /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
196 #[inline]
197 pub fn set_target_width(mut self, width: Pixels) -> Self {
198 self.use_auto_scaling = true;
199 self.target_width = Some(width);
200
201 self
202 }
203
204 /// Converts the height of a [PdfPage] from points to pixels, scaling the source page
205 /// height to the given target pixel height. The aspect ratio of the source page
206 /// will be maintained so long as there is no call to [PdfRenderConfig::set_target_size()]
207 /// or [PdfRenderConfig::set_target_width()] that overrides it. Overrides any previous
208 /// call to [PdfRenderConfig::set_fixed_width()] or [PdfRenderConfig::set_fixed_height()].
209 #[inline]
210 pub fn set_target_height(mut self, height: Pixels) -> Self {
211 self.use_auto_scaling = true;
212 self.target_height = Some(height);
213
214 self
215 }
216
217 /// Applies settings to this [PdfRenderConfig] suitable for filling the given [PdfBitmap].
218 ///
219 /// The source page's dimensions will be scaled so that both width and height attempt
220 /// to fill, but do not exceed, the pixel dimensions of the bitmap. The aspect ratio
221 /// of the source page will be maintained. Landscape pages will be automatically rotated
222 /// by 90 degrees and will be scaled down if necessary to fit the bitmap width.
223 #[inline]
224 pub fn scale_page_to_bitmap(self, bitmap: &PdfBitmap) -> Self {
225 self.scale_page_to_display_size(bitmap.width(), bitmap.height())
226 }
227
228 /// Applies settings to this [PdfRenderConfig] suitable for filling the given
229 /// screen display size.
230 ///
231 /// The source page's dimensions will be scaled so that both width and height attempt
232 /// to fill, but do not exceed, the given pixel dimensions. The aspect ratio of the
233 /// source page will be maintained. Landscape pages will be automatically rotated
234 /// by 90 degrees and will be scaled down if necessary to fit the display width.
235 #[inline]
236 pub fn scale_page_to_display_size(mut self, width: Pixels, height: Pixels) -> Self {
237 self.scale_width_factor = None;
238 self.scale_height_factor = None;
239
240 self.set_target_width(width)
241 .set_maximum_width(width)
242 .set_maximum_height(height)
243 .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true)
244 }
245
246 /// Converts the width and height of a [PdfPage] from points to pixels by applying
247 /// the given scale factor to both dimensions. The aspect ratio of the source page
248 /// will be maintained. Overrides any previous call to [PdfRenderConfig::scale_page_by_factor()],
249 /// [PdfRenderConfig::scale_page_width_by_factor()], or [PdfRenderConfig::scale_page_height_by_factor()].
250 #[inline]
251 pub fn scale_page_by_factor(self, scale: f32) -> Self {
252 let result = self.scale_page_width_by_factor(scale);
253
254 result.scale_page_height_by_factor(scale)
255 }
256
257 /// Converts the width of the [PdfPage] from points to pixels by applying the given
258 /// scale factor. The aspect ratio of the source page will not be maintained if a
259 /// different scale factor is applied to the height. Overrides any previous call to
260 /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
261 /// or [PdfRenderConfig::scale_page_height_by_factor()].
262 #[inline]
263 pub fn scale_page_width_by_factor(mut self, scale: f32) -> Self {
264 self.scale_width_factor = Some(scale);
265
266 self
267 }
268
269 /// Converts the height of the [PdfPage] from points to pixels by applying the given
270 /// scale factor. The aspect ratio of the source page will not be maintained if a
271 /// different scale factor is applied to the width. Overrides any previous call to
272 /// [PdfRenderConfig::scale_page_by_factor()], [PdfRenderConfig::scale_page_width_by_factor()],
273 /// or [PdfRenderConfig::scale_page_height_by_factor()].
274 #[inline]
275 pub fn scale_page_height_by_factor(mut self, scale: f32) -> Self {
276 self.scale_height_factor = Some(scale);
277
278 self
279 }
280
281 /// Specifies that the final pixel width of the [PdfPage] will not exceed the given maximum.
282 #[inline]
283 pub fn set_maximum_width(mut self, width: Pixels) -> Self {
284 self.maximum_width = Some(width);
285
286 self
287 }
288
289 /// Specifies that the final pixel height of the [PdfPage] will not exceed the given maximum.
290 #[inline]
291 pub fn set_maximum_height(mut self, height: Pixels) -> Self {
292 self.maximum_height = Some(height);
293
294 self
295 }
296
297 /// Applies the given clockwise rotation setting to the [PdfPage] during rendering, irrespective
298 /// of its orientation. If the given flag is set to `true` then any maximum
299 /// constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
300 /// will be rotated so it becomes a constraint on the final pixel height, and any
301 /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
302 /// will be rotated so it becomes a constraint on the final pixel width.
303 #[inline]
304 pub fn rotate(self, rotation: PdfPageRenderRotation, do_rotate_constraints: bool) -> Self {
305 self.rotate_if_portrait(rotation, do_rotate_constraints)
306 .rotate_if_landscape(rotation, do_rotate_constraints)
307 }
308
309 /// Applies the given clockwise rotation settings to the [PdfPage] during rendering, if the page
310 /// is in portrait orientation. If the given flag is set to `true` and the given
311 /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
312 /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
313 /// will be rotated so it becomes a constraint on the final pixel height and any
314 /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
315 /// will be rotated so it becomes a constraint on the final pixel width.
316 #[inline]
317 pub fn rotate_if_portrait(
318 mut self,
319 rotation: PdfPageRenderRotation,
320 do_rotate_constraints: bool,
321 ) -> Self {
322 self.portrait_rotation = rotation;
323
324 if rotation == PdfPageRenderRotation::Degrees90
325 || rotation == PdfPageRenderRotation::Degrees270
326 {
327 self.portrait_rotation_do_rotate_constraints = do_rotate_constraints;
328 }
329
330 self
331 }
332
333 /// Applies the given rotation settings to the [PdfPage] during rendering, if the page
334 /// is in landscape orientation. If the given flag is set to `true` and the given
335 /// rotation setting is [PdfPageRenderRotation::Degrees90] or [PdfPageRenderRotation::Degrees270]
336 /// then any maximum constraint on the final pixel width set by a call to [PdfRenderConfig::set_maximum_width()]
337 /// will be rotated so it becomes a constraint on the final pixel height and any
338 /// maximum constraint on the final pixel height set by a call to [PdfRenderConfig::set_maximum_height()]
339 /// will be rotated so it becomes a constraint on the final pixel width.
340 #[inline]
341 pub fn rotate_if_landscape(
342 mut self,
343 rotation: PdfPageRenderRotation,
344 do_rotate_constraints: bool,
345 ) -> Self {
346 self.landscape_rotation = rotation;
347
348 if rotation == PdfPageRenderRotation::Degrees90
349 || rotation == PdfPageRenderRotation::Degrees270
350 {
351 self.landscape_rotation_do_rotate_constraints = do_rotate_constraints;
352 }
353
354 self
355 }
356
357 /// Sets the pixel format that will be used during rendering of the [PdfPage].
358 /// The default is [PdfBitmapFormat::BGRA].
359 #[inline]
360 pub fn set_format(mut self, format: PdfBitmapFormat) -> Self {
361 self.format = format;
362
363 self
364 }
365
366 /// Controls whether the destination bitmap should be cleared by setting every pixel to a
367 /// known color value before rendering the [PdfPage]. The default is `true`.
368 /// The color used during clearing can be customised by calling [PdfRenderConfig::set_clear_color()].
369 #[inline]
370 pub fn clear_before_rendering(mut self, do_clear: bool) -> Self {
371 self.do_clear_bitmap_before_rendering = do_clear;
372
373 self
374 }
375
376 /// Sets the color applied to every pixel in the destination bitmap when clearing the bitmap
377 /// before rendering the [PdfPage]. The default is [PdfColor::WHITE]. This setting
378 /// has no effect if [PdfRenderConfig::clear_before_rendering()] is set to `false`.
379 #[inline]
380 pub fn set_clear_color(mut self, color: PdfColor) -> Self {
381 self.clear_color = color;
382
383 self
384 }
385
386 /// Controls whether form data widgets and user-supplied form data should be included
387 /// during rendering of the [PdfPage]. The default is `true`.
388 ///
389 /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
390 /// a custom transformation matrix, but not both at the same time. Applying any transformation
391 /// automatically sets this value to `false`, disabling rendering of form data.
392 #[inline]
393 pub fn render_form_data(mut self, do_render: bool) -> Self {
394 self.do_render_form_data = do_render;
395
396 self
397 }
398
399 /// Controls whether user-supplied annotations should be included during rendering of
400 /// the [PdfPage]. The default is `true`.
401 #[inline]
402 pub fn render_annotations(mut self, do_render: bool) -> Self {
403 self.do_set_flag_render_annotations = do_render;
404
405 self
406 }
407
408 /// Controls whether text rendering should be optimized for LCD display.
409 /// The default is `false`.
410 /// Has no effect if anti-aliasing of text has been disabled by a call to
411 /// `PdfRenderConfig::set_text_smoothing(false)`.
412 #[inline]
413 pub fn use_lcd_text_rendering(mut self, do_set_flag: bool) -> Self {
414 self.do_set_flag_use_lcd_text_rendering = do_set_flag;
415
416 self
417 }
418
419 /// Controls whether platform text rendering should be disabled on platforms that support it.
420 /// The alternative is for Pdfium to render all text internally, which may give more
421 /// consistent rendering results across platforms but may also be slower.
422 /// The default is `false`.
423 #[inline]
424 pub fn disable_native_text_rendering(mut self, do_set_flag: bool) -> Self {
425 self.do_set_flag_no_native_text = do_set_flag;
426
427 self
428 }
429
430 /// Controls whether rendering output should be grayscale rather than full color.
431 /// The default is `false`.
432 #[inline]
433 pub fn use_grayscale_rendering(mut self, do_set_flag: bool) -> Self {
434 self.do_set_flag_grayscale = do_set_flag;
435
436 self
437 }
438
439 /// Controls whether Pdfium should limit its image cache size during rendering.
440 /// A smaller cache size may result in lower memory usage at the cost of slower rendering.
441 /// The default is `false`.
442 #[inline]
443 pub fn limit_render_image_cache_size(mut self, do_set_flag: bool) -> Self {
444 self.do_set_flag_render_limited_image_cache = do_set_flag;
445
446 self
447 }
448
449 /// Controls whether Pdfium should always use halftone for image stretching.
450 /// Halftone image stretching is often higher quality than linear image stretching
451 /// but is much slower. The default is `false`.
452 #[inline]
453 pub fn force_half_tone(mut self, do_set_flag: bool) -> Self {
454 self.do_set_flag_render_force_half_tone = do_set_flag;
455
456 self
457 }
458
459 /// Controls whether Pdfium should render for printing. The default is `false`.
460 ///
461 /// Certain PDF files may stipulate different quality settings for on-screen display
462 /// compared to printing. For these files, changing this setting to `true` will result
463 /// in a higher quality rendered bitmap but slower performance. For PDF files that do
464 /// not stipulate different quality settings, changing this setting will have no effect.
465 #[inline]
466 pub fn use_print_quality(mut self, do_set_flag: bool) -> Self {
467 self.do_set_flag_render_for_printing = do_set_flag;
468
469 self
470 }
471
472 /// Controls whether rendered text should be anti-aliased.
473 /// The default is `true`.
474 /// The enabling of LCD-optimized text rendering via a call to
475 /// `PdfiumBitmapConfig::use_lcd_text_rendering(true)` has no effect if this flag
476 /// is set to `false`.
477 #[inline]
478 pub fn set_text_smoothing(mut self, do_set_flag: bool) -> Self {
479 self.do_set_flag_render_no_smooth_text = !do_set_flag;
480
481 self
482 }
483
484 /// Controls whether rendered images should be anti-aliased.
485 /// The default is `true`.
486 #[inline]
487 pub fn set_image_smoothing(mut self, do_set_flag: bool) -> Self {
488 self.do_set_flag_render_no_smooth_image = !do_set_flag;
489
490 self
491 }
492
493 /// Controls whether rendered vector paths should be anti-aliased.
494 /// The default is `true`.
495 #[inline]
496 pub fn set_path_smoothing(mut self, do_set_flag: bool) -> Self {
497 self.do_set_flag_render_no_smooth_path = !do_set_flag;
498
499 self
500 }
501
502 /// Controls whether the byte order of generated image data should be reversed
503 /// during rendering. The default is `true`, so that Pdfium returns pixel data as
504 /// four-channel RGBA rather than its default of four-channel BGRA.
505 ///
506 /// There should generally be no need to change this flag unless you want to do raw
507 /// image processing and specifically need the pixel data returned by the
508 /// [PdfBitmap::as_raw_bytes()] function to be in BGR8 format.
509 #[inline]
510 pub fn set_reverse_byte_order(mut self, do_set_flag: bool) -> Self {
511 self.do_set_flag_reverse_byte_order = do_set_flag;
512
513 self
514 }
515
516 /// Controls whether rendered vector fill paths need to be stroked.
517 /// The default is `false`.
518 #[inline]
519 pub fn render_fills_as_strokes(mut self, do_set_flag: bool) -> Self {
520 self.do_set_flag_convert_fill_to_stroke = do_set_flag;
521
522 self
523 }
524
525 /// Highlights all rendered form fields with the given color.
526 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
527 #[inline]
528 pub fn highlight_all_form_fields(self, color: PdfColor) -> Self {
529 self.highlight_form_fields_of_type(PdfFormFieldType::Unknown, color)
530 }
531
532 /// Highlights all rendered push button form fields with the given color.
533 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
534 #[inline]
535 pub fn highlight_button_form_fields(self, color: PdfColor) -> Self {
536 self.highlight_form_fields_of_type(PdfFormFieldType::PushButton, color)
537 }
538
539 /// Highlights all rendered checkbox form fields with the given color.
540 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
541 #[inline]
542 pub fn highlight_checkbox_form_fields(self, color: PdfColor) -> Self {
543 self.highlight_form_fields_of_type(PdfFormFieldType::Checkbox, color)
544 }
545
546 /// Highlights all rendered radio button form fields with the given color.
547 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
548 #[inline]
549 pub fn highlight_radio_button_form_fields(self, color: PdfColor) -> Self {
550 self.highlight_form_fields_of_type(PdfFormFieldType::RadioButton, color)
551 }
552
553 /// Highlights all rendered combobox form fields with the given color.
554 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
555 #[inline]
556 pub fn highlight_combobox_form_fields(self, color: PdfColor) -> Self {
557 self.highlight_form_fields_of_type(PdfFormFieldType::ComboBox, color)
558 }
559
560 /// Highlights all rendered listbox form fields with the given color.
561 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
562 #[inline]
563 pub fn highlight_listbox_form_fields(self, color: PdfColor) -> Self {
564 self.highlight_form_fields_of_type(PdfFormFieldType::ListBox, color)
565 }
566
567 /// Highlights all rendered text entry form fields with the given color.
568 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
569 #[inline]
570 pub fn highlight_text_form_fields(self, color: PdfColor) -> Self {
571 self.highlight_form_fields_of_type(PdfFormFieldType::Text, color)
572 }
573
574 /// Highlights all rendered signature form fields with the given color.
575 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
576 #[inline]
577 pub fn highlight_signature_form_fields(self, color: PdfColor) -> Self {
578 self.highlight_form_fields_of_type(PdfFormFieldType::Signature, color)
579 }
580
581 /// Highlights all rendered form fields matching the given type with the given color.
582 /// Note that specifying a solid color with no opacity will overprint any user data in the field.
583 #[inline]
584 pub fn highlight_form_fields_of_type(
585 mut self,
586 form_field_type: PdfFormFieldType,
587 color: PdfColor,
588 ) -> Self {
589 if let Some(form_field_highlight) = self.form_field_highlight.as_mut() {
590 form_field_highlight.push((form_field_type, color));
591 } else {
592 self.form_field_highlight = Some(vec![(form_field_type, color)]);
593 }
594
595 self
596 }
597
598 create_transform_setters!(
599 Self,
600 Result<Self, PdfiumError>,
601 "the [PdfPage] during rendering",
602 "the [PdfPage] during rendering.",
603 "the [PdfPage] during rendering,",
604 "Pdfium's rendering pipeline supports _either_ rendering with form data _or_ rendering with
605 a custom transformation matrix, but not both at the same time. Applying any transformation
606 automatically disables rendering of form data. If you must render form data while simultaneously
607 applying transformations, consider using the [PdfPage::flatten()] function to flatten the
608 form elements and form data into the containing page."
609 );
610
611 // The internal implementation of the transform() function used by the create_transform_setters!() macro.
612 fn transform_impl(
613 mut self,
614 a: PdfMatrixValue,
615 b: PdfMatrixValue,
616 c: PdfMatrixValue,
617 d: PdfMatrixValue,
618 e: PdfMatrixValue,
619 f: PdfMatrixValue,
620 ) -> Result<Self, PdfiumError> {
621 let result = self
622 .transformation_matrix
623 .multiply(PdfMatrix::new(a, b, c, d, e, f));
624
625 if result.determinant() == 0.0 {
626 Err(PdfiumError::InvalidTransformationMatrix)
627 } else {
628 self.transformation_matrix = result;
629 self.do_render_form_data = false;
630
631 Ok(self)
632 }
633 }
634
635 // The internal implementation of the reset_matrix() function used by the create_transform_setters!() macro.
636 fn reset_matrix_impl(mut self, matrix: PdfMatrix) -> Result<Self, PdfiumError> {
637 self.transformation_matrix = matrix;
638
639 Ok(self)
640 }
641
642 /// Clips rendering output to the given pixel coordinates. Pdfium will not render outside
643 /// the clipping area; any existing image data in the destination [PdfBitmap] will remain
644 /// intact.
645 ///
646 /// Pdfium's rendering pipeline supports _either_ rendering with form data _or_ clipping rendering
647 /// output, but not both at the same time. Applying a clipping rectangle automatically disables
648 /// rendering of form data. If you must render form data while simultaneously applying a
649 /// clipping rectangle, consider using the [PdfPage::flatten()] function to flatten the
650 /// form elements and form data into the containing page.
651 #[inline]
652 pub fn clip(mut self, left: Pixels, top: Pixels, right: Pixels, bottom: Pixels) -> Self {
653 self.clip_rect = Some((left, top, right, bottom));
654 self.do_render_form_data = false;
655
656 self
657 }
658
659 /// Sets the position of the page's top-left corner within the destination [PdfBitmap],
660 /// in bitmap pixel coordinates.
661 ///
662 /// The default is `(0, 0)`, which renders the page's top-left corner at the bitmap's
663 /// top-left corner. Negative offsets push the page off the bitmap's top-left edge;
664 /// positive offsets push it down and right. The rendered page is clipped to the
665 /// destination bitmap's own width and height, so a strip-sized destination bitmap
666 /// combined with a negative `y` offset can be used to render a horizontal strip from
667 /// a large page without having to render the page in its entirety.
668 ///
669 /// To render rows `[y_offset, y_offset + strip_height)` of a page into a strip-sized
670 /// destination bitmap, call `set_origin(0, -y_offset)` and pass a destination bitmap
671 /// with a height of `strip_height`.
672 ///
673 /// Pdfium's rendering pipeline _either_ rendering with form data _or_ rendering with
674 /// a custom transformation matrix, but not both at the same time. Since `set_origin()`
675 /// affects rendering with form data, it is disabled automatically on applying any
676 /// transformation.
677 #[inline]
678 pub fn set_origin(mut self, left: Pixels, top: Pixels) -> Self {
679 self.start_x = left;
680 self.start_y = top;
681
682 self
683 }
684
685 /// Computes the pixel dimensions and rotation settings for the given [PdfPage]
686 /// based on the configuration of this [PdfRenderConfig].
687 #[inline]
688 pub(crate) fn apply_to_page(&self, page: &PdfPage) -> PdfPageRenderSettings {
689 let source_width = page.width();
690
691 let source_height = page.height();
692
693 let source_orientation =
694 PdfPageOrientation::from_width_and_height(source_width, source_height);
695
696 // Do we need to apply any rotation?
697
698 let (target_rotation, do_rotate_constraints) = if source_orientation == Portrait
699 && self.portrait_rotation != PdfPageRenderRotation::None
700 {
701 (
702 self.portrait_rotation,
703 self.portrait_rotation_do_rotate_constraints,
704 )
705 } else if source_orientation == Landscape
706 && self.landscape_rotation != PdfPageRenderRotation::None
707 {
708 (
709 self.landscape_rotation,
710 self.landscape_rotation_do_rotate_constraints,
711 )
712 } else {
713 (PdfPageRenderRotation::None, false)
714 };
715
716 let (output_width, output_height, width_scale, height_scale) = if self.use_auto_scaling {
717 // Compute output width and height based on target sizes and page dimensions.
718
719 let width_scale = if let Some(scale) = self.scale_width_factor {
720 Some(scale)
721 } else {
722 self.target_width
723 .map(|target| (target as f32) / source_width.value)
724 };
725
726 let height_scale = if let Some(scale) = self.scale_height_factor {
727 Some(scale)
728 } else {
729 self.target_height
730 .map(|target| (target as f32) / source_height.value)
731 };
732
733 // Maintain source aspect ratio if only one dimension's scale is set.
734
735 let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) =
736 match (width_scale, height_scale) {
737 (Some(width_scale), Some(height_scale)) => {
738 (width_scale == height_scale, width_scale, height_scale)
739 }
740 (Some(width_scale), None) => (true, width_scale, width_scale),
741 (None, Some(height_scale)) => (true, height_scale, height_scale),
742 (None, None) => {
743 // Set default scale to 1.0 if neither dimension is specified.
744
745 (false, 1.0, 1.0)
746 }
747 };
748
749 // Apply constraints on maximum width and height, if any.
750
751 let (source_width, source_height, width_constraint, height_constraint) =
752 if do_rotate_constraints {
753 (
754 source_height,
755 source_width,
756 self.maximum_height,
757 self.maximum_width,
758 )
759 } else {
760 (
761 source_width,
762 source_height,
763 self.maximum_width,
764 self.maximum_height,
765 )
766 };
767
768 if let Some(maximum) = width_constraint {
769 let maximum = maximum as f32;
770
771 if source_width.value * width_scale > maximum {
772 // Constrain the width, so it does not exceed the maximum.
773
774 width_scale = maximum / source_width.value;
775
776 if do_maintain_aspect_ratio {
777 height_scale = width_scale;
778 }
779 }
780 }
781
782 if let Some(maximum) = height_constraint {
783 let maximum = maximum as f32;
784
785 if source_height.value * height_scale > maximum {
786 // Constrain the height, so it does not exceed the maximum.
787
788 height_scale = maximum / source_height.value;
789
790 if do_maintain_aspect_ratio {
791 width_scale = height_scale;
792 }
793 }
794 }
795
796 (
797 (source_width.value * width_scale).round() as c_int,
798 (source_height.value * height_scale).round() as c_int,
799 width_scale,
800 height_scale,
801 )
802 } else {
803 // Take output width and height directly from user's fixed settings.
804
805 (
806 self.fixed_width.unwrap_or(0) as c_int,
807 self.fixed_height.unwrap_or(0) as c_int,
808 self.scale_width_factor.unwrap_or(1.0),
809 self.scale_height_factor.unwrap_or(1.0),
810 )
811 };
812
813 // Compose render flags.
814
815 let mut render_flags = 0;
816
817 if self.do_set_flag_render_annotations {
818 render_flags |= FPDF_ANNOT;
819 }
820
821 if self.do_set_flag_use_lcd_text_rendering {
822 render_flags |= FPDF_LCD_TEXT;
823 }
824
825 if self.do_set_flag_no_native_text {
826 render_flags |= FPDF_NO_NATIVETEXT;
827 }
828
829 if self.do_set_flag_grayscale {
830 render_flags |= FPDF_GRAYSCALE;
831 }
832
833 if self.do_set_flag_render_limited_image_cache {
834 render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
835 }
836
837 if self.do_set_flag_render_force_half_tone {
838 render_flags |= FPDF_RENDER_FORCEHALFTONE;
839 }
840
841 if self.do_set_flag_render_for_printing {
842 render_flags |= FPDF_PRINTING;
843 }
844
845 if self.do_set_flag_render_no_smooth_text {
846 render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
847 }
848
849 if self.do_set_flag_render_no_smooth_image {
850 render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
851 }
852
853 if self.do_set_flag_render_no_smooth_path {
854 render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
855 }
856
857 if self.do_set_flag_reverse_byte_order {
858 render_flags |= FPDF_REVERSE_BYTE_ORDER;
859 }
860
861 if self.do_set_flag_convert_fill_to_stroke {
862 render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
863 }
864
865 // Pages can be rendered either _with_ transformation matrices and clipping
866 // but _without_ form data, or _with_ form data but _without_ transformation matrices
867 // and clipping. We need to be prepared for either option. If rendering of form data
868 // is disabled, then the scaled output width and height and any user-specified
869 // 90-degree rotation need to be applied to the transformation matrix now.
870
871 let transformation_matrix = if !self.do_render_form_data {
872 let result = if target_rotation != PdfPageRenderRotation::None {
873 // Translate the origin to the center of the page before rotating.
874
875 let (delta_x, delta_y) = match target_rotation {
876 PdfPageRenderRotation::None => unreachable!(),
877 PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
878 PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
879 PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
880 };
881
882 self.transformation_matrix
883 .translate(delta_x, delta_y)
884 .and_then(|result| {
885 result.rotate_clockwise_degrees(target_rotation.as_degrees())
886 })
887 } else {
888 Ok(self.transformation_matrix)
889 };
890
891 result.and_then(|result| result.scale(width_scale, height_scale))
892 } else {
893 Ok(PdfMatrix::identity())
894 };
895
896 PdfPageRenderSettings {
897 start_x: self.start_x as c_int,
898 start_y: self.start_y as c_int,
899 width: output_width,
900 height: output_height,
901 format: self.format.as_pdfium() as c_int,
902 rotate: target_rotation.as_pdfium(),
903 do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
904 clear_color: self.clear_color.as_pdfium_color(),
905 do_render_form_data: self.do_render_form_data,
906 form_field_highlight: if !self.do_render_form_data
907 || self.form_field_highlight.is_none()
908 {
909 None
910 } else {
911 Some(
912 self.form_field_highlight
913 .as_ref()
914 .unwrap()
915 .iter()
916 .map(|(form_field_type, color)| {
917 (
918 form_field_type.as_pdfium() as c_int,
919 color.as_pdfium_color_with_alpha(),
920 )
921 })
922 .collect::<Vec<_>>(),
923 )
924 },
925 matrix: transformation_matrix
926 .unwrap_or(PdfMatrix::IDENTITY)
927 .as_pdfium(),
928 clipping: if let Some((left, top, right, bottom)) = self.clip_rect {
929 FS_RECTF {
930 left: left as f32,
931 top: top as f32,
932 right: right as f32,
933 bottom: bottom as f32,
934 }
935 } else {
936 FS_RECTF {
937 left: 0.0,
938 top: 0.0,
939 right: output_width as f32,
940 bottom: output_height as f32,
941 }
942 },
943 render_flags: render_flags as c_int,
944 is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
945 }
946 }
947}
948
949impl Default for PdfRenderConfig {
950 #[inline]
951 fn default() -> Self {
952 PdfRenderConfig::new()
953 }
954}
955
956/// Finalized rendering settings, ready to be passed to a Pdfium rendering function.
957/// Generated by calling [PdfRenderConfig::apply_to_page()].
958#[derive(Debug, Clone)]
959pub(crate) struct PdfPageRenderSettings {
960 pub(crate) start_x: c_int,
961 pub(crate) start_y: c_int,
962 pub(crate) width: c_int,
963 pub(crate) height: c_int,
964 pub(crate) format: c_int,
965 pub(crate) rotate: c_int,
966 pub(crate) do_clear_bitmap_before_rendering: bool,
967 pub(crate) clear_color: FPDF_DWORD,
968 pub(crate) do_render_form_data: bool,
969 pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
970 pub(crate) matrix: FS_MATRIX,
971 pub(crate) clipping: FS_RECTF,
972 pub(crate) render_flags: c_int,
973 pub(crate) is_reversed_byte_order_flag_set: bool,
974}
975
976#[cfg(test)]
977mod tests {
978 use crate::prelude::*;
979 use crate::utils::test::test_bind_to_pdfium; // Temporary until PdfParagraph is included in the prelude.
980
981 #[test]
982 fn test_set_origin_default_settings_zero() -> Result<(), PdfiumError> {
983 let render_settings = get_render_settings_from_config(PdfRenderConfig::new())?;
984
985 assert_eq!(render_settings.start_x, 0);
986 assert_eq!(render_settings.start_y, 0);
987
988 Ok(())
989 }
990
991 #[test]
992 fn test_set_origin_negative_offsets_pass_through() -> Result<(), PdfiumError> {
993 let render_settings =
994 get_render_settings_from_config(PdfRenderConfig::new().set_origin(-50, -400))?;
995
996 assert_eq!(render_settings.start_x, -50);
997 assert_eq!(render_settings.start_y, -400);
998
999 Ok(())
1000 }
1001
1002 #[test]
1003 fn test_set_origin_positive_offsets_pass_through() -> Result<(), PdfiumError> {
1004 let render_settings =
1005 get_render_settings_from_config(PdfRenderConfig::new().set_origin(75, 125))?;
1006
1007 assert_eq!(render_settings.start_x, 75);
1008 assert_eq!(render_settings.start_y, 125);
1009
1010 Ok(())
1011 }
1012
1013 #[test]
1014 fn test_set_origin_strips_stitch_to_full_page() -> Result<(), PdfiumError> {
1015 // Compare a full page render of a sample page against a render assembled
1016 // from multiple stripped renders. The results should be identical.
1017
1018 let pdfium = test_bind_to_pdfium();
1019
1020 let document = pdfium.load_pdf_from_file("./test/image-test.pdf", None)?;
1021 let page = document.pages().first()?;
1022
1023 // `image-test.pdf` is single-page A4: 595 × 842 points at 72 DPI outputs to
1024 // 595 × 842 pixels when rendered with `set_target_size(595, 842)`.
1025
1026 let target_width: Pixels = 1000;
1027 let target_height: Pixels = 800;
1028 let strips: i32 = 4; // The number of render passes, with each pass outputting a single strip
1029 let strip_height: Pixels = target_height / strips as Pixels;
1030
1031 assert_eq!(strip_height * strips, target_height);
1032
1033 // First, create a full page render of the target page.
1034
1035 let full_bitmap =
1036 page.render_with_config(&PdfRenderConfig::new().set_fixed_size(target_width, target_height))?;
1037 let full_bytes = full_bitmap.as_image()?.to_rgba8().into_raw();
1038 let row_bytes = (target_width as usize) * 4;
1039
1040 assert_eq!(full_bytes.len(), row_bytes * (target_height as usize));
1041
1042 // Next, render the page in strips...
1043
1044 let mut stitched: Vec<u8> = Vec::with_capacity(full_bytes.len());
1045
1046 for i in 0..strips {
1047 let mut strip_bitmap = PdfBitmap::empty(target_width, strip_height, full_bitmap.format()?)?;
1048
1049 page.render_into_bitmap_with_config(
1050 &mut strip_bitmap,
1051 &&PdfRenderConfig::new()
1052 .set_fixed_size(target_width, target_height)
1053 .set_origin(0, -(i * strip_height)),
1054 )?;
1055
1056 // ... joining each strip in memory to build a complete rendered image.
1057
1058 stitched.extend_from_slice(strip_bitmap.as_image()?.to_rgba8().as_raw());
1059 }
1060
1061 // The render output assembled from the strips should exactly match the full page render.
1062
1063 println!("{}, {}, {}, {}", strip_height, strip_height * strips, target_height, row_bytes);
1064 assert_eq!(stitched.len(), full_bytes.len());
1065
1066 let mut sums = [0u64; 4]; // Track per-channel mean drift between stitched and full-page renders.
1067 let pixel_count = full_bytes.len() / 4;
1068
1069 for px in 0..pixel_count {
1070 for ch in 0..4 {
1071 let a = stitched[px * 4 + ch] as i32;
1072 let b = full_bytes[px * 4 + ch] as i32;
1073 sums[ch] += (a - b).unsigned_abs() as u64;
1074 }
1075 }
1076
1077 let n = pixel_count as f64;
1078 let max_drift = (0..4).map(|ch| sums[ch] as f64 / n).fold(0.0_f64, f64::max);
1079
1080 assert!(
1081 max_drift < 5.0,
1082 "stitched-vs-full per-channel mean drift {:.3}/255 exceeds tolerance",
1083 max_drift
1084 );
1085
1086 Ok(())
1087 }
1088
1089 #[test]
1090 fn test_fixed_size_render_config() -> Result<(), PdfiumError> {
1091 let render_settings =
1092 get_render_settings_from_config(PdfRenderConfig::new().set_fixed_size(2000, 2000))?;
1093
1094 assert_eq!(render_settings.width, 2000);
1095 assert_eq!(render_settings.height, 2000);
1096
1097 // Applying scaling does not affect the rendered bitmap size.
1098
1099 let render_settings = get_render_settings_from_config(
1100 PdfRenderConfig::new()
1101 .set_fixed_size(2000, 2000)
1102 .scale_page_by_factor(5.0),
1103 )?;
1104
1105 assert_eq!(render_settings.width, 2000);
1106 assert_eq!(render_settings.height, 2000);
1107
1108 Ok(())
1109 }
1110
1111 #[test]
1112 fn test_target_size_render_config() -> Result<(), PdfiumError> {
1113 let render_settings = get_render_settings_from_config(
1114 PdfRenderConfig::new().scale_page_to_display_size(2000, 2000),
1115 )?;
1116
1117 assert_eq!(render_settings.width, 1414);
1118 assert_eq!(render_settings.height, 2000);
1119
1120 // Applying scaling does affected the rendered bitmap size.
1121
1122 let render_settings = get_render_settings_from_config(
1123 PdfRenderConfig::new()
1124 .set_target_size(2000, 2000)
1125 .scale_page_by_factor(5.0),
1126 )?;
1127
1128 assert_eq!(render_settings.width, 2976);
1129 assert_eq!(render_settings.height, 4209);
1130
1131 Ok(())
1132 }
1133
1134 fn get_render_settings_from_config(
1135 config: PdfRenderConfig,
1136 ) -> Result<PdfPageRenderSettings, PdfiumError> {
1137 let pdfium = test_bind_to_pdfium();
1138
1139 let mut document = pdfium.create_new_pdf()?;
1140 let page = document
1141 .pages_mut()
1142 .create_page_at_start(PdfPagePaperSize::Portrait(PdfPagePaperStandardSize::A4))?;
1143
1144 Ok(config.apply_to_page(&page))
1145 }
1146}