1use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::create_transform_setters;
7use crate::error::PdfiumError;
8use crate::pdf::color::PdfColor;
9use crate::pdf::document::page::index_cache::PdfPageIndexCache;
10use crate::pdf::document::page::object::path::PdfPathFillMode;
11use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
12use crate::pdf::document::page::object::{
13 PdfPageObject, PdfPageObjectBlendMode, PdfPageObjectCommon, PdfPageObjectLineCap,
14 PdfPageObjectLineJoin,
15};
16use crate::pdf::document::page::objects::common::{PdfPageObjectIndex, PdfPageObjectsCommon};
17use crate::pdf::document::page::{
18 PdfPage, PdfPageContentRegenerationStrategy, PdfPageObjectOwnership,
19};
20use crate::pdf::document::pages::{PdfPageIndex, PdfPages};
21use crate::pdf::document::PdfDocument;
22use crate::pdf::matrix::PdfMatrix;
23use crate::pdf::matrix::PdfMatrixValue;
24use crate::pdf::points::PdfPoints;
25use crate::pdf::quad_points::PdfQuadPoints;
26use crate::pdf::rect::PdfRect;
27use crate::pdfium::Pdfium;
28use std::collections::HashMap;
29
30pub struct PdfPageGroupObject<'a> {
39 document_handle: FPDF_DOCUMENT,
40 page_handle: FPDF_PAGE,
41 ownership: PdfPageObjectOwnership,
42 object_handles: Vec<FPDF_PAGEOBJECT>,
43 bindings: &'a dyn PdfiumLibraryBindings,
44}
45
46impl<'a> PdfPageGroupObject<'a> {
47 #[inline]
48 pub(crate) fn from_pdfium(
49 document_handle: FPDF_DOCUMENT,
50 page_handle: FPDF_PAGE,
51 bindings: &'a dyn PdfiumLibraryBindings,
52 ) -> Self {
53 PdfPageGroupObject {
54 page_handle,
55 document_handle,
56 ownership: PdfPageObjectOwnership::owned_by_page(document_handle, page_handle),
57 object_handles: Vec::new(),
58 bindings,
59 }
60 }
61
62 pub fn empty(page: &'a PdfPage) -> Self {
65 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings())
66 }
67
68 pub fn new<F>(page: &'a PdfPage, predicate: F) -> Result<Self, PdfiumError>
71 where
72 F: FnMut(&PdfPageObject) -> bool,
73 {
74 let mut result =
75 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
76
77 for mut object in page.objects().iter().filter(predicate) {
78 result.push(&mut object)?;
79 }
80
81 Ok(result)
82 }
83
84 #[inline]
87 pub fn from_vec(
88 page: &PdfPage<'a>,
89 mut objects: Vec<PdfPageObject<'a>>,
90 ) -> Result<Self, PdfiumError> {
91 Self::from_slice(page, objects.as_mut_slice())
92 }
93
94 pub fn from_slice(
97 page: &PdfPage<'a>,
98 objects: &mut [PdfPageObject<'a>],
99 ) -> Result<Self, PdfiumError> {
100 let mut result =
101 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
102
103 for object in objects.iter_mut() {
104 result.push(object)?;
105 }
106
107 Ok(result)
108 }
109
110 #[inline]
112 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
113 self.document_handle
114 }
115
116 #[inline]
118 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
119 self.page_handle
120 }
121
122 #[inline]
124 pub(crate) fn ownership(&self) -> &PdfPageObjectOwnership {
125 &self.ownership
126 }
127
128 #[inline]
130 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
131 self.bindings
132 }
133
134 #[inline]
136 pub fn len(&self) -> usize {
137 self.object_handles.len()
138 }
139
140 #[inline]
142 pub fn is_empty(&self) -> bool {
143 self.len() == 0
144 }
145
146 #[inline]
148 pub fn contains(&self, object: &PdfPageObject) -> bool {
149 self.object_handles.contains(&object.object_handle())
150 }
151
152 pub fn push(&mut self, object: &mut PdfPageObject<'a>) -> Result<(), PdfiumError> {
154 let page_handle = match object.ownership() {
155 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
156 _ => None,
157 };
158
159 if let Some(page_handle) = page_handle {
160 if page_handle != self.page_handle() {
161 return Err(PdfiumError::OwnershipAlreadyAttachedToDifferentPage);
174 } else {
175 true
178 }
179 } else {
180 object.add_object_to_page_handle(self.document_handle(), self.page_handle())?;
183
184 false
185 };
186
187 self.object_handles.push(object.object_handle());
188
189 Ok(())
190 }
191
192 pub fn append(&mut self, objects: &mut [PdfPageObject<'a>]) -> Result<(), PdfiumError> {
194 let content_regeneration_strategy =
197 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
198 self.document_handle(),
199 self.page_handle(),
200 )
201 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
202
203 let page_index =
204 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
205
206 if let Some(page_index) = page_index {
207 PdfPageIndexCache::cache_props_for_page(
208 self.document_handle(),
209 self.page_handle(),
210 page_index,
211 PdfPageContentRegenerationStrategy::Manual,
212 );
213 }
214
215 for object in objects.iter_mut() {
216 self.push(object)?;
217 }
218
219 if let Some(page_index) = page_index {
222 PdfPageIndexCache::cache_props_for_page(
223 self.document_handle(),
224 self.page_handle(),
225 page_index,
226 content_regeneration_strategy,
227 );
228 }
229
230 if content_regeneration_strategy
231 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
232 {
233 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
234 }
235
236 Ok(())
237 }
238
239 pub fn remove_objects_from_page(mut self) -> Result<(), PdfiumError> {
250 let content_regeneration_strategy =
253 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
254 self.document_handle(),
255 self.page_handle(),
256 )
257 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
258
259 let page_index =
260 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
261
262 if let Some(page_index) = page_index {
263 PdfPageIndexCache::cache_props_for_page(
264 self.document_handle(),
265 self.page_handle(),
266 page_index,
267 PdfPageContentRegenerationStrategy::Manual,
268 );
269 }
270
271 self.apply_to_each(|object| object.remove_object_from_page())?;
274 self.object_handles.clear();
275
276 let page_height = PdfPoints::new(self.bindings().FPDF_GetPageHeightF(self.page_handle()));
281
282 for index in 0..self.bindings().FPDFPage_CountObjects(self.page_handle()) {
283 let mut object = PdfPageObject::from_pdfium(
284 self.bindings()
285 .FPDFPage_GetObject(self.page_handle(), index),
286 self.ownership().clone(),
287 self.bindings(),
288 );
289
290 object.flip_vertically()?;
298 object.translate(PdfPoints::ZERO, page_height)?;
299 }
300
301 if let Some(page_index) = page_index {
304 PdfPageIndexCache::cache_props_for_page(
305 self.document_handle,
306 self.page_handle,
307 page_index,
308 content_regeneration_strategy,
309 );
310 }
311
312 if content_regeneration_strategy
313 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
314 {
315 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
316 }
317
318 Ok(())
319 }
320
321 #[inline]
323 pub fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject, PdfiumError> {
324 if let Some(handle) = self.object_handles.get(index) {
325 Ok(self.get_object_from_handle(handle))
326 } else {
327 Err(PdfiumError::PageObjectIndexOutOfBounds)
328 }
329 }
330
331 pub fn retain<F>(&mut self, f: F)
336 where
337 F: Fn(&PdfPageObject) -> bool,
338 {
339 let mut do_retain = vec![false; self.object_handles.len()];
348
349 for (index, handle) in self.object_handles.iter().enumerate() {
350 do_retain[index] = f(&self.get_object_from_handle(handle));
351 }
352
353 let mut index = 0;
356
357 self.object_handles.retain(|_| {
358 let do_retain = do_retain[index];
361
362 index += 1;
363
364 do_retain
365 });
366 }
367
368 #[inline]
373 pub fn retain_if_copyable(&mut self) {
374 self.retain(|object| object.is_copyable());
375 }
376
377 #[inline]
379 pub fn is_copyable(&self) -> bool {
380 self.iter().all(|object| object.is_copyable())
381 }
382
383 pub fn try_copy_onto_existing_page<'b>(
395 &self,
396 destination: &mut PdfPage<'b>,
397 ) -> Result<PdfPageGroupObject<'b>, PdfiumError> {
398 if !self.is_copyable() {
399 return Err(PdfiumError::GroupContainsNonCopyablePageObjects);
400 }
401
402 let mut group = destination.objects_mut().create_empty_group();
403
404 for handle in self.object_handles.iter() {
405 let source = self.get_object_from_handle(handle);
406
407 let clone =
408 source.try_copy_impl(destination.document_handle(), destination.bindings())?;
409
410 group.push(&mut destination.objects_mut().add_object(clone)?)?;
411 }
412
413 Ok(group)
414 }
415
416 #[inline]
431 pub fn copy_onto_new_page_at_start(
432 &self,
433 destination: &PdfDocument,
434 ) -> Result<(), PdfiumError> {
435 self.copy_onto_new_page_at_index(0, destination)
436 }
437
438 #[inline]
453 pub fn copy_onto_new_page_at_end(&self, destination: &PdfDocument) -> Result<(), PdfiumError> {
454 self.copy_onto_new_page_at_index(destination.pages().len(), destination)
455 }
456
457 pub fn copy_onto_new_page_at_index(
472 &self,
473 index: PdfPageIndex,
474 destination: &PdfDocument,
475 ) -> Result<(), PdfiumError> {
476 let cache = Pdfium::pdfium_document_handle_to_result(
487 self.bindings.FPDF_CreateNewDocument(),
488 self.bindings,
489 )?;
490
491 if let Some(source_page_index) =
492 PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
493 {
494 PdfPages::copy_page_range_between_documents(
495 self.document_handle,
496 source_page_index..=source_page_index,
497 cache.handle(),
498 0,
499 self.bindings,
500 )?;
501 } else {
502 return Err(PdfiumError::SourcePageIndexNotInCache);
503 }
504
505 let mut objects_to_discard = HashMap::new();
512
513 for index in 0..self.bindings.FPDFPage_CountObjects(self.page_handle) {
514 let object = PdfPageObject::from_pdfium(
515 self.bindings().FPDFPage_GetObject(self.page_handle, index),
516 self.ownership().clone(),
517 self.bindings(),
518 );
519
520 if !self.contains(&object) {
521 objects_to_discard.insert(
522 (object.bounds()?, object.matrix()?, object.object_type()),
523 true,
524 );
525 }
526 }
527
528 cache
532 .pages()
533 .get(0)?
534 .objects()
535 .create_group(|object| {
536 objects_to_discard.contains_key(&(
537 object.bounds().unwrap_or(PdfQuadPoints::ZERO),
538 object.matrix().unwrap_or(PdfMatrix::IDENTITY),
539 object.object_type(),
540 ))
541 })?
542 .remove_objects_from_page()?;
543
544 PdfPages::copy_page_range_between_documents(
548 cache.handle(),
549 0..=0,
550 destination.handle(),
551 index,
552 self.bindings,
553 )?;
554
555 Ok(())
556 }
557
558 #[inline]
560 pub fn iter(&'a self) -> PdfPageGroupObjectIterator<'a> {
561 PdfPageGroupObjectIterator::new(self)
562 }
563
564 #[inline]
566 pub fn text(&self) -> String {
567 self.text_separated("")
568 }
569
570 pub fn text_separated(&self, separator: &str) -> String {
573 let mut strings = Vec::with_capacity(self.len());
574
575 self.for_each(|object| {
576 if let Some(object) = object.as_text_object() {
577 strings.push(object.text());
578 }
579 });
580
581 strings.join(separator)
582 }
583
584 #[inline]
586 pub fn has_transparency(&self) -> bool {
587 self.object_handles.iter().any(|object_handle| {
588 PdfPageObject::from_pdfium(*object_handle, self.ownership().clone(), self.bindings())
589 .has_transparency()
590 })
591 }
592
593 pub fn bounds(&self) -> Result<PdfRect, PdfiumError> {
596 let mut bottom = PdfPoints::MAX;
597 let mut top = PdfPoints::MIN;
598 let mut left = PdfPoints::MAX;
599 let mut right = PdfPoints::MIN;
600 let mut empty = true;
601
602 self.object_handles.iter().for_each(|object_handle| {
603 if let Ok(object_bounds) = PdfPageObject::from_pdfium(
604 *object_handle,
605 self.ownership().clone(),
606 self.bindings(),
607 )
608 .bounds()
609 {
610 empty = false;
611
612 if object_bounds.bottom() < bottom {
613 bottom = object_bounds.bottom();
614 }
615
616 if object_bounds.left() < left {
617 left = object_bounds.left();
618 }
619
620 if object_bounds.top() > top {
621 top = object_bounds.top();
622 }
623
624 if object_bounds.right() > right {
625 right = object_bounds.right();
626 }
627 }
628 });
629
630 if empty {
631 Err(PdfiumError::EmptyPageObjectGroup)
632 } else {
633 Ok(PdfRect::new(bottom, left, top, right))
634 }
635 }
636
637 #[inline]
639 pub fn set_blend_mode(
640 &mut self,
641 blend_mode: PdfPageObjectBlendMode,
642 ) -> Result<(), PdfiumError> {
643 self.apply_to_each(|object| object.set_blend_mode(blend_mode))
644 }
645
646 #[inline]
648 pub fn set_fill_color(&mut self, fill_color: PdfColor) -> Result<(), PdfiumError> {
649 self.apply_to_each(|object| object.set_fill_color(fill_color))
650 }
651
652 #[inline]
657 pub fn set_stroke_color(&mut self, stroke_color: PdfColor) -> Result<(), PdfiumError> {
658 self.apply_to_each(|object| object.set_stroke_color(stroke_color))
659 }
660
661 #[inline]
671 pub fn set_stroke_width(&mut self, stroke_width: PdfPoints) -> Result<(), PdfiumError> {
672 self.apply_to_each(|object| object.set_stroke_width(stroke_width))
673 }
674
675 #[inline]
678 pub fn set_line_join(&mut self, line_join: PdfPageObjectLineJoin) -> Result<(), PdfiumError> {
679 self.apply_to_each(|object| object.set_line_join(line_join))
680 }
681
682 #[inline]
685 pub fn set_line_cap(&mut self, line_cap: PdfPageObjectLineCap) -> Result<(), PdfiumError> {
686 self.apply_to_each(|object| object.set_line_cap(line_cap))
687 }
688
689 #[inline]
696 pub fn set_fill_and_stroke_mode(
697 &mut self,
698 fill_mode: PdfPathFillMode,
699 do_stroke: bool,
700 ) -> Result<(), PdfiumError> {
701 self.apply_to_each(|object| {
702 if let Some(object) = object.as_path_object_mut() {
703 object.set_fill_and_stroke_mode(fill_mode, do_stroke)
704 } else {
705 Ok(())
706 }
707 })
708 }
709
710 #[inline]
712 pub(crate) fn apply_to_each<F, T>(&mut self, f: F) -> Result<(), PdfiumError>
713 where
714 F: Fn(&mut PdfPageObject<'a>) -> Result<T, PdfiumError>,
715 {
716 let mut error = None;
717
718 self.object_handles.iter().for_each(|handle| {
719 if let Err(err) = f(&mut self.get_object_from_handle(handle)) {
720 error = Some(err)
721 }
722 });
723
724 match error {
725 Some(err) => Err(err),
726 None => Ok(()),
727 }
728 }
729
730 #[inline]
732 pub(crate) fn for_each<F>(&self, mut f: F)
733 where
734 F: FnMut(&mut PdfPageObject<'a>),
735 {
736 self.object_handles.iter().for_each(|handle| {
737 f(&mut self.get_object_from_handle(handle));
738 });
739 }
740
741 #[inline]
743 pub(crate) fn get_object_from_handle(&self, handle: &FPDF_PAGEOBJECT) -> PdfPageObject<'a> {
744 PdfPageObject::from_pdfium(*handle, self.ownership().clone(), self.bindings())
745 }
746
747 create_transform_setters!(
748 &mut Self,
749 Result<(), PdfiumError>,
750 "every [PdfPageObject] in this group",
751 "every [PdfPageObject] in this group.",
752 "every [PdfPageObject] in this group,"
753 );
754
755 fn transform_impl(
757 &mut self,
758 a: PdfMatrixValue,
759 b: PdfMatrixValue,
760 c: PdfMatrixValue,
761 d: PdfMatrixValue,
762 e: PdfMatrixValue,
763 f: PdfMatrixValue,
764 ) -> Result<(), PdfiumError> {
765 self.apply_to_each(|object| object.transform(a, b, c, d, e, f))
766 }
767
768 fn reset_matrix_impl(&mut self, matrix: PdfMatrix) -> Result<(), PdfiumError> {
770 self.apply_to_each(|object| object.reset_matrix_impl(matrix))
771 }
772}
773
774pub struct PdfPageGroupObjectIterator<'a> {
776 group: &'a PdfPageGroupObject<'a>,
777 next_index: PdfPageObjectIndex,
778}
779
780impl<'a> PdfPageGroupObjectIterator<'a> {
781 #[inline]
782 pub(crate) fn new(group: &'a PdfPageGroupObject<'a>) -> Self {
783 PdfPageGroupObjectIterator {
784 group,
785 next_index: 0,
786 }
787 }
788}
789
790impl<'a> Iterator for PdfPageGroupObjectIterator<'a> {
791 type Item = PdfPageObject<'a>;
792
793 fn next(&mut self) -> Option<Self::Item> {
794 let next = self.group.get(self.next_index);
795
796 self.next_index += 1;
797
798 next.ok()
799 }
800}
801
802#[cfg(test)]
803mod test {
804 use crate::prelude::*;
805 use crate::utils::test::test_bind_to_pdfium;
806
807 #[test]
808 fn test_group_bounds() -> Result<(), PdfiumError> {
809 let pdfium = test_bind_to_pdfium();
810
811 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
812
813 let page = document.pages().get(2)?;
816
817 let mut group = page.objects().create_empty_group();
818
819 group.append(
820 page.objects()
821 .iter()
822 .filter(|object| {
823 object.object_type() == PdfPageObjectType::Text
824 && object.bounds().unwrap().bottom() > page.height() / 2.0
825 })
826 .collect::<Vec<_>>()
827 .as_mut_slice(),
828 )?;
829
830 let bounds = group.bounds()?;
833
834 assert_eq!(bounds.bottom().value, 428.31033);
835 assert_eq!(bounds.left().value, 62.60526);
836 assert_eq!(bounds.top().value, 807.8812);
837 assert_eq!(bounds.right().value, 544.48096);
838
839 Ok(())
840 }
841
842 #[test]
843 fn test_group_text() -> Result<(), PdfiumError> {
844 let pdfium = test_bind_to_pdfium();
845
846 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
847
848 let page = document.pages().get(5)?;
851
852 let mut group = page.objects().create_empty_group();
853
854 group.append(
855 page.objects()
856 .iter()
857 .filter(|object| {
858 object.object_type() == PdfPageObjectType::Text
859 && object.bounds().unwrap().bottom() < page.height() / 2.0
860 })
861 .collect::<Vec<_>>()
862 .as_mut_slice(),
863 )?;
864
865 assert_eq!(group.text_separated(" "), "Cento Concerti Ecclesiastici a Una, a Due, a Tre, e a Quattro voci Giacomo Vincenti, Venice, 1605 Edited by Alastair Carey Source is the 1605 reprint of the original 1602 publication. Item #2 in the source. Folio pages f5r (binding B1) in both Can to and Basso partbooks. The Basso partbook is barred; the Canto par tbook is not. The piece is marked ™Canto solo, Û Tenoreº in the Basso partbook, indicating it can be sung either by a Soprano or by a Tenor down an octave. V. Quem vidistis, pastores, dicite, annuntiate nobis: in terris quis apparuit? R. Natum vidimus, et choros angelorum collaudantes Dominum. Alleluia. What did you see, shepherds, speak, tell us: who has appeared on earth? We saw the new-born, and choirs of angels praising the Lord. Alleluia. Third responsory at Matins on Christmas Day 2 Basso, bar 47: one tone lower in source.");
868
869 Ok(())
870 }
871
872 #[test]
873 fn test_group_apply() -> Result<(), PdfiumError> {
874 let pdfium = test_bind_to_pdfium();
878
879 let mut document = pdfium.create_new_pdf()?;
880
881 let mut page = document
882 .pages_mut()
883 .create_page_at_start(PdfPagePaperSize::a4())?;
884
885 page.objects_mut().create_path_object_rect(
886 PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0),
887 None,
888 None,
889 Some(PdfColor::RED),
890 )?;
891
892 page.objects_mut().create_path_object_rect(
893 PdfRect::new_from_values(150.0, 150.0, 250.0, 250.0),
894 None,
895 None,
896 Some(PdfColor::GREEN),
897 )?;
898
899 page.objects_mut().create_path_object_rect(
900 PdfRect::new_from_values(200.0, 200.0, 300.0, 300.0),
901 None,
902 None,
903 Some(PdfColor::BLUE),
904 )?;
905
906 let mut group = PdfPageGroupObject::new(&page, |_| true)?;
907
908 let bounds = group.bounds()?;
909
910 assert_eq!(bounds.bottom().value, 100.0);
911 assert_eq!(bounds.left().value, 100.0);
912 assert_eq!(bounds.top().value, 300.0);
913 assert_eq!(bounds.right().value, 300.0);
914
915 group.translate(PdfPoints::new(150.0), PdfPoints::new(200.0))?;
916
917 let bounds = group.bounds()?;
918
919 assert_eq!(bounds.bottom().value, 300.0);
920 assert_eq!(bounds.left().value, 250.0);
921 assert_eq!(bounds.top().value, 500.0);
922 assert_eq!(bounds.right().value, 450.0);
923
924 Ok(())
925 }
926}