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::annotation::PdfPageAnnotation;
10use crate::pdf::document::page::index_cache::PdfPageIndexCache;
11use crate::pdf::document::page::object::path::PdfPathFillMode;
12use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
13use crate::pdf::document::page::object::{
14 PdfPageObject, PdfPageObjectBlendMode, PdfPageObjectCommon, PdfPageObjectLineCap,
15 PdfPageObjectLineJoin,
16};
17use crate::pdf::document::page::objects::common::{PdfPageObjectIndex, PdfPageObjectsCommon};
18use crate::pdf::document::page::{
19 PdfPage, PdfPageContentRegenerationStrategy, PdfPageObjectOwnership,
20};
21use crate::pdf::document::pages::{PdfPageIndex, PdfPages};
22use crate::pdf::document::PdfDocument;
23use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
24use crate::pdf::points::PdfPoints;
25use crate::pdf::quad_points::PdfQuadPoints;
26use crate::pdf::rect::PdfRect;
27use crate::pdfium::Pdfium;
28use crate::prelude::PdfPageXObjectFormObject;
29use std::collections::HashMap;
30use std::ffi::c_double;
31
32#[cfg(doc)]
33use crate::pdf::document::page::object::text::PdfPageTextObject;
34
35pub struct PdfPageGroupObject<'a> {
44 document_handle: FPDF_DOCUMENT,
45 page_handle: FPDF_PAGE,
46 ownership: PdfPageObjectOwnership,
47 object_handles: Vec<FPDF_PAGEOBJECT>,
48 bindings: &'a dyn PdfiumLibraryBindings,
49}
50
51impl<'a> PdfPageGroupObject<'a> {
52 #[inline]
53 pub(crate) fn from_pdfium(
54 document_handle: FPDF_DOCUMENT,
55 page_handle: FPDF_PAGE,
56 bindings: &'a dyn PdfiumLibraryBindings,
57 ) -> Self {
58 PdfPageGroupObject {
59 page_handle,
60 document_handle,
61 ownership: PdfPageObjectOwnership::owned_by_page(document_handle, page_handle),
62 object_handles: Vec::new(),
63 bindings,
64 }
65 }
66
67 pub fn empty(page: &'a PdfPage) -> Self {
70 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings())
71 }
72
73 pub fn new<F>(page: &'a PdfPage, predicate: F) -> Result<Self, PdfiumError>
76 where
77 F: FnMut(&PdfPageObject) -> bool,
78 {
79 let mut result =
80 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
81
82 for mut object in page.objects().iter().filter(predicate) {
83 result.push(&mut object)?;
84 }
85
86 Ok(result)
87 }
88
89 #[inline]
92 pub fn from_vec(
93 page: &PdfPage<'a>,
94 mut objects: Vec<PdfPageObject<'a>>,
95 ) -> Result<Self, PdfiumError> {
96 Self::from_slice(page, objects.as_mut_slice())
97 }
98
99 pub fn from_slice(
102 page: &PdfPage<'a>,
103 objects: &mut [PdfPageObject<'a>],
104 ) -> Result<Self, PdfiumError> {
105 let mut result =
106 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
107
108 for object in objects.iter_mut() {
109 result.push(object)?;
110 }
111
112 Ok(result)
113 }
114
115 #[inline]
117 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
118 self.document_handle
119 }
120
121 #[inline]
123 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
124 self.page_handle
125 }
126
127 #[inline]
129 pub(crate) fn ownership(&self) -> &PdfPageObjectOwnership {
130 &self.ownership
131 }
132
133 #[inline]
135 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
136 self.bindings
137 }
138
139 #[inline]
141 pub fn len(&self) -> usize {
142 self.object_handles.len()
143 }
144
145 #[inline]
147 pub fn is_empty(&self) -> bool {
148 self.len() == 0
149 }
150
151 #[inline]
153 pub fn contains(&self, object: &PdfPageObject) -> bool {
154 self.object_handles.contains(&object.object_handle())
155 }
156
157 pub fn push(&mut self, object: &mut PdfPageObject<'a>) -> Result<(), PdfiumError> {
159 let page_handle = match object.ownership() {
160 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
161 _ => None,
162 };
163
164 if let Some(page_handle) = page_handle {
165 if page_handle != self.page_handle() {
166 return Err(PdfiumError::OwnershipAlreadyAttachedToDifferentPage);
184 } else {
185 true
188 }
189 } else {
190 object.add_object_to_page_handle(self.document_handle(), self.page_handle())?;
193
194 false
195 };
196
197 self.object_handles.push(object.object_handle());
198
199 Ok(())
200 }
201
202 pub fn append(&mut self, objects: &mut [PdfPageObject<'a>]) -> Result<(), PdfiumError> {
204 let content_regeneration_strategy =
207 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
208 self.document_handle(),
209 self.page_handle(),
210 )
211 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
212
213 let page_index =
214 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
215
216 if let Some(page_index) = page_index {
217 PdfPageIndexCache::cache_props_for_page(
218 self.document_handle(),
219 self.page_handle(),
220 page_index,
221 PdfPageContentRegenerationStrategy::Manual,
222 );
223 }
224
225 for object in objects.iter_mut() {
226 self.push(object)?;
227 }
228
229 if let Some(page_index) = page_index {
232 PdfPageIndexCache::cache_props_for_page(
233 self.document_handle(),
234 self.page_handle(),
235 page_index,
236 content_regeneration_strategy,
237 );
238 }
239
240 if content_regeneration_strategy
241 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
242 {
243 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
244 }
245
246 Ok(())
247 }
248
249 pub fn remove_objects_from_page(mut self) -> Result<(), PdfiumError> {
260 let content_regeneration_strategy =
263 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
264 self.document_handle(),
265 self.page_handle(),
266 )
267 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
268
269 let page_index =
270 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
271
272 if let Some(page_index) = page_index {
273 PdfPageIndexCache::cache_props_for_page(
274 self.document_handle(),
275 self.page_handle(),
276 page_index,
277 PdfPageContentRegenerationStrategy::Manual,
278 );
279 }
280
281 self.apply_to_each(|object| object.remove_object_from_page())?;
284 self.object_handles.clear();
285
286 let page_height = PdfPoints::new(self.bindings().FPDF_GetPageHeightF(self.page_handle()));
291
292 for index in 0..self.bindings().FPDFPage_CountObjects(self.page_handle()) {
293 let mut object = PdfPageObject::from_pdfium(
294 self.bindings()
295 .FPDFPage_GetObject(self.page_handle(), index),
296 *self.ownership(),
297 self.bindings(),
298 );
299
300 object.flip_vertically()?;
308 object.translate(PdfPoints::ZERO, page_height)?;
309 }
310
311 if let Some(page_index) = page_index {
314 PdfPageIndexCache::cache_props_for_page(
315 self.document_handle,
316 self.page_handle,
317 page_index,
318 content_regeneration_strategy,
319 );
320 }
321
322 if content_regeneration_strategy
323 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
324 {
325 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
326 }
327
328 Ok(())
329 }
330
331 #[inline]
333 pub fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject, PdfiumError> {
334 if let Some(handle) = self.object_handles.get(index) {
335 Ok(self.get_object_from_handle(handle))
336 } else {
337 Err(PdfiumError::PageObjectIndexOutOfBounds)
338 }
339 }
340
341 pub fn retain<F>(&mut self, f: F)
346 where
347 F: Fn(&PdfPageObject) -> bool,
348 {
349 let mut do_retain = vec![false; self.object_handles.len()];
358
359 for (index, handle) in self.object_handles.iter().enumerate() {
360 do_retain[index] = f(&self.get_object_from_handle(handle));
361 }
362
363 let mut index = 0;
366
367 self.object_handles.retain(|_| {
368 let do_retain = do_retain[index];
371
372 index += 1;
373
374 do_retain
375 });
376 }
377
378 #[inline]
379 #[deprecated(
380 since = "0.8.32",
381 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
382 )]
383 pub fn retain_if_copyable(&mut self) {
388 #[allow(deprecated)]
389 self.retain(|object| object.is_copyable());
390 }
391
392 #[inline]
393 #[deprecated(
394 since = "0.8.32",
395 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
396 )]
397 pub fn is_copyable(&self) -> bool {
399 #[allow(deprecated)]
400 self.iter().all(|object| object.is_copyable())
401 }
402
403 #[deprecated(
404 since = "0.8.32",
405 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
406 )]
407 pub fn try_copy_onto_existing_page<'b>(
419 &self,
420 destination: &mut PdfPage<'b>,
421 ) -> Result<PdfPageGroupObject<'b>, PdfiumError> {
422 #[allow(deprecated)]
423 if !self.is_copyable() {
424 return Err(PdfiumError::GroupContainsNonCopyablePageObjects);
425 }
426
427 let mut group = destination.objects_mut().create_empty_group();
428
429 for handle in self.object_handles.iter() {
430 let source = self.get_object_from_handle(handle);
431
432 let clone =
433 source.try_copy_impl(destination.document_handle(), destination.bindings())?;
434
435 group.push(&mut destination.objects_mut().add_object(clone)?)?;
436 }
437
438 Ok(group)
439 }
440
441 pub fn move_to_page(mut self, page: &mut PdfPage) -> Result<(), PdfiumError> {
448 self.apply_to_each(|object| object.move_to_page(page))?;
449 self.object_handles.clear();
450 Ok(())
451 }
452
453 pub fn move_to_annotation(
460 mut self,
461 annotation: &mut PdfPageAnnotation,
462 ) -> Result<(), PdfiumError> {
463 self.apply_to_each(|object| object.move_to_annotation(annotation))?;
464 self.object_handles.clear();
465 Ok(())
466 }
467
468 pub fn copy_to_page(
472 &mut self,
473 page: &mut PdfPage<'a>,
474 ) -> Result<PdfPageObject<'a>, PdfiumError> {
475 let mut object = self.copy_into_x_object_form_object_from_handles(
476 page.document_handle(),
477 page.width(),
478 page.height(),
479 )?;
480
481 object.move_to_page(page)?;
482
483 Ok(object)
484 }
485
486 pub fn copy_into_x_object_form_object(
489 &mut self,
490 destination: &mut PdfDocument<'a>,
491 ) -> Result<PdfPageObject<'a>, PdfiumError> {
492 self.copy_into_x_object_form_object_from_handles(
493 destination.handle(),
494 PdfPoints::new(self.bindings().FPDF_GetPageWidthF(self.page_handle())),
495 PdfPoints::new(self.bindings().FPDF_GetPageHeightF(self.page_handle())),
496 )
497 }
498
499 pub(crate) fn copy_into_x_object_form_object_from_handles(
500 &mut self,
501 destination_document_handle: FPDF_DOCUMENT,
502 destination_page_width: PdfPoints,
503 destination_page_height: PdfPoints,
504 ) -> Result<PdfPageObject<'a>, PdfiumError> {
505 let src_doc_handle = self.document_handle();
510 let src_page_handle = self.page_handle();
511
512 let tmp_page_index = self.bindings().FPDF_GetPageCount(src_doc_handle);
515
516 let tmp_page = self.bindings().FPDFPage_New(
517 src_doc_handle,
518 tmp_page_index,
519 destination_page_width.value as c_double,
520 destination_page_height.value as c_double,
521 );
522
523 PdfPageIndexCache::cache_props_for_page(
524 src_doc_handle,
525 tmp_page,
526 tmp_page_index as u16,
527 PdfPageContentRegenerationStrategy::AutomaticOnEveryChange,
528 );
529
530 self.apply_to_each(|object| {
533 match object.ownership() {
534 PdfPageObjectOwnership::Page(_) => object.remove_object_from_page()?,
535 PdfPageObjectOwnership::AttachedAnnotation(_)
536 | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
537 object.remove_object_from_annotation()?
538 }
539 _ => {}
540 }
541
542 object.add_object_to_page_handle(src_doc_handle, tmp_page)?;
543
544 Ok(())
545 })?;
546 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
547 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
548
549 let x_object = self.bindings().FPDF_NewXObjectFromPage(
552 destination_document_handle,
553 src_doc_handle,
554 tmp_page_index,
555 );
556
557 let object_handle = self.bindings().FPDF_NewFormObjectFromXObject(x_object);
558 if object_handle.is_null() {
559 return Err(PdfiumError::PdfiumLibraryInternalError(
560 crate::error::PdfiumInternalError::Unknown,
561 ));
562 }
563
564 let object = PdfPageXObjectFormObject::from_pdfium(
565 object_handle,
566 PdfPageObjectOwnership::owned_by_document(destination_document_handle),
567 self.bindings(),
568 );
569
570 self.bindings().FPDF_CloseXObject(x_object);
571
572 self.apply_to_each(|object| {
575 match object.ownership() {
576 PdfPageObjectOwnership::Page(ownership) => {
577 if ownership.page_handle() != src_page_handle {
578 object.remove_object_from_page()?
579 }
580 }
581 PdfPageObjectOwnership::AttachedAnnotation(_)
582 | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
583 object.remove_object_from_annotation()?
584 }
585 _ => {}
586 }
587 object.add_object_to_page_handle(src_doc_handle, src_page_handle)?;
588
589 Ok(())
590 })?;
591 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
592 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
593
594 PdfPageIndexCache::remove_index_for_page(src_doc_handle, tmp_page);
595 self.bindings()
596 .FPDFPage_Delete(src_doc_handle, tmp_page_index);
597
598 Ok(PdfPageObject::XObjectForm(object))
599 }
600
601 #[deprecated(
602 since = "0.8.32",
603 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
604 )]
605 #[inline]
606 pub fn copy_onto_new_page_at_start(
621 &self,
622 destination: &PdfDocument,
623 ) -> Result<(), PdfiumError> {
624 #[allow(deprecated)]
625 self.copy_onto_new_page_at_index(0, destination)
626 }
627
628 #[deprecated(
629 since = "0.8.32",
630 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
631 )]
632 #[inline]
633 pub fn copy_onto_new_page_at_end(&self, destination: &PdfDocument) -> Result<(), PdfiumError> {
648 #[allow(deprecated)]
649 self.copy_onto_new_page_at_index(destination.pages().len(), destination)
650 }
651
652 #[deprecated(
653 since = "0.8.32",
654 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
655 )]
656 pub fn copy_onto_new_page_at_index(
671 &self,
672 index: PdfPageIndex,
673 destination: &PdfDocument,
674 ) -> Result<(), PdfiumError> {
675 let temp = Pdfium::pdfium_document_handle_to_result(
686 self.bindings.FPDF_CreateNewDocument(),
687 self.bindings,
688 )?;
689
690 if let Some(source_page_index) =
691 PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
692 {
693 PdfPages::copy_page_range_between_documents(
694 self.document_handle,
695 source_page_index..=source_page_index,
696 temp.handle(),
697 0,
698 self.bindings,
699 )?;
700 } else {
701 return Err(PdfiumError::SourcePageIndexNotInCache);
702 }
703
704 let mut objects_to_discard = HashMap::new();
711
712 for index in 0..self.bindings.FPDFPage_CountObjects(self.page_handle) {
713 let object = PdfPageObject::from_pdfium(
714 self.bindings().FPDFPage_GetObject(self.page_handle, index),
715 *self.ownership(),
716 self.bindings(),
717 );
718
719 if !self.contains(&object) {
720 objects_to_discard.insert(
721 (object.bounds()?, object.matrix()?, object.object_type()),
722 true,
723 );
724 }
725 }
726
727 temp.pages()
731 .get(0)?
732 .objects()
733 .create_group(|object| {
734 objects_to_discard.contains_key(&(
735 object.bounds().unwrap_or(PdfQuadPoints::ZERO),
736 object.matrix().unwrap_or(PdfMatrix::IDENTITY),
737 object.object_type(),
738 ))
739 })?
740 .remove_objects_from_page()?;
741
742 PdfPages::copy_page_range_between_documents(
746 temp.handle(),
747 0..=0,
748 destination.handle(),
749 index,
750 self.bindings,
751 )?;
752
753 Ok(())
754 }
755
756 #[inline]
758 pub fn iter(&'a self) -> PdfPageGroupObjectIterator<'a> {
759 PdfPageGroupObjectIterator::new(self)
760 }
761
762 #[inline]
764 pub fn text(&self) -> String {
765 self.text_separated("")
766 }
767
768 pub fn text_separated(&self, separator: &str) -> String {
771 let mut strings = Vec::with_capacity(self.len());
772
773 self.for_each(|object| {
774 if let Some(object) = object.as_text_object() {
775 strings.push(object.text());
776 }
777 });
778
779 strings.join(separator)
780 }
781
782 #[inline]
784 pub fn has_transparency(&self) -> bool {
785 self.object_handles.iter().any(|object_handle| {
786 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings())
787 .has_transparency()
788 })
789 }
790
791 pub fn bounds(&self) -> Result<PdfRect, PdfiumError> {
794 let mut bottom = PdfPoints::MAX;
795 let mut top = PdfPoints::MIN;
796 let mut left = PdfPoints::MAX;
797 let mut right = PdfPoints::MIN;
798 let mut empty = true;
799
800 self.object_handles.iter().for_each(|object_handle| {
801 if let Ok(object_bounds) =
802 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings())
803 .bounds()
804 {
805 empty = false;
806
807 if object_bounds.bottom() < bottom {
808 bottom = object_bounds.bottom();
809 }
810
811 if object_bounds.left() < left {
812 left = object_bounds.left();
813 }
814
815 if object_bounds.top() > top {
816 top = object_bounds.top();
817 }
818
819 if object_bounds.right() > right {
820 right = object_bounds.right();
821 }
822 }
823 });
824
825 if empty {
826 Err(PdfiumError::EmptyPageObjectGroup)
827 } else {
828 Ok(PdfRect::new(bottom, left, top, right))
829 }
830 }
831
832 #[inline]
834 pub fn set_blend_mode(
835 &mut self,
836 blend_mode: PdfPageObjectBlendMode,
837 ) -> Result<(), PdfiumError> {
838 self.apply_to_each(|object| object.set_blend_mode(blend_mode))
839 }
840
841 #[inline]
843 pub fn set_fill_color(&mut self, fill_color: PdfColor) -> Result<(), PdfiumError> {
844 self.apply_to_each(|object| object.set_fill_color(fill_color))
845 }
846
847 #[inline]
852 pub fn set_stroke_color(&mut self, stroke_color: PdfColor) -> Result<(), PdfiumError> {
853 self.apply_to_each(|object| object.set_stroke_color(stroke_color))
854 }
855
856 #[inline]
866 pub fn set_stroke_width(&mut self, stroke_width: PdfPoints) -> Result<(), PdfiumError> {
867 self.apply_to_each(|object| object.set_stroke_width(stroke_width))
868 }
869
870 #[inline]
873 pub fn set_line_join(&mut self, line_join: PdfPageObjectLineJoin) -> Result<(), PdfiumError> {
874 self.apply_to_each(|object| object.set_line_join(line_join))
875 }
876
877 #[inline]
880 pub fn set_line_cap(&mut self, line_cap: PdfPageObjectLineCap) -> Result<(), PdfiumError> {
881 self.apply_to_each(|object| object.set_line_cap(line_cap))
882 }
883
884 #[inline]
891 pub fn set_fill_and_stroke_mode(
892 &mut self,
893 fill_mode: PdfPathFillMode,
894 do_stroke: bool,
895 ) -> Result<(), PdfiumError> {
896 self.apply_to_each(|object| {
897 if let Some(object) = object.as_path_object_mut() {
898 object.set_fill_and_stroke_mode(fill_mode, do_stroke)
899 } else {
900 Ok(())
901 }
902 })
903 }
904
905 #[inline]
907 pub(crate) fn apply_to_each<F, T>(&mut self, mut f: F) -> Result<(), PdfiumError>
908 where
909 F: FnMut(&mut PdfPageObject<'a>) -> Result<T, PdfiumError>,
910 {
911 let mut error = None;
912
913 self.object_handles.iter().for_each(|handle| {
914 if let Err(err) = f(&mut self.get_object_from_handle(handle)) {
915 error = Some(err)
916 }
917 });
918
919 match error {
920 Some(err) => Err(err),
921 None => Ok(()),
922 }
923 }
924
925 #[inline]
927 pub(crate) fn for_each<F>(&self, mut f: F)
928 where
929 F: FnMut(&mut PdfPageObject<'a>),
930 {
931 self.object_handles.iter().for_each(|handle| {
932 f(&mut self.get_object_from_handle(handle));
933 });
934 }
935
936 #[inline]
938 pub(crate) fn get_object_from_handle(&self, handle: &FPDF_PAGEOBJECT) -> PdfPageObject<'a> {
939 PdfPageObject::from_pdfium(*handle, *self.ownership(), self.bindings())
940 }
941
942 create_transform_setters!(
943 &mut Self,
944 Result<(), PdfiumError>,
945 "every [PdfPageObject] in this group",
946 "every [PdfPageObject] in this group.",
947 "every [PdfPageObject] in this group,"
948 );
949
950 fn transform_impl(
952 &mut self,
953 a: PdfMatrixValue,
954 b: PdfMatrixValue,
955 c: PdfMatrixValue,
956 d: PdfMatrixValue,
957 e: PdfMatrixValue,
958 f: PdfMatrixValue,
959 ) -> Result<(), PdfiumError> {
960 self.apply_to_each(|object| object.transform(a, b, c, d, e, f))
961 }
962
963 fn reset_matrix_impl(&mut self, matrix: PdfMatrix) -> Result<(), PdfiumError> {
965 self.apply_to_each(|object| object.reset_matrix_impl(matrix))
966 }
967}
968
969pub struct PdfPageGroupObjectIterator<'a> {
971 group: &'a PdfPageGroupObject<'a>,
972 next_index: PdfPageObjectIndex,
973}
974
975impl<'a> PdfPageGroupObjectIterator<'a> {
976 #[inline]
977 pub(crate) fn new(group: &'a PdfPageGroupObject<'a>) -> Self {
978 PdfPageGroupObjectIterator {
979 group,
980 next_index: 0,
981 }
982 }
983}
984
985impl<'a> Iterator for PdfPageGroupObjectIterator<'a> {
986 type Item = PdfPageObject<'a>;
987
988 fn next(&mut self) -> Option<Self::Item> {
989 let next = self.group.get(self.next_index);
990
991 self.next_index += 1;
992
993 next.ok()
994 }
995}
996
997#[cfg(test)]
998mod test {
999 use crate::prelude::*;
1000 use crate::utils::test::test_bind_to_pdfium;
1001
1002 #[test]
1003 fn test_group_bounds() -> Result<(), PdfiumError> {
1004 let pdfium = test_bind_to_pdfium();
1005
1006 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
1007
1008 let page = document.pages().get(2)?;
1011
1012 let mut group = page.objects().create_empty_group();
1013
1014 group.append(
1015 page.objects()
1016 .iter()
1017 .filter(|object| {
1018 object.object_type() == PdfPageObjectType::Text
1019 && object.bounds().unwrap().bottom() > page.height() / 2.0
1020 })
1021 .collect::<Vec<_>>()
1022 .as_mut_slice(),
1023 )?;
1024
1025 let bounds = group.bounds()?;
1028
1029 assert_eq!(bounds.bottom().value, 428.31033);
1030 assert_eq!(bounds.left().value, 62.60526);
1031 assert_eq!(bounds.top().value, 807.8812);
1032 assert_eq!(bounds.right().value, 544.48096);
1033
1034 Ok(())
1035 }
1036
1037 #[test]
1038 fn test_group_text() -> Result<(), PdfiumError> {
1039 let pdfium = test_bind_to_pdfium();
1040
1041 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
1042
1043 let page = document.pages().get(5)?;
1046
1047 let mut group = page.objects().create_empty_group();
1048
1049 group.append(
1050 page.objects()
1051 .iter()
1052 .filter(|object| {
1053 object.object_type() == PdfPageObjectType::Text
1054 && object.bounds().unwrap().bottom() < page.height() / 2.0
1055 })
1056 .collect::<Vec<_>>()
1057 .as_mut_slice(),
1058 )?;
1059
1060 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.");
1063
1064 Ok(())
1065 }
1066
1067 #[test]
1068 fn test_group_apply() -> Result<(), PdfiumError> {
1069 let pdfium = test_bind_to_pdfium();
1073
1074 let mut document = pdfium.create_new_pdf()?;
1075
1076 let mut page = document
1077 .pages_mut()
1078 .create_page_at_start(PdfPagePaperSize::a4())?;
1079
1080 page.objects_mut().create_path_object_rect(
1081 PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0),
1082 None,
1083 None,
1084 Some(PdfColor::RED),
1085 )?;
1086
1087 page.objects_mut().create_path_object_rect(
1088 PdfRect::new_from_values(150.0, 150.0, 250.0, 250.0),
1089 None,
1090 None,
1091 Some(PdfColor::GREEN),
1092 )?;
1093
1094 page.objects_mut().create_path_object_rect(
1095 PdfRect::new_from_values(200.0, 200.0, 300.0, 300.0),
1096 None,
1097 None,
1098 Some(PdfColor::BLUE),
1099 )?;
1100
1101 let mut group = PdfPageGroupObject::new(&page, |_| true)?;
1102
1103 let bounds = group.bounds()?;
1104
1105 assert_eq!(bounds.bottom().value, 100.0);
1106 assert_eq!(bounds.left().value, 100.0);
1107 assert_eq!(bounds.top().value, 300.0);
1108 assert_eq!(bounds.right().value, 300.0);
1109
1110 group.translate(PdfPoints::new(150.0), PdfPoints::new(200.0))?;
1111
1112 let bounds = group.bounds()?;
1113
1114 assert_eq!(bounds.bottom().value, 300.0);
1115 assert_eq!(bounds.left().value, 250.0);
1116 assert_eq!(bounds.top().value, 500.0);
1117 assert_eq!(bounds.right().value, 450.0);
1118
1119 Ok(())
1120 }
1121}