1use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE, FPDF_PAGEOBJECT};
5use crate::create_transform_setters;
6use crate::error::PdfiumError;
7use crate::pdf::color::PdfColor;
8use crate::pdf::document::page::annotation::PdfPageAnnotation;
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;
21use crate::pdf::document::PdfDocument;
22use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
23use crate::pdf::points::PdfPoints;
24use crate::pdf::rect::PdfRect;
25use crate::pdfium::PdfiumLibraryBindingsAccessor;
26use crate::prelude::PdfPageXObjectFormObject;
27use std::ffi::c_double;
28use std::marker::PhantomData;
29
30#[cfg(doc)]
31use {
32 crate::pdf::document::page::object::text::PdfPageTextObject,
33 crate::pdf::document::page::objects::PdfPageObjects,
34};
35
36pub struct PdfPageGroupObject<'a> {
45 document_handle: FPDF_DOCUMENT,
46 page_handle: FPDF_PAGE,
47 ownership: PdfPageObjectOwnership,
48 object_handles: Vec<FPDF_PAGEOBJECT>,
49 lifetime: PhantomData<&'a FPDF_DOCUMENT>,
50}
51
52impl<'a> PdfPageGroupObject<'a> {
53 #[inline]
54 pub(crate) fn from_pdfium(document_handle: FPDF_DOCUMENT, page_handle: FPDF_PAGE) -> 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 lifetime: PhantomData,
61 }
62 }
63
64 pub fn empty(page: &'a PdfPage) -> Self {
67 Self::from_pdfium(page.document_handle(), page.page_handle())
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());
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(
89 page: &PdfPage<'a>,
90 mut objects: Vec<PdfPageObject<'a>>,
91 ) -> Result<Self, PdfiumError> {
92 Self::from_slice(page, objects.as_mut_slice())
93 }
94
95 pub fn from_slice(
98 page: &PdfPage<'a>,
99 objects: &mut [PdfPageObject<'a>],
100 ) -> Result<Self, PdfiumError> {
101 let mut result = Self::from_pdfium(page.document_handle(), page.page_handle());
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 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);
173 } else {
174 true
177 }
178 } else {
179 object.add_object_to_page_handle(self.document_handle(), self.page_handle())?;
182
183 false
184 };
185
186 self.object_handles.push(object.object_handle());
187
188 Ok(())
189 }
190
191 pub fn append(&mut self, objects: &mut [PdfPageObject<'a>]) -> Result<(), PdfiumError> {
193 let content_regeneration_strategy =
196 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
197 self.document_handle(),
198 self.page_handle(),
199 )
200 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
201
202 let page_index =
203 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
204
205 if let Some(page_index) = page_index {
206 PdfPageIndexCache::cache_props_for_page(
207 self.document_handle(),
208 self.page_handle(),
209 page_index,
210 PdfPageContentRegenerationStrategy::Manual,
211 );
212 }
213
214 for object in objects.iter_mut() {
215 self.push(object)?;
216 }
217
218 if let Some(page_index) = page_index {
221 PdfPageIndexCache::cache_props_for_page(
222 self.document_handle(),
223 self.page_handle(),
224 page_index,
225 content_regeneration_strategy,
226 );
227 }
228
229 if content_regeneration_strategy
230 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
231 {
232 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
233 }
234
235 Ok(())
236 }
237
238 pub fn remove_objects_from_page(mut self) -> Result<(), PdfiumError> {
249 let content_regeneration_strategy =
252 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
253 self.document_handle(),
254 self.page_handle(),
255 )
256 .unwrap_or(PdfPageContentRegenerationStrategy::AutomaticOnEveryChange);
257
258 let page_index =
259 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle());
260
261 if let Some(page_index) = page_index {
262 PdfPageIndexCache::cache_props_for_page(
263 self.document_handle(),
264 self.page_handle(),
265 page_index,
266 PdfPageContentRegenerationStrategy::Manual,
267 );
268 }
269
270 self.apply_to_each(|object| object.remove_object_from_page())?;
273 self.object_handles.clear();
274
275 let page_height =
280 PdfPoints::new(unsafe { self.bindings().FPDF_GetPageHeightF(self.page_handle()) });
281
282 for index in 0..(unsafe { self.bindings().FPDFPage_CountObjects(self.page_handle()) }) {
283 let mut object = PdfPageObject::from_pdfium(
284 unsafe {
285 self.bindings()
286 .FPDFPage_GetObject(self.page_handle(), index)
287 },
288 *self.ownership(),
289 self.bindings(),
290 );
291
292 object.flip_vertically()?;
300 object.translate(PdfPoints::ZERO, page_height)?;
301 }
302
303 if let Some(page_index) = page_index {
306 PdfPageIndexCache::cache_props_for_page(
307 self.document_handle,
308 self.page_handle,
309 page_index,
310 content_regeneration_strategy,
311 );
312 }
313
314 if content_regeneration_strategy
315 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
316 {
317 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
318 }
319
320 Ok(())
321 }
322
323 #[inline]
325 pub fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'_>, PdfiumError> {
326 if let Some(handle) = self.object_handles.get(index) {
327 Ok(self.get_object_from_handle(handle))
328 } else {
329 Err(PdfiumError::PageObjectIndexOutOfBounds)
330 }
331 }
332
333 pub fn retain<F>(&mut self, f: F)
338 where
339 F: Fn(&PdfPageObject) -> bool,
340 {
341 let mut do_retain = vec![false; self.object_handles.len()];
350
351 for (index, handle) in self.object_handles.iter().enumerate() {
352 do_retain[index] = f(&self.get_object_from_handle(handle));
353 }
354
355 let mut index = 0;
358
359 self.object_handles.retain(|_| {
360 let do_retain = do_retain[index];
363
364 index += 1;
365
366 do_retain
367 });
368 }
369
370 pub fn move_to_page(mut self, page: &mut PdfPage) -> Result<(), PdfiumError> {
377 self.apply_to_each(|object| object.move_to_page(page))?;
378 self.object_handles.clear();
379 Ok(())
380 }
381
382 pub fn move_to_annotation(
389 mut self,
390 annotation: &mut PdfPageAnnotation,
391 ) -> Result<(), PdfiumError> {
392 self.apply_to_each(|object| object.move_to_annotation(annotation))?;
393 self.object_handles.clear();
394 Ok(())
395 }
396
397 pub fn copy_to_page(
401 &mut self,
402 page: &mut PdfPage<'a>,
403 ) -> Result<PdfPageObject<'a>, PdfiumError> {
404 let mut object = self.copy_into_x_object_form_object_from_handles(
405 page.document_handle(),
406 page.width(),
407 page.height(),
408 )?;
409
410 object.move_to_page(page)?;
411
412 Ok(object)
413 }
414
415 pub fn copy_into_x_object_form_object(
418 &mut self,
419 destination: &mut PdfDocument<'a>,
420 ) -> Result<PdfPageObject<'a>, PdfiumError> {
421 self.copy_into_x_object_form_object_from_handles(
422 destination.handle(),
423 PdfPoints::new(unsafe { self.bindings().FPDF_GetPageWidthF(self.page_handle()) }),
424 PdfPoints::new(unsafe { self.bindings().FPDF_GetPageHeightF(self.page_handle()) }),
425 )
426 }
427
428 pub(crate) fn copy_into_x_object_form_object_from_handles(
429 &mut self,
430 destination_document_handle: FPDF_DOCUMENT,
431 destination_page_width: PdfPoints,
432 destination_page_height: PdfPoints,
433 ) -> Result<PdfPageObject<'a>, PdfiumError> {
434 let src_doc_handle = self.document_handle();
439 let src_page_handle = self.page_handle();
440
441 let tmp_page_index = unsafe { self.bindings().FPDF_GetPageCount(src_doc_handle) };
444
445 let tmp_page = unsafe {
446 self.bindings().FPDFPage_New(
447 src_doc_handle,
448 tmp_page_index,
449 destination_page_width.value as c_double,
450 destination_page_height.value as c_double,
451 )
452 };
453
454 PdfPageIndexCache::cache_props_for_page(
455 src_doc_handle,
456 tmp_page,
457 tmp_page_index as PdfPageIndex,
458 PdfPageContentRegenerationStrategy::AutomaticOnEveryChange,
459 );
460
461 self.apply_to_each(|object| {
464 match object.ownership() {
465 PdfPageObjectOwnership::Page(_) => object.remove_object_from_page()?,
466 PdfPageObjectOwnership::AttachedAnnotation(_)
467 | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
468 object.remove_object_from_annotation()?
469 }
470 _ => {}
471 }
472
473 object.add_object_to_page_handle(src_doc_handle, tmp_page)?;
474
475 Ok(())
476 })?;
477 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
478 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
479
480 let x_object = unsafe {
483 self.bindings().FPDF_NewXObjectFromPage(
484 destination_document_handle,
485 src_doc_handle,
486 tmp_page_index,
487 )
488 };
489
490 let object_handle = unsafe { self.bindings().FPDF_NewFormObjectFromXObject(x_object) };
491 if object_handle.is_null() {
492 return Err(PdfiumError::PdfiumLibraryInternalError(
493 crate::error::PdfiumInternalError::Unknown,
494 ));
495 }
496
497 let object = PdfPageXObjectFormObject::from_pdfium(
498 object_handle,
499 PdfPageObjectOwnership::owned_by_document(destination_document_handle),
500 );
501
502 unsafe {
503 self.bindings().FPDF_CloseXObject(x_object);
504 }
505
506 self.apply_to_each(|object| {
509 match object.ownership() {
510 PdfPageObjectOwnership::Page(ownership) => {
511 if ownership.page_handle() != src_page_handle {
512 object.remove_object_from_page()?
513 }
514 }
515 PdfPageObjectOwnership::AttachedAnnotation(_)
516 | PdfPageObjectOwnership::UnattachedAnnotation(_) => {
517 object.remove_object_from_annotation()?
518 }
519 _ => {}
520 }
521 object.add_object_to_page_handle(src_doc_handle, src_page_handle)?;
522
523 Ok(())
524 })?;
525 PdfPage::regenerate_content_immut_for_handle(tmp_page, self.bindings())?;
526 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())?;
527
528 PdfPageIndexCache::remove_index_for_page(src_doc_handle, tmp_page);
529
530 unsafe {
531 self.bindings()
532 .FPDFPage_Delete(src_doc_handle, tmp_page_index);
533 }
534
535 Ok(PdfPageObject::XObjectForm(object))
536 }
537
538 #[inline]
540 pub fn iter(&'a self) -> PdfPageGroupObjectIterator<'a> {
541 PdfPageGroupObjectIterator::new(self)
542 }
543
544 #[inline]
546 pub fn text(&self) -> String {
547 self.text_separated("")
548 }
549
550 pub fn text_separated(&self, separator: &str) -> String {
553 let mut strings = Vec::with_capacity(self.len());
554
555 self.for_each(|object| {
556 if let Some(object) = object.as_text_object() {
557 strings.push(object.text());
558 }
559 });
560
561 strings.join(separator)
562 }
563
564 #[inline]
566 pub fn has_transparency(&self) -> bool {
567 self.object_handles.iter().any(|object_handle| {
568 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings())
569 .has_transparency()
570 })
571 }
572
573 pub fn bounds(&self) -> Result<PdfRect, PdfiumError> {
576 let mut bottom = PdfPoints::MAX;
577 let mut top = PdfPoints::MIN;
578 let mut left = PdfPoints::MAX;
579 let mut right = PdfPoints::MIN;
580 let mut empty = true;
581
582 self.object_handles.iter().for_each(|object_handle| {
583 if let Ok(object_bounds) =
584 PdfPageObject::from_pdfium(*object_handle, *self.ownership(), self.bindings())
585 .bounds()
586 {
587 empty = false;
588
589 if object_bounds.bottom() < bottom {
590 bottom = object_bounds.bottom();
591 }
592
593 if object_bounds.left() < left {
594 left = object_bounds.left();
595 }
596
597 if object_bounds.top() > top {
598 top = object_bounds.top();
599 }
600
601 if object_bounds.right() > right {
602 right = object_bounds.right();
603 }
604 }
605 });
606
607 if empty {
608 Err(PdfiumError::EmptyPageObjectGroup)
609 } else {
610 Ok(PdfRect::new(bottom, left, top, right))
611 }
612 }
613
614 #[inline]
616 pub fn set_blend_mode(
617 &mut self,
618 blend_mode: PdfPageObjectBlendMode,
619 ) -> Result<(), PdfiumError> {
620 self.apply_to_each(|object| object.set_blend_mode(blend_mode))
621 }
622
623 #[inline]
625 pub fn set_fill_color(&mut self, fill_color: PdfColor) -> Result<(), PdfiumError> {
626 self.apply_to_each(|object| object.set_fill_color(fill_color))
627 }
628
629 #[inline]
634 pub fn set_stroke_color(&mut self, stroke_color: PdfColor) -> Result<(), PdfiumError> {
635 self.apply_to_each(|object| object.set_stroke_color(stroke_color))
636 }
637
638 #[inline]
648 pub fn set_stroke_width(&mut self, stroke_width: PdfPoints) -> Result<(), PdfiumError> {
649 self.apply_to_each(|object| object.set_stroke_width(stroke_width))
650 }
651
652 #[inline]
655 pub fn set_line_join(&mut self, line_join: PdfPageObjectLineJoin) -> Result<(), PdfiumError> {
656 self.apply_to_each(|object| object.set_line_join(line_join))
657 }
658
659 #[inline]
662 pub fn set_line_cap(&mut self, line_cap: PdfPageObjectLineCap) -> Result<(), PdfiumError> {
663 self.apply_to_each(|object| object.set_line_cap(line_cap))
664 }
665
666 #[inline]
673 pub fn set_fill_and_stroke_mode(
674 &mut self,
675 fill_mode: PdfPathFillMode,
676 do_stroke: bool,
677 ) -> Result<(), PdfiumError> {
678 self.apply_to_each(|object| {
679 if let Some(object) = object.as_path_object_mut() {
680 object.set_fill_and_stroke_mode(fill_mode, do_stroke)
681 } else {
682 Ok(())
683 }
684 })
685 }
686
687 #[inline]
689 pub(crate) fn apply_to_each<F, T>(&mut self, mut f: F) -> Result<(), PdfiumError>
690 where
691 F: FnMut(&mut PdfPageObject<'a>) -> Result<T, PdfiumError>,
692 {
693 let mut error = None;
694
695 self.object_handles.iter().for_each(|handle| {
696 if let Err(err) = f(&mut self.get_object_from_handle(handle)) {
697 error = Some(err)
698 }
699 });
700
701 match error {
702 Some(err) => Err(err),
703 None => Ok(()),
704 }
705 }
706
707 #[inline]
709 pub(crate) fn for_each<F>(&self, mut f: F)
710 where
711 F: FnMut(&mut PdfPageObject<'a>),
712 {
713 self.object_handles.iter().for_each(|handle| {
714 f(&mut self.get_object_from_handle(handle));
715 });
716 }
717
718 #[inline]
720 pub(crate) fn get_object_from_handle(&self, handle: &FPDF_PAGEOBJECT) -> PdfPageObject<'a> {
721 PdfPageObject::from_pdfium(*handle, *self.ownership(), self.bindings())
722 }
723
724 create_transform_setters!(
725 &mut Self,
726 Result<(), PdfiumError>,
727 "every [PdfPageObject] in this group",
728 "every [PdfPageObject] in this group.",
729 "every [PdfPageObject] in this group,"
730 );
731
732 fn transform_impl(
734 &mut self,
735 a: PdfMatrixValue,
736 b: PdfMatrixValue,
737 c: PdfMatrixValue,
738 d: PdfMatrixValue,
739 e: PdfMatrixValue,
740 f: PdfMatrixValue,
741 ) -> Result<(), PdfiumError> {
742 self.apply_to_each(|object| object.transform(a, b, c, d, e, f))
743 }
744
745 fn reset_matrix_impl(&mut self, matrix: PdfMatrix) -> Result<(), PdfiumError> {
747 self.apply_to_each(|object| object.reset_matrix_impl(matrix))
748 }
749}
750
751impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageGroupObject<'a> {}
752
753#[cfg(feature = "thread_safe")]
754unsafe impl<'a> Send for PdfPageGroupObject<'a> {}
755
756#[cfg(feature = "thread_safe")]
757unsafe impl<'a> Sync for PdfPageGroupObject<'a> {}
758
759pub struct PdfPageGroupObjectIterator<'a> {
761 group: &'a PdfPageGroupObject<'a>,
762 next_index: PdfPageObjectIndex,
763}
764
765impl<'a> PdfPageGroupObjectIterator<'a> {
766 #[inline]
767 pub(crate) fn new(group: &'a PdfPageGroupObject<'a>) -> Self {
768 PdfPageGroupObjectIterator {
769 group,
770 next_index: 0,
771 }
772 }
773}
774
775impl<'a> Iterator for PdfPageGroupObjectIterator<'a> {
776 type Item = PdfPageObject<'a>;
777
778 fn next(&mut self) -> Option<Self::Item> {
779 let next = self.group.get(self.next_index);
780
781 self.next_index += 1;
782
783 next.ok()
784 }
785}
786
787#[cfg(test)]
788mod test {
789 use crate::prelude::*;
790 use crate::utils::test::test_bind_to_pdfium;
791
792 #[test]
793 fn test_group_bounds() -> Result<(), PdfiumError> {
794 let pdfium = test_bind_to_pdfium();
795
796 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
797
798 let page = document.pages().get(2)?;
801
802 let mut group = page.objects().create_empty_group();
803
804 group.append(
805 page.objects()
806 .iter()
807 .filter(|object| {
808 object.object_type() == PdfPageObjectType::Text
809 && object.bounds().unwrap().bottom() > page.height() / 2.0
810 })
811 .collect::<Vec<_>>()
812 .as_mut_slice(),
813 )?;
814
815 let bounds = group.bounds()?;
818
819 assert_eq!(bounds.bottom().value, 428.31033);
820 assert_eq!(bounds.left().value, 62.60526);
821 assert_eq!(bounds.top().value, 807.8812);
822 assert_eq!(bounds.right().value, 544.48096);
823
824 Ok(())
825 }
826
827 #[test]
828 fn test_group_text() -> Result<(), PdfiumError> {
829 let pdfium = test_bind_to_pdfium();
830
831 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
832
833 let page = document.pages().get(5)?;
836
837 let mut group = page.objects().create_empty_group();
838
839 group.append(
840 page.objects()
841 .iter()
842 .filter(|object| {
843 object.object_type() == PdfPageObjectType::Text
844 && object.bounds().unwrap().bottom() < page.height() / 2.0
845 })
846 .collect::<Vec<_>>()
847 .as_mut_slice(),
848 )?;
849
850 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.");
853
854 Ok(())
855 }
856
857 #[test]
858 fn test_group_apply() -> Result<(), PdfiumError> {
859 let pdfium = test_bind_to_pdfium();
863
864 let mut document = pdfium.create_new_pdf()?;
865
866 let mut page = document
867 .pages_mut()
868 .create_page_at_start(PdfPagePaperSize::a4())?;
869
870 page.objects_mut().create_path_object_rect(
871 PdfRect::new_from_values(100.0, 100.0, 200.0, 200.0),
872 None,
873 None,
874 Some(PdfColor::RED),
875 )?;
876
877 page.objects_mut().create_path_object_rect(
878 PdfRect::new_from_values(150.0, 150.0, 250.0, 250.0),
879 None,
880 None,
881 Some(PdfColor::GREEN),
882 )?;
883
884 page.objects_mut().create_path_object_rect(
885 PdfRect::new_from_values(200.0, 200.0, 300.0, 300.0),
886 None,
887 None,
888 Some(PdfColor::BLUE),
889 )?;
890
891 let mut group = PdfPageGroupObject::new(&page, |_| true)?;
892
893 let bounds = group.bounds()?;
894
895 assert_eq!(bounds.bottom().value, 100.0);
896 assert_eq!(bounds.left().value, 100.0);
897 assert_eq!(bounds.top().value, 300.0);
898 assert_eq!(bounds.right().value, 300.0);
899
900 group.translate(PdfPoints::new(150.0), PdfPoints::new(200.0))?;
901
902 let bounds = group.bounds()?;
903
904 assert_eq!(bounds.bottom().value, 300.0);
905 assert_eq!(bounds.left().value, 250.0);
906 assert_eq!(bounds.top().value, 500.0);
907 assert_eq!(bounds.right().value, 450.0);
908
909 Ok(())
910 }
911}