1use 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#[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 #[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
54pub 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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
306 pub fn set_maximum_width(mut self, width: Pixels) -> Self {
307 self.maximum_width = Some(width);
308
309 self
310 }
311
312 #[inline]
314 pub fn set_maximum_height(mut self, height: Pixels) -> Self {
315 self.maximum_height = Some(height);
316
317 self
318 }
319
320 #[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 #[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 #[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 #[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 #[inline]
390 pub fn set_format(mut self, format: PdfBitmapFormat) -> Self {
391 self.format = format;
392
393 self
394 }
395
396 #[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 #[inline]
410 pub fn set_clear_color(mut self, color: PdfColor) -> Self {
411 self.clear_color = color;
412
413 self
414 }
415
416 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
615 pub(crate) fn apply_to_page(&self, page: &PdfPage) -> PdfPageRenderSettings {
616 let source_width = page.width();
617
618 let source_height = page.height();
619
620 let source_orientation = PdfPageOrientation::from_width_and_height(source_width, source_height);
621
622 let (target_rotation, do_rotate_constraints) =
623 if source_orientation == Portrait && self.portrait_rotation != PdfPageRenderRotation::None {
624 (self.portrait_rotation, self.portrait_rotation_do_rotate_constraints)
625 } else if source_orientation == Landscape && self.landscape_rotation != PdfPageRenderRotation::None {
626 (self.landscape_rotation, self.landscape_rotation_do_rotate_constraints)
627 } else {
628 (PdfPageRenderRotation::None, false)
629 };
630
631 let (output_width, output_height, width_scale, height_scale) = if self.use_auto_scaling {
632 let width_scale = if let Some(scale) = self.scale_width_factor {
633 Some(scale)
634 } else {
635 self.target_width.map(|target| (target as f32) / source_width.value)
636 };
637
638 let height_scale = if let Some(scale) = self.scale_height_factor {
639 Some(scale)
640 } else {
641 self.target_height.map(|target| (target as f32) / source_height.value)
642 };
643
644 let (do_maintain_aspect_ratio, mut width_scale, mut height_scale) = match (width_scale, height_scale) {
645 (Some(width_scale), Some(height_scale)) => (width_scale == height_scale, width_scale, height_scale),
646 (Some(width_scale), None) => (true, width_scale, width_scale),
647 (None, Some(height_scale)) => (true, height_scale, height_scale),
648 (None, None) => (false, 1.0, 1.0),
649 };
650
651 let (source_width, source_height, width_constraint, height_constraint) = if do_rotate_constraints {
652 (source_height, source_width, self.maximum_height, self.maximum_width)
653 } else {
654 (source_width, source_height, self.maximum_width, self.maximum_height)
655 };
656
657 if let Some(maximum) = width_constraint {
658 let maximum = maximum as f32;
659
660 if source_width.value * width_scale > maximum {
661 width_scale = maximum / source_width.value;
662
663 if do_maintain_aspect_ratio {
664 height_scale = width_scale;
665 }
666 }
667 }
668
669 if let Some(maximum) = height_constraint {
670 let maximum = maximum as f32;
671
672 if source_height.value * height_scale > maximum {
673 height_scale = maximum / source_height.value;
674
675 if do_maintain_aspect_ratio {
676 width_scale = height_scale;
677 }
678 }
679 }
680
681 (
682 (source_width.value * width_scale).round() as c_int,
683 (source_height.value * height_scale).round() as c_int,
684 width_scale,
685 height_scale,
686 )
687 } else {
688 (
689 self.fixed_width.unwrap_or(0) as c_int,
690 self.fixed_height.unwrap_or(0) as c_int,
691 self.scale_width_factor.unwrap_or(1.0),
692 self.scale_height_factor.unwrap_or(1.0),
693 )
694 };
695
696 let mut render_flags = 0;
697
698 if self.do_set_flag_render_annotations {
699 render_flags |= FPDF_ANNOT;
700 }
701
702 if self.do_set_flag_use_lcd_text_rendering {
703 render_flags |= FPDF_LCD_TEXT;
704 }
705
706 if self.do_set_flag_no_native_text {
707 render_flags |= FPDF_NO_NATIVETEXT;
708 }
709
710 if self.do_set_flag_grayscale {
711 render_flags |= FPDF_GRAYSCALE;
712 }
713
714 if self.do_set_flag_render_limited_image_cache {
715 render_flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
716 }
717
718 if self.do_set_flag_render_force_half_tone {
719 render_flags |= FPDF_RENDER_FORCEHALFTONE;
720 }
721
722 if self.do_set_flag_render_for_printing {
723 render_flags |= FPDF_PRINTING;
724 }
725
726 if self.do_set_flag_render_no_smooth_text {
727 render_flags |= FPDF_RENDER_NO_SMOOTHTEXT;
728 }
729
730 if self.do_set_flag_render_no_smooth_image {
731 render_flags |= FPDF_RENDER_NO_SMOOTHIMAGE;
732 }
733
734 if self.do_set_flag_render_no_smooth_path {
735 render_flags |= FPDF_RENDER_NO_SMOOTHPATH;
736 }
737
738 if self.do_set_flag_reverse_byte_order {
739 render_flags |= FPDF_REVERSE_BYTE_ORDER;
740 }
741
742 if self.do_set_flag_convert_fill_to_stroke {
743 render_flags |= FPDF_CONVERT_FILL_TO_STROKE;
744 }
745
746 let transformation_matrix = if !self.do_render_form_data {
747 let result = if target_rotation != PdfPageRenderRotation::None {
748 let (delta_x, delta_y) = match target_rotation {
749 PdfPageRenderRotation::None => unreachable!(),
750 PdfPageRenderRotation::Degrees90 => (PdfPoints::ZERO, -source_width),
751 PdfPageRenderRotation::Degrees180 => (-source_width, -source_height),
752 PdfPageRenderRotation::Degrees270 => (-source_height, PdfPoints::ZERO),
753 };
754
755 self.transformation_matrix
756 .translate(delta_x, delta_y)
757 .and_then(|result| result.rotate_clockwise_degrees(target_rotation.as_degrees()))
758 } else {
759 Ok(self.transformation_matrix)
760 };
761
762 result.and_then(|result| result.scale(width_scale, height_scale))
763 } else {
764 Ok(PdfMatrix::identity())
765 };
766
767 PdfPageRenderSettings {
768 width: output_width,
769 height: output_height,
770 format: self.format.as_pdfium() as c_int,
771 rotate: target_rotation.as_pdfium(),
772 do_clear_bitmap_before_rendering: self.do_clear_bitmap_before_rendering,
773 clear_color: self.clear_color.as_pdfium_color(),
774 do_render_form_data: self.do_render_form_data,
775 form_field_highlight: None,
776 matrix: transformation_matrix.unwrap_or(PdfMatrix::IDENTITY).as_pdfium(),
777 clipping: if let Some((left, top, right, bottom)) = self.clip_rect {
778 FS_RECTF {
779 left: left as f32,
780 top: top as f32,
781 right: right as f32,
782 bottom: bottom as f32,
783 }
784 } else {
785 FS_RECTF {
786 left: 0.0,
787 top: 0.0,
788 right: output_width as f32,
789 bottom: output_height as f32,
790 }
791 },
792 render_flags: render_flags as c_int,
793 is_reversed_byte_order_flag_set: self.do_set_flag_reverse_byte_order,
794 }
795 }
796}
797
798impl Default for PdfRenderConfig {
799 #[inline]
800 fn default() -> Self {
801 PdfRenderConfig::new()
802 }
803}
804
805#[derive(Debug, Clone)]
808pub(crate) struct PdfPageRenderSettings {
809 pub(crate) width: c_int,
810 pub(crate) height: c_int,
811 pub(crate) format: c_int,
812 pub(crate) rotate: c_int,
813 pub(crate) do_clear_bitmap_before_rendering: bool,
814 pub(crate) clear_color: FPDF_DWORD,
815 pub(crate) do_render_form_data: bool,
816 pub(crate) form_field_highlight: Option<Vec<(c_int, (FPDF_DWORD, u8))>>,
817 pub(crate) matrix: FS_MATRIX,
818 pub(crate) clipping: FS_RECTF,
819 pub(crate) render_flags: c_int,
820 pub(crate) is_reversed_byte_order_flag_set: bool,
821}
822
823#[cfg(test)]
824mod tests {
825 use crate::prelude::*;
826 use crate::utils::test::test_bind_to_pdfium;
827
828 #[test]
829 fn test_fixed_size_render_config() -> Result<(), PdfiumError> {
830 let render_settings = get_render_settings_from_config(PdfRenderConfig::new().set_fixed_size(2000, 2000))?;
831
832 assert_eq!(render_settings.width, 2000);
833 assert_eq!(render_settings.height, 2000);
834
835 let render_settings = get_render_settings_from_config(
836 PdfRenderConfig::new()
837 .set_fixed_size(2000, 2000)
838 .scale_page_by_factor(5.0),
839 )?;
840
841 assert_eq!(render_settings.width, 2000);
842 assert_eq!(render_settings.height, 2000);
843
844 Ok(())
845 }
846
847 #[test]
848 fn test_target_size_render_config() -> Result<(), PdfiumError> {
849 let render_settings =
850 get_render_settings_from_config(PdfRenderConfig::new().scale_page_to_display_size(2000, 2000))?;
851
852 assert_eq!(render_settings.width, 1414);
853 assert_eq!(render_settings.height, 2000);
854
855 let render_settings = get_render_settings_from_config(
856 PdfRenderConfig::new()
857 .set_target_size(2000, 2000)
858 .scale_page_by_factor(5.0),
859 )?;
860
861 assert_eq!(render_settings.width, 2976);
862 assert_eq!(render_settings.height, 4209);
863
864 Ok(())
865 }
866
867 fn get_render_settings_from_config(config: PdfRenderConfig) -> Result<PdfPageRenderSettings, PdfiumError> {
868 let pdfium = test_bind_to_pdfium();
869
870 let mut document = pdfium.create_new_pdf()?;
871 let page = document
872 .pages_mut()
873 .create_page_at_start(PdfPagePaperSize::Portrait(PdfPagePaperStandardSize::A4))?;
874
875 Ok(config.apply_to_page(&page))
876 }
877}