1use crate::error::{PdfiumError, PdfiumInternalError};
5use crate::pdf::color::PdfColor;
6use crate::pdf::document::fonts::ToPdfFontToken;
7use crate::pdf::document::page::object::image::PdfPageImageObject;
8use crate::pdf::document::page::object::path::PdfPagePathObject;
9use crate::pdf::document::page::object::text::PdfPageTextObject;
10use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
11use crate::pdf::document::page::objects::private::internal::PdfPageObjectsPrivate;
12use crate::pdf::document::page::PdfPageObjectOwnership;
13use crate::pdf::points::PdfPoints;
14use crate::pdf::rect::PdfRect;
15use std::ops::{Range, RangeInclusive};
16
17#[cfg(any(feature = "image_latest", feature = "image_025"))]
18use image_025::DynamicImage;
19
20#[cfg(feature = "image_024")]
21use image_024::DynamicImage;
22
23#[cfg(feature = "image_023")]
24use image_023::{DynamicImage, GenericImageView};
25
26#[cfg(doc)]
27use crate::pdf::document::page::PdfPageObjects;
28
29pub type PdfPageObjectIndex = usize;
31
32pub trait PdfPageObjectsCommon<'a> {
35 fn len(&self) -> PdfPageObjectIndex;
37
38 #[inline]
40 fn is_empty(&self) -> bool {
41 self.len() == 0
42 }
43
44 #[inline]
46 fn as_range(&self) -> Range<PdfPageObjectIndex> {
47 0..self.len()
48 }
49
50 #[inline]
52 fn as_range_inclusive(&self) -> RangeInclusive<PdfPageObjectIndex> {
53 if self.is_empty() {
54 0..=0
55 } else {
56 0..=(self.len() - 1)
57 }
58 }
59
60 fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
62
63 #[inline]
65 fn first(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
66 if !self.is_empty() {
67 self.get(0)
68 } else {
69 Err(PdfiumError::NoPageObjectsInCollection)
70 }
71 }
72
73 #[inline]
75 fn last(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
76 if !self.is_empty() {
77 self.get(self.len() - 1)
78 } else {
79 Err(PdfiumError::NoPageObjectsInCollection)
80 }
81 }
82
83 fn iter(&'a self) -> PdfPageObjectsIterator<'a>;
85
86 fn bounds(&'a self) -> PdfRect {
89 let mut bottom: f32 = 0.0;
90 let mut top: f32 = 0.0;
91 let mut left: f32 = 0.0;
92 let mut right: f32 = 0.0;
93
94 for object in self.iter() {
95 if let Ok(bounds) = object.bounds() {
96 bottom = bottom.min(bounds.bottom().value);
97 top = top.max(bounds.top().value);
98 left = left.min(bounds.left().value);
99 right = right.max(bounds.right().value);
100 }
101 }
102
103 PdfRect::new_from_values(bottom, left, top, right)
104 }
105
106 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
114
115 #[inline]
122 fn add_text_object(
123 &mut self,
124 object: PdfPageTextObject<'a>,
125 ) -> Result<PdfPageObject<'a>, PdfiumError> {
126 self.add_object(PdfPageObject::Text(object))
127 }
128
129 fn create_text_object(
137 &mut self,
138 x: PdfPoints,
139 y: PdfPoints,
140 text: impl ToString,
141 font: impl ToPdfFontToken,
142 font_size: PdfPoints,
143 ) -> Result<PdfPageObject<'a>, PdfiumError>;
144
145 #[inline]
152 fn add_path_object(
153 &mut self,
154 object: PdfPagePathObject<'a>,
155 ) -> Result<PdfPageObject<'a>, PdfiumError> {
156 self.add_object(PdfPageObject::Path(object))
157 }
158
159 fn create_path_object_line(
167 &mut self,
168 x1: PdfPoints,
169 y1: PdfPoints,
170 x2: PdfPoints,
171 y2: PdfPoints,
172 stroke_color: PdfColor,
173 stroke_width: PdfPoints,
174 ) -> Result<PdfPageObject<'a>, PdfiumError>;
175
176 #[allow(clippy::too_many_arguments)]
184 fn create_path_object_bezier(
185 &mut self,
186 x1: PdfPoints,
187 y1: PdfPoints,
188 x2: PdfPoints,
189 y2: PdfPoints,
190 control1_x: PdfPoints,
191 control1_y: PdfPoints,
192 control2_x: PdfPoints,
193 control2_y: PdfPoints,
194 stroke_color: PdfColor,
195 stroke_width: PdfPoints,
196 ) -> Result<PdfPageObject<'a>, PdfiumError>;
197
198 fn create_path_object_rect(
208 &mut self,
209 rect: PdfRect,
210 stroke_color: Option<PdfColor>,
211 stroke_width: Option<PdfPoints>,
212 fill_color: Option<PdfColor>,
213 ) -> Result<PdfPageObject<'a>, PdfiumError>;
214
215 fn create_path_object_circle(
225 &mut self,
226 rect: PdfRect,
227 stroke_color: Option<PdfColor>,
228 stroke_width: Option<PdfPoints>,
229 fill_color: Option<PdfColor>,
230 ) -> Result<PdfPageObject<'a>, PdfiumError>;
231
232 fn create_path_object_circle_at(
242 &mut self,
243 center_x: PdfPoints,
244 center_y: PdfPoints,
245 radius: PdfPoints,
246 stroke_color: Option<PdfColor>,
247 stroke_width: Option<PdfPoints>,
248 fill_color: Option<PdfColor>,
249 ) -> Result<PdfPageObject<'a>, PdfiumError>;
250
251 fn create_path_object_ellipse(
261 &mut self,
262 rect: PdfRect,
263 stroke_color: Option<PdfColor>,
264 stroke_width: Option<PdfPoints>,
265 fill_color: Option<PdfColor>,
266 ) -> Result<PdfPageObject<'a>, PdfiumError>;
267
268 #[allow(clippy::too_many_arguments)]
278 fn create_path_object_ellipse_at(
279 &mut self,
280 center_x: PdfPoints,
281 center_y: PdfPoints,
282 x_radius: PdfPoints,
283 y_radius: PdfPoints,
284 stroke_color: Option<PdfColor>,
285 stroke_width: Option<PdfPoints>,
286 fill_color: Option<PdfColor>,
287 ) -> Result<PdfPageObject<'a>, PdfiumError>;
288
289 #[inline]
296 fn add_image_object(
297 &mut self,
298 object: PdfPageImageObject<'a>,
299 ) -> Result<PdfPageObject<'a>, PdfiumError> {
300 self.add_object(PdfPageObject::Image(object))
301 }
302
303 #[cfg(feature = "image_api")]
317 fn create_image_object(
318 &mut self,
319 x: PdfPoints,
320 y: PdfPoints,
321 image: &DynamicImage,
322 width: Option<PdfPoints>,
323 height: Option<PdfPoints>,
324 ) -> Result<PdfPageObject<'a>, PdfiumError>;
325
326 fn remove_object(
336 &mut self,
337 object: PdfPageObject<'a>,
338 ) -> Result<PdfPageObject<'a>, PdfiumError>;
339
340 fn remove_object_at_index(
350 &mut self,
351 index: PdfPageObjectIndex,
352 ) -> Result<PdfPageObject<'a>, PdfiumError>;
353}
354
355impl<'a, T> PdfPageObjectsCommon<'a> for T
358where
359 T: PdfPageObjectsPrivate<'a>,
360{
361 #[inline]
362 fn len(&self) -> PdfPageObjectIndex {
363 self.len_impl()
364 }
365
366 #[inline]
367 fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
368 self.get_impl(index)
369 }
370
371 #[inline]
372 fn iter(&'a self) -> PdfPageObjectsIterator<'a> {
373 self.iter_impl()
374 }
375
376 #[inline]
377 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
378 self.add_object_impl(object)
379 }
380
381 #[inline]
382 fn create_text_object(
383 &mut self,
384 x: PdfPoints,
385 y: PdfPoints,
386 text: impl ToString,
387 font: impl ToPdfFontToken,
388 font_size: PdfPoints,
389 ) -> Result<PdfPageObject<'a>, PdfiumError> {
390 let document_handle = match self.ownership() {
391 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
392 PdfPageObjectOwnership::AttachedAnnotation(ownership) => {
393 Some(ownership.document_handle())
394 }
395 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => {
396 Some(ownership.document_handle())
397 }
398 _ => None,
399 };
400
401 if let Some(document_handle) = document_handle {
402 let mut object = PdfPageTextObject::new_from_handles(
403 document_handle,
404 text,
405 font.token().handle(),
406 font_size,
407 self.bindings(),
408 )?;
409
410 object.translate(x, y)?;
411
412 self.add_text_object(object)
413 } else {
414 Err(PdfiumError::OwnershipNotAttachedToPage)
415 }
416 }
417
418 #[inline]
419 fn create_path_object_line(
420 &mut self,
421 x1: PdfPoints,
422 y1: PdfPoints,
423 x2: PdfPoints,
424 y2: PdfPoints,
425 stroke_color: PdfColor,
426 stroke_width: PdfPoints,
427 ) -> Result<PdfPageObject<'a>, PdfiumError> {
428 let object = PdfPagePathObject::new_line_from_bindings(
429 self.bindings(),
430 x1,
431 y1,
432 x2,
433 y2,
434 stroke_color,
435 stroke_width,
436 )?;
437
438 self.add_path_object(object)
439 }
440
441 #[inline]
442 fn create_path_object_bezier(
443 &mut self,
444 x1: PdfPoints,
445 y1: PdfPoints,
446 x2: PdfPoints,
447 y2: PdfPoints,
448 control1_x: PdfPoints,
449 control1_y: PdfPoints,
450 control2_x: PdfPoints,
451 control2_y: PdfPoints,
452 stroke_color: PdfColor,
453 stroke_width: PdfPoints,
454 ) -> Result<PdfPageObject<'a>, PdfiumError> {
455 let object = PdfPagePathObject::new_bezier_from_bindings(
456 self.bindings(),
457 x1,
458 y1,
459 x2,
460 y2,
461 control1_x,
462 control1_y,
463 control2_x,
464 control2_y,
465 stroke_color,
466 stroke_width,
467 )?;
468
469 self.add_path_object(object)
470 }
471
472 #[inline]
473 fn create_path_object_rect(
474 &mut self,
475 rect: PdfRect,
476 stroke_color: Option<PdfColor>,
477 stroke_width: Option<PdfPoints>,
478 fill_color: Option<PdfColor>,
479 ) -> Result<PdfPageObject<'a>, PdfiumError> {
480 let object = PdfPagePathObject::new_rect_from_bindings(
481 self.bindings(),
482 rect,
483 stroke_color,
484 stroke_width,
485 fill_color,
486 )?;
487
488 self.add_path_object(object)
489 }
490
491 #[inline]
492 fn create_path_object_circle(
493 &mut self,
494 rect: PdfRect,
495 stroke_color: Option<PdfColor>,
496 stroke_width: Option<PdfPoints>,
497 fill_color: Option<PdfColor>,
498 ) -> Result<PdfPageObject<'a>, PdfiumError> {
499 let object = PdfPagePathObject::new_circle_from_bindings(
500 self.bindings(),
501 rect,
502 stroke_color,
503 stroke_width,
504 fill_color,
505 )?;
506
507 self.add_path_object(object)
508 }
509
510 #[inline]
511 fn create_path_object_circle_at(
512 &mut self,
513 center_x: PdfPoints,
514 center_y: PdfPoints,
515 radius: PdfPoints,
516 stroke_color: Option<PdfColor>,
517 stroke_width: Option<PdfPoints>,
518 fill_color: Option<PdfColor>,
519 ) -> Result<PdfPageObject<'a>, PdfiumError> {
520 let object = PdfPagePathObject::new_circle_at_from_bindings(
521 self.bindings(),
522 center_x,
523 center_y,
524 radius,
525 stroke_color,
526 stroke_width,
527 fill_color,
528 )?;
529
530 self.add_path_object(object)
531 }
532
533 #[inline]
534 fn create_path_object_ellipse(
535 &mut self,
536 rect: PdfRect,
537 stroke_color: Option<PdfColor>,
538 stroke_width: Option<PdfPoints>,
539 fill_color: Option<PdfColor>,
540 ) -> Result<PdfPageObject<'a>, PdfiumError> {
541 let object = PdfPagePathObject::new_ellipse_from_bindings(
542 self.bindings(),
543 rect,
544 stroke_color,
545 stroke_width,
546 fill_color,
547 )?;
548
549 self.add_path_object(object)
550 }
551
552 #[inline]
553 fn create_path_object_ellipse_at(
554 &mut self,
555 center_x: PdfPoints,
556 center_y: PdfPoints,
557 x_radius: PdfPoints,
558 y_radius: PdfPoints,
559 stroke_color: Option<PdfColor>,
560 stroke_width: Option<PdfPoints>,
561 fill_color: Option<PdfColor>,
562 ) -> Result<PdfPageObject<'a>, PdfiumError> {
563 let object = PdfPagePathObject::new_ellipse_at_from_bindings(
564 self.bindings(),
565 center_x,
566 center_y,
567 x_radius,
568 y_radius,
569 stroke_color,
570 stroke_width,
571 fill_color,
572 )?;
573
574 self.add_path_object(object)
575 }
576
577 #[cfg(feature = "image_api")]
578 fn create_image_object(
579 &mut self,
580 x: PdfPoints,
581 y: PdfPoints,
582 image: &DynamicImage,
583 width: Option<PdfPoints>,
584 height: Option<PdfPoints>,
585 ) -> Result<PdfPageObject<'a>, PdfiumError> {
586 let document_handle = match self.ownership() {
587 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
588 PdfPageObjectOwnership::AttachedAnnotation(ownership) => {
589 Some(ownership.document_handle())
590 }
591 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => {
592 Some(ownership.document_handle())
593 }
594 _ => None,
595 };
596
597 if let Some(document_handle) = document_handle {
598 let image_width = image.width();
599
600 let image_height = image.height();
601
602 let mut object = PdfPageImageObject::new_from_handle(document_handle, self.bindings())?;
603
604 object.set_image(image)?;
605
606 match (width, height) {
609 (Some(width), Some(height)) => {
610 object.scale(width.value, height.value)?;
611 }
612 (Some(width), None) => {
613 let aspect_ratio = image_height as f32 / image_width as f32;
614
615 let height = width * aspect_ratio;
616
617 object.scale(width.value, height.value)?;
618 }
619 (None, Some(height)) => {
620 let aspect_ratio = image_height as f32 / image_width as f32;
621
622 let width = height / aspect_ratio;
623
624 object.scale(width.value, height.value)?;
625 }
626 (None, None) => {}
627 }
628
629 object.translate(x, y)?;
630
631 self.add_image_object(object)
632 } else {
633 Err(PdfiumError::OwnershipNotAttachedToPage)
634 }
635 }
636
637 #[inline]
638 fn remove_object(
639 &mut self,
640 object: PdfPageObject<'a>,
641 ) -> Result<PdfPageObject<'a>, PdfiumError> {
642 self.remove_object_impl(object)
643 }
644
645 fn remove_object_at_index(
646 &mut self,
647 index: PdfPageObjectIndex,
648 ) -> Result<PdfPageObject<'a>, PdfiumError> {
649 if index >= self.len() {
650 return Err(PdfiumError::PageObjectIndexOutOfBounds);
651 }
652
653 if let Ok(object) = self.get(index) {
654 self.remove_object(object)
655 } else {
656 Err(PdfiumError::PdfiumLibraryInternalError(
657 PdfiumInternalError::Unknown,
658 ))
659 }
660 }
661}
662
663pub struct PdfPageObjectsIterator<'a> {
665 objects: &'a dyn PdfPageObjectsPrivate<'a>,
666 next_index: PdfPageObjectIndex,
667}
668
669impl<'a> PdfPageObjectsIterator<'a> {
670 #[inline]
671 pub(crate) fn new(objects: &'a dyn PdfPageObjectsPrivate<'a>) -> Self {
672 PdfPageObjectsIterator {
673 objects,
674 next_index: 0,
675 }
676 }
677}
678
679impl<'a> Iterator for PdfPageObjectsIterator<'a> {
680 type Item = PdfPageObject<'a>;
681
682 fn next(&mut self) -> Option<Self::Item> {
683 let next = self.objects.get_impl(self.next_index);
684
685 self.next_index += 1;
686
687 next.ok()
688 }
689}