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::PdfDocument;
10use crate::pdf::document::page::annotation::PdfPageAnnotation;
11use crate::pdf::document::page::index_cache::PdfPageIndexCache;
12use crate::pdf::document::page::object::path::PdfPathFillMode;
13use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
14use crate::pdf::document::page::object::{
15 PdfPageObject, PdfPageObjectBlendMode, PdfPageObjectCommon, PdfPageObjectLineCap, PdfPageObjectLineJoin,
16};
17use crate::pdf::document::page::objects::common::{PdfPageObjectIndex, PdfPageObjectsCommon};
18use crate::pdf::document::page::{PdfPage, PdfPageContentRegenerationStrategy, PdfPageObjectOwnership};
19use crate::pdf::document::pages::{PdfPageIndex, PdfPages};
20use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
21use crate::pdf::points::PdfPoints;
22use crate::pdf::quad_points::PdfQuadPoints;
23use crate::pdf::rect::PdfRect;
24use crate::pdfium::Pdfium;
25use crate::prelude::PdfPageXObjectFormObject;
26use std::collections::HashMap;
27use std::ffi::c_double;
28
29#[cfg(doc)]
30use crate::pdf::document::page::object::text::PdfPageTextObject;
31
32pub struct PdfPageGroupObject<'a> {
41 document_handle: FPDF_DOCUMENT,
42 page_handle: FPDF_PAGE,
43 ownership: PdfPageObjectOwnership,
44 object_handles: Vec<FPDF_PAGEOBJECT>,
45 bindings: &'a dyn PdfiumLibraryBindings,
46}
47
48impl<'a> PdfPageGroupObject<'a> {
49 #[inline]
50 pub(crate) fn from_pdfium(
51 document_handle: FPDF_DOCUMENT,
52 page_handle: FPDF_PAGE,
53 bindings: &'a dyn PdfiumLibraryBindings,
54 ) -> Self {
55 PdfPageGroupObject {
56 page_handle,
57 document_handle,
58 ownership: PdfPageObjectOwnership::owned_by_page(document_handle, page_handle),
59 object_handles: Vec::new(),
60 bindings,
61 }
62 }
63
64 pub fn empty(page: &'a PdfPage) -> Self {
67 Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings())
68 }
69
70 pub fn new<F>(page: &'a PdfPage, predicate: F) -> Result<Self, PdfiumError>
73 where
74 F: FnMut(&PdfPageObject) -> bool,
75 {
76 let mut result = Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
77
78 for mut object in page.objects().iter().filter(predicate) {
79 result.push(&mut object)?;
80 }
81
82 Ok(result)
83 }
84
85 #[inline]
88 pub fn from_vec(page: &PdfPage<'a>, mut objects: Vec<PdfPageObject<'a>>) -> Result<Self, PdfiumError> {
89 Self::from_slice(page, objects.as_mut_slice())
90 }
91
92 pub fn from_slice(page: &PdfPage<'a>, objects: &mut [PdfPageObject<'a>]) -> Result<Self, PdfiumError> {
95 let mut result = Self::from_pdfium(page.document_handle(), page.page_handle(), page.bindings());
96
97 for object in objects.iter_mut() {
98 result.push(object)?;
99 }
100
101 Ok(result)
102 }
103
104 #[inline]
106 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
107 self.document_handle
108 }
109
110 #[inline]
112 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
113 self.page_handle
114 }
115
116 #[inline]
118 pub(crate) fn ownership(&self) -> &PdfPageObjectOwnership {
119 &self.ownership
120 }
121
122 #[inline]
124 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
125 self.bindings
126 }
127
128 #[inline]
130 pub fn len(&self) -> usize {
131 self.object_handles.len()
132 }
133
134 #[inline]
136 pub fn is_empty(&self) -> bool {
137 self.len() == 0
138 }
139
140 #[inline]
142 pub fn contains(&self, object: &PdfPageObject) -> bool {
143 self.object_handles.contains(&object.object_handle())
144 }
145
146 pub fn push(&mut self, object: &mut PdfPageObject<'a>) -> Result<(), PdfiumError> {
148 let page_handle = match object.ownership() {
149 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
150 _ => None,
151 };
152
153 if let Some(page_handle) = page_handle {
154 if page_handle != self.page_handle() {
155 return Err(PdfiumError::OwnershipAlreadyAttachedToDifferentPage);
165 } else {
166 true
167 }
168 } else {
169 object.add_object_to_page_handle(self.document_handle(), self.page_handle())?;
170
171 false
172 };
173
174 self.object_handles.push(object.object_handle());
175
176 Ok(())
177 }
178
179 pub fn append(&mut self, objects: &mut [PdfPageObject<'a>]) -> Result<(), PdfiumError> {
181 let content_regeneration_strategy =
182 PdfPageIndexCache::get_content_regeneration_strategy_for_page(self.document_handle(), self.page_handle())
183 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
184
185 let page_index = PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
186
187 if let Some(page_index) = page_index {
188 PdfPageIndexCache::cache_props_for_page(
189 self.document_handle(),
190 self.page_handle(),
191 page_index,
192 PdfPageContentRegenerationStrategy::Manual,
193 );
194 }
195
196 for object in objects.iter_mut() {
197 self.push(object)?;
198 }
199
200 if let Some(page_index) = page_index {
201 PdfPageIndexCache::cache_props_for_page(
202 self.document_handle(),
203 self.page_handle(),
204 page_index,
205 content_regeneration_strategy,
206 );
207 }
208
209 if content_regeneration_strategy == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange {
210 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
211 }
212
213 Ok(())
214 }
215
216 pub fn remove_objects_from_page(mut self) -> Result<(), PdfiumError> {
227 let content_regeneration_strategy =
228 PdfPageIndexCache::get_content_regeneration_strategy_for_page(self.document_handle(), self.page_handle())
229 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
230
231 let page_index = PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
232
233 if let Some(page_index) = page_index {
234 PdfPageIndexCache::cache_props_for_page(
235 self.document_handle(),
236 self.page_handle(),
237 page_index,
238 PdfPageContentRegenerationStrategy::Manual,
239 );
240 }
241
242 self.apply_to_each(|object| object.remove_object_from_page())?;
243 self.object_handles.clear();
244
245 let page_height = PdfPoints::new(self.bindings().FPDF_GetPageHeightF(self.page_handle()));
246
247 for index in 0..self.bindings().FPDFPage_CountObjects(self.page_handle()) {
248 let mut object = PdfPageObject::from_pdfium(
249 self.bindings().FPDFPage_GetObject(self.page_handle(), index),
250 *self.ownership(),
251 self.bindings(),
252 );
253
254 object.flip_vertically()?;
262 object.translate(PdfPoints::ZERO, page_height)?;
263 }
264
265 if let Some(page_index) = page_index {
266 PdfPageIndexCache::cache_props_for_page(
267 self.document_handle,
268 self.page_handle,
269 page_index,
270 content_regeneration_strategy,
271 );
272 }
273
274 if content_regeneration_strategy == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange {
275 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
276 }
277
278 Ok(())
279 }
280
281 #[inline]
283 pub fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'_>, PdfiumError> {
284 if let Some(handle) = self.object_handles.get(index) {
285 Ok(self.get_object_from_handle(handle))
286 } else {
287 Err(PdfiumError::PageObjectIndexOutOfBounds)
288 }
289 }
290
291 pub fn retain<F>(&mut self, f: F)
296 where
297 F: Fn(&PdfPageObject) -> bool,
298 {
299 let mut do_retain = vec![false; self.object_handles.len()];
300
301 for (index, handle) in self.object_handles.iter().enumerate() {
302 do_retain[index] = f(&self.get_object_from_handle(handle));
303 }
304
305 let mut index = 0;
306
307 self.object_handles.retain(|_| {
308 let do_retain = do_retain[index];
309
310 index += 1;
311
312 do_retain
313 });
314 }
315
316 #[inline]
317 #[deprecated(
318 since = "0.8.32",
319 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
320 )]
321 pub fn retain_if_copyable(&mut self) {
326 #[allow(deprecated)]
327 self.retain(|object| object.is_copyable());
328 }
329
330 #[inline]
331 #[deprecated(
332 since = "0.8.32",
333 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
334 )]
335 pub fn is_copyable(&self) -> bool {
337 #[allow(deprecated)]
338 self.iter().all(|object| object.is_copyable())
339 }
340
341 #[deprecated(
342 since = "0.8.32",
343 note = "This function is no longer relevant, as the PdfPageGroupObject::copy_to_page() function can copy all object types."
344 )]
345 pub fn try_copy_onto_existing_page<'b>(
357 &self,
358 destination: &mut PdfPage<'b>,
359 ) -> Result<PdfPageGroupObject<'b>, PdfiumError> {
360 #[allow(deprecated)]
361 if !self.is_copyable() {
362 return Err(PdfiumError::GroupContainsNonCopyablePageObjects);
363 }
364
365 let mut group = destination.objects_mut().create_empty_group();
366
367 for handle in self.object_handles.iter() {
368 let source = self.get_object_from_handle(handle);
369
370 let clone = source.try_copy_impl(destination.document_handle(), destination.bindings())?;
371
372 group.push(&mut destination.objects_mut().add_object(clone)?)?;
373 }
374
375 Ok(group)
376 }
377
378 pub fn move_to_page(mut self, page: &mut PdfPage) -> Result<(), PdfiumError> {
385 self.apply_to_each(|object| object.move_to_page(page))?;
386 self.object_handles.clear();
387 Ok(())
388 }
389
390 pub fn move_to_annotation(mut self, annotation: &mut PdfPageAnnotation) -> Result<(), PdfiumError> {
397 self.apply_to_each(|object| object.move_to_annotation(annotation))?;
398 self.object_handles.clear();
399 Ok(())
400 }
401
402 pub fn copy_to_page(&mut self, page: &mut PdfPage<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
406 let mut object =
407 self.copy_into_x_object_form_object_from_handles(page.document_handle(), page.width(), page.height())?;
408
409 object.move_to_page(page)?;
410
411 Ok(object)
412 }
413
414 pub fn copy_into_x_object_form_object(
417 &mut self,
418 destination: &mut PdfDocument<'a>,
419 ) -> Result<PdfPageObject<'a>, PdfiumError> {
420 self.copy_into_x_object_form_object_from_handles(
421 destination.handle(),
422 PdfPoints::new(self.bindings().FPDF_GetPageWidthF(self.page_handle())),
423 PdfPoints::new(self.bindings().FPDF_GetPageHeightF(self.page_handle())),
424 )
425 }
426
427 pub(crate) fn copy_into_x_object_form_object_from_handles(
428 &mut self,
429 destination_document_handle: FPDF_DOCUMENT,
430 destination_page_width: PdfPoints,
431 destination_page_height: PdfPoints,
432 ) -> Result<PdfPageObject<'a>, PdfiumError> {
433 let src_doc_handle = self.document_handle();
434 let src_page_handle = self.page_handle();
435
436 let tmp_page_index = self.bindings().FPDF_GetPageCount(src_doc_handle);
437
438 let tmp_page = self.bindings().FPDFPage_New(
439 src_doc_handle,
440 tmp_page_index,
441 destination_page_width.value as c_double,
442 destination_page_height.value as c_double,
443 );
444
445 PdfPageIndexCache::cache_props_for_page(
446 src_doc_handle,
447 tmp_page,
448 tmp_page_index as PdfPageIndex,
449 PdfPageContentRegenerationStrategy::AutomaticOnEveryChange,
450 );
451
452 self.apply_to_each(|object| {
453 match object.ownership() {
454 PdfPageObjectOwnership::Page(_) => object.remove_object_from_page()?,
455 PdfPageObjectOwnership::AttachedAnnotation(_) | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
456 object.remove_object_from_annotation()?
457 }
458 _ => {}
459 }
460
461 object.add_object_to_page_handle(src_doc_handle, tmp_page)?;
462
463 Ok(())
464 })?;
465 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
466 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
467
468 let x_object =
469 self.bindings()
470 .FPDF_NewXObjectFromPage(destination_document_handle, src_doc_handle, tmp_page_index);
471
472 let object_handle = self.bindings().FPDF_NewFormObjectFromXObject(x_object);
473 if object_handle.is_null() {
474 return Err(PdfiumError::PdfiumLibraryInternalError(
475 crate::error::PdfiumInternalError::Unknown,
476 ));
477 }
478
479 let object = PdfPageXObjectFormObject::from_pdfium(
480 object_handle,
481 PdfPageObjectOwnership::owned_by_document(destination_document_handle),
482 self.bindings(),
483 );
484
485 self.bindings().FPDF_CloseXObject(x_object);
486
487 self.apply_to_each(|object| {
488 match object.ownership() {
489 PdfPageObjectOwnership::Page(ownership) if ownership.page_handle() != src_page_handle => {
490 object.remove_object_from_page()?
491 }
492 PdfPageObjectOwnership::AttachedAnnotation(_) | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
493 object.remove_object_from_annotation()?
494 }
495 _ => {}
496 }
497 object.add_object_to_page_handle(src_doc_handle, src_page_handle)?;
498
499 Ok(())
500 })?;
501 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
502 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
503
504 PdfPageIndexCache::remove_index_for_page(src_doc_handle, tmp_page);
505 self.bindings().FPDFPage_Delete(src_doc_handle, tmp_page_index);
506
507 Ok(PdfPageObject::XObjectForm(object))
508 }
509
510 #[deprecated(
511 since = "0.8.32",
512 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
513 )]
514 #[inline]
515 pub fn copy_onto_new_page_at_start(&self, destination: &PdfDocument) -> Result<(), PdfiumError> {
530 #[allow(deprecated)]
531 self.copy_onto_new_page_at_index(0, destination)
532 }
533
534 #[deprecated(
535 since = "0.8.32",
536 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
537 )]
538 #[inline]
539 pub fn copy_onto_new_page_at_end(&self, destination: &PdfDocument) -> Result<(), PdfiumError> {
554 #[allow(deprecated)]
555 self.copy_onto_new_page_at_index(destination.pages().len(), destination)
556 }
557
558 #[deprecated(
559 since = "0.8.32",
560 note = "This function has been retired in favour of the PdfPageGroupObject::copy_to_page() function."
561 )]
562 pub fn copy_onto_new_page_at_index(
577 &self,
578 index: PdfPageIndex,
579 destination: &PdfDocument,
580 ) -> Result<(), PdfiumError> {
581 let temp = Pdfium::pdfium_document_handle_to_result(self.bindings.FPDF_CreateNewDocument(), self.bindings)?;
582
583 if let Some(source_page_index) = PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle) {
584 PdfPages::copy_page_range_between_documents(
585 self.document_handle,
586 source_page_index..=source_page_index,
587 temp.handle(),
588 0,
589 self.bindings,
590 )?;
591 } else {
592 return Err(PdfiumError::SourcePageIndexNotInCache);
593 }
594
595 let mut objects_to_discard = HashMap::new();
596
597 for index in 0..self.bindings.FPDFPage_CountObjects(self.page_handle) {
598 let object = PdfPageObject::from_pdfium(
599 self.bindings().FPDFPage_GetObject(self.page_handle, index),
600 *self.ownership(),
601 self.bindings(),
602 );
603
604 if !self.contains(&object) {
605 objects_to_discard.insert((object.bounds()?, object.matrix()?, object.object_type()), true);
606 }
607 }
608
609 temp.pages()
610 .get(0)?
611 .objects()
612 .create_group(|object| {
613 objects_to_discard.contains_key(&(
614 object.bounds().unwrap_or(PdfQuadPoints::ZERO),
615 object.matrix().unwrap_or(PdfMatrix::IDENTITY),
616 object.object_type(),
617 ))
618 })?
619 .remove_objects_from_page()?;
620
621 PdfPages::copy_page_range_between_documents(temp.handle(), 0..=0, destination.handle(), index, self.bindings)?;
622
623 Ok(())
624 }
625
626 #[inline]
628 pub fn iter(&'a self) -> PdfPageGroupObjectIterator<'a> {
629 PdfPageGroupObjectIterator::new(self)
630 }
631
632 #[inline]
634 pub fn text(&self) -> String {
635 self.text_separated("")
636 }
637
638 pub fn text_separated(&self, separator: &str) -> String {
641 let mut strings = Vec::with_capacity(self.len());
642
643 self.for_each(|object| {
644 if let Some(object) = object.as_text_object() {
645 strings.push(object.text());
646 }
647 });
648
649 strings.join(separator)
650 }
651
652 #[inline]
654 pub fn has_transparency(&self) -> bool {
655 self.object_handles.iter().any(|object_handle| {
656 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings()).has_transparency()
657 })
658 }
659
660 pub fn bounds(&self) -> Result<PdfRect, PdfiumError> {
663 let mut bottom = PdfPoints::MAX;
664 let mut top = PdfPoints::MIN;
665 let mut left = PdfPoints::MAX;
666 let mut right = PdfPoints::MIN;
667 let mut empty = true;
668
669 self.object_handles.iter().for_each(|object_handle| {
670 if let Ok(object_bounds) =
671 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings()).bounds()
672 {
673 empty = false;
674
675 if object_bounds.bottom() < bottom {
676 bottom = object_bounds.bottom();
677 }
678
679 if object_bounds.left() < left {
680 left = object_bounds.left();
681 }
682
683 if object_bounds.top() > top {
684 top = object_bounds.top();
685 }
686
687 if object_bounds.right() > right {
688 right = object_bounds.right();
689 }
690 }
691 });
692
693 if empty {
694 Err(PdfiumError::EmptyPageObjectGroup)
695 } else {
696 Ok(PdfRect::new(bottom, left, top, right))
697 }
698 }
699
700 #[inline]
702 pub fn set_blend_mode(&mut self, blend_mode: PdfPageObjectBlendMode) -> Result<(), PdfiumError> {
703 self.apply_to_each(|object| object.set_blend_mode(blend_mode))
704 }
705
706 #[inline]
708 pub fn set_fill_color(&mut self, fill_color: PdfColor) -> Result<(), PdfiumError> {
709 self.apply_to_each(|object| object.set_fill_color(fill_color))
710 }
711
712 #[inline]
717 pub fn set_stroke_color(&mut self, stroke_color: PdfColor) -> Result<(), PdfiumError> {
718 self.apply_to_each(|object| object.set_stroke_color(stroke_color))
719 }
720
721 #[inline]
731 pub fn set_stroke_width(&mut self, stroke_width: PdfPoints) -> Result<(), PdfiumError> {
732 self.apply_to_each(|object| object.set_stroke_width(stroke_width))
733 }
734
735 #[inline]
738 pub fn set_line_join(&mut self, line_join: PdfPageObjectLineJoin) -> Result<(), PdfiumError> {
739 self.apply_to_each(|object| object.set_line_join(line_join))
740 }
741
742 #[inline]
745 pub fn set_line_cap(&mut self, line_cap: PdfPageObjectLineCap) -> Result<(), PdfiumError> {
746 self.apply_to_each(|object| object.set_line_cap(line_cap))
747 }
748
749 #[inline]
756 pub fn set_fill_and_stroke_mode(&mut self, fill_mode: PdfPathFillMode, do_stroke: bool) -> Result<(), PdfiumError> {
757 self.apply_to_each(|object| {
758 if let Some(object) = object.as_path_object_mut() {
759 object.set_fill_and_stroke_mode(fill_mode, do_stroke)
760 } else {
761 Ok(())
762 }
763 })
764 }
765
766 #[inline]
768 pub(crate) fn apply_to_each<F, T>(&mut self, mut f: F) -> Result<(), PdfiumError>
769 where
770 F: FnMut(&mut PdfPageObject<'a>) -> Result<T, PdfiumError>,
771 {
772 let mut error = None;
773
774 self.object_handles.iter().for_each(|handle| {
775 if let Err(err) = f(&mut self.get_object_from_handle(handle)) {
776 error = Some(err)
777 }
778 });
779
780 match error {
781 Some(err) => Err(err),
782 None => Ok(()),
783 }
784 }
785
786 #[inline]
788 pub(crate) fn for_each<F>(&self, mut f: F)
789 where
790 F: FnMut(&mut PdfPageObject<'a>),
791 {
792 self.object_handles.iter().for_each(|handle| {
793 f(&mut self.get_object_from_handle(handle));
794 });
795 }
796
797 #[inline]
799 pub(crate) fn get_object_from_handle(&self, handle: &FPDF_PAGEOBJECT) -> PdfPageObject<'a> {
800 PdfPageObject::from_pdfium(*handle, *self.ownership(), self.bindings())
801 }
802
803 create_transform_setters!(
804 &mut Self,
805 Result<(), PdfiumError>,
806 "every [PdfPageObject] in this group",
807 "every [PdfPageObject] in this group.",
808 "every [PdfPageObject] in this group,"
809 );
810
811 fn transform_impl(
812 &mut self,
813 a: PdfMatrixValue,
814 b: PdfMatrixValue,
815 c: PdfMatrixValue,
816 d: PdfMatrixValue,
817 e: PdfMatrixValue,
818 f: PdfMatrixValue,
819 ) -> Result<(), PdfiumError> {
820 self.apply_to_each(|object| object.transform(a, b, c, d, e, f))
821 }
822
823 fn reset_matrix_impl(&mut self, matrix: PdfMatrix) -> Result<(), PdfiumError> {
824 self.apply_to_each(|object| object.reset_matrix_impl(matrix))
825 }
826}
827
828pub struct PdfPageGroupObjectIterator<'a> {
830 group: &'a PdfPageGroupObject<'a>,
831 next_index: PdfPageObjectIndex,
832}
833
834impl<'a> PdfPageGroupObjectIterator<'a> {
835 #[inline]
836 pub(crate) fn new(group: &'a PdfPageGroupObject<'a>) -> Self {
837 PdfPageGroupObjectIterator { group, next_index: 0 }
838 }
839}
840
841impl<'a> Iterator for PdfPageGroupObjectIterator<'a> {
842 type Item = PdfPageObject<'a>;
843
844 fn next(&mut self) -> Option<Self::Item> {
845 let next = self.group.get(self.next_index);
846
847 self.next_index += 1;
848
849 next.ok()
850 }
851}
852
853#[cfg(test)]
854mod test {
855 use crate::prelude::*;
856 use crate::utils::test::{test_bind_to_pdfium, test_fixture_path};
857
858 #[test]
859 fn test_group_bounds() -> Result<(), PdfiumError> {
860 let pdfium = test_bind_to_pdfium();
861
862 let document = pdfium.load_pdf_from_file(&test_fixture_path("export-test.pdf"), None)?;
863
864 let page = document.pages().get(2)?;
865
866 let mut group = page.objects().create_empty_group();
867
868 group.append(
869 page.objects()
870 .iter()
871 .filter(|object| {
872 object.object_type() == PdfPageObjectType::Text
873 && object.bounds().unwrap().bottom() > page.height() / 2.0
874 })
875 .collect::<Vec<_>>()
876 .as_mut_slice(),
877 )?;
878
879 let bounds = group.bounds()?;
880
881 assert_eq!(bounds.bottom().value, 428.31033);
882 assert_eq!(bounds.left().value, 62.60526);
883 assert_eq!(bounds.top().value, 807.8812);
884 assert_eq!(bounds.right().value, 544.48096);
885
886 Ok(())
887 }
888
889 #[test]
890 fn test_group_text() -> Result<(), PdfiumError> {
891 let pdfium = test_bind_to_pdfium();
892
893 let document = pdfium.load_pdf_from_file(&test_fixture_path("export-test.pdf"), None)?;
894
895 let page = document.pages().get(5)?;
896
897 let mut group = page.objects().create_empty_group();
898
899 group.append(
900 page.objects()
901 .iter()
902 .filter(|object| {
903 object.object_type() == PdfPageObjectType::Text
904 && object.bounds().unwrap().bottom() < page.height() / 2.0
905 })
906 .collect::<Vec<_>>()
907 .as_mut_slice(),
908 )?;
909
910 assert_eq!(
911 group.text_separated(" "),
912 "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."
913 );
914
915 Ok(())
916 }
917
918 #[test]
919 fn test_group_apply() -> Result<(), PdfiumError> {
920 let pdfium = test_bind_to_pdfium();
921
922 let mut document = pdfium.create_new_pdf()?;
923
924 let mut page = document.pages_mut().create_page_at_start(PdfPagePaperSize::a4())?;
925
926 page.objects_mut().create_path_object_rect(
927 PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0),
928 None,
929 None,
930 Some(PdfColor::RED),
931 )?;
932
933 page.objects_mut().create_path_object_rect(
934 PdfRect::new_from_values(150.0, 150.0, 250.0, 250.0),
935 None,
936 None,
937 Some(PdfColor::GREEN),
938 )?;
939
940 page.objects_mut().create_path_object_rect(
941 PdfRect::new_from_values(200.0, 200.0, 300.0, 300.0),
942 None,
943 None,
944 Some(PdfColor::BLUE),
945 )?;
946
947 let mut group = PdfPageGroupObject::new(&page, |_| true)?;
948
949 let bounds = group.bounds()?;
950
951 assert_eq!(bounds.bottom().value, 100.0);
952 assert_eq!(bounds.left().value, 100.0);
953 assert_eq!(bounds.top().value, 300.0);
954 assert_eq!(bounds.right().value, 300.0);
955
956 group.translate(PdfPoints::new(150.0), PdfPoints::new(200.0))?;
957
958 let bounds = group.bounds()?;
959
960 assert_eq!(bounds.bottom().value, 300.0);
961 assert_eq!(bounds.left().value, 250.0);
962 assert_eq!(bounds.top().value, 500.0);
963 assert_eq!(bounds.right().value, 450.0);
964
965 Ok(())
966 }
967}