1use crate::error::{PdfiumError, PdfiumInternalError};
5use crate::pdf::color::PdfColor;
6use crate::pdf::document::fonts::ToPdfFontToken;
7use crate::pdf::document::page::PdfPageObjectOwnership;
8use crate::pdf::document::page::object::image::PdfPageImageObject;
9use crate::pdf::document::page::object::path::{BezierPoints, PathFillStroke, PathStroke, PdfPagePathObject};
10use crate::pdf::document::page::object::text::PdfPageTextObject;
11use crate::pdf::document::page::object::x_object_form::PdfPageXObjectFormObject;
12use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
13use crate::pdf::document::page::objects::private::internal::PdfPageObjectsPrivate;
14use crate::pdf::points::PdfPoints;
15use crate::pdf::rect::PdfRect;
16use std::ops::{Range, RangeInclusive};
17
18#[cfg(feature = "image_025")]
19use image_025::DynamicImage;
20
21#[cfg(doc)]
22use {
23 crate::pdf::document::page::PdfPage, crate::pdf::document::page::PdfPageContentRegenerationStrategy,
24 crate::pdf::document::page::PdfPageObjects,
25};
26
27pub type PdfPageObjectIndex = usize;
29
30pub trait PdfPageObjectsCommon<'a> {
33 fn len(&self) -> PdfPageObjectIndex;
35
36 #[inline]
38 fn is_empty(&self) -> bool {
39 self.len() == 0
40 }
41
42 #[inline]
44 fn as_range(&self) -> Range<PdfPageObjectIndex> {
45 0..self.len()
46 }
47
48 #[inline]
50 fn as_range_inclusive(&self) -> RangeInclusive<PdfPageObjectIndex> {
51 if self.is_empty() { 0..=0 } else { 0..=(self.len() - 1) }
52 }
53
54 fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
56
57 #[inline]
59 fn first(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
60 first_of(self)
61 }
62
63 #[inline]
65 fn last(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
66 last_of(self)
67 }
68
69 fn iter(&'a self) -> PdfPageObjectsIterator<'a>;
71
72 fn bounds(&'a self) -> PdfRect {
75 bounds_of(self)
76 }
77
78 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
86
87 #[inline]
94 fn add_text_object(&mut self, object: PdfPageTextObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
95 self.add_object(PdfPageObject::Text(object))
96 }
97
98 fn create_text_object(
106 &mut self,
107 x: PdfPoints,
108 y: PdfPoints,
109 text: impl ToString,
110 font: impl ToPdfFontToken,
111 font_size: PdfPoints,
112 ) -> Result<PdfPageObject<'a>, PdfiumError>;
113
114 #[inline]
121 fn add_path_object(&mut self, object: PdfPagePathObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
122 self.add_object(PdfPageObject::Path(object))
123 }
124
125 #[inline]
132 fn add_x_object_form_object(
133 &mut self,
134 object: PdfPageXObjectFormObject<'a>,
135 ) -> Result<PdfPageObject<'a>, PdfiumError> {
136 self.add_object(PdfPageObject::XObjectForm(object))
137 }
138
139 fn create_path_object_line(
147 &mut self,
148 x1: PdfPoints,
149 y1: PdfPoints,
150 x2: PdfPoints,
151 y2: PdfPoints,
152 stroke_color: PdfColor,
153 stroke_width: PdfPoints,
154 ) -> Result<PdfPageObject<'a>, PdfiumError>;
155
156 #[allow(clippy::too_many_arguments)]
164 fn create_path_object_bezier(
165 &mut self,
166 x1: PdfPoints,
167 y1: PdfPoints,
168 x2: PdfPoints,
169 y2: PdfPoints,
170 control1_x: PdfPoints,
171 control1_y: PdfPoints,
172 control2_x: PdfPoints,
173 control2_y: PdfPoints,
174 stroke_color: PdfColor,
175 stroke_width: PdfPoints,
176 ) -> Result<PdfPageObject<'a>, PdfiumError>;
177
178 fn create_path_object_rect(
188 &mut self,
189 rect: PdfRect,
190 stroke_color: Option<PdfColor>,
191 stroke_width: Option<PdfPoints>,
192 fill_color: Option<PdfColor>,
193 ) -> Result<PdfPageObject<'a>, PdfiumError>;
194
195 fn create_path_object_circle(
205 &mut self,
206 rect: PdfRect,
207 stroke_color: Option<PdfColor>,
208 stroke_width: Option<PdfPoints>,
209 fill_color: Option<PdfColor>,
210 ) -> Result<PdfPageObject<'a>, PdfiumError>;
211
212 fn create_path_object_circle_at(
222 &mut self,
223 center_x: PdfPoints,
224 center_y: PdfPoints,
225 radius: PdfPoints,
226 stroke_color: Option<PdfColor>,
227 stroke_width: Option<PdfPoints>,
228 fill_color: Option<PdfColor>,
229 ) -> Result<PdfPageObject<'a>, PdfiumError>;
230
231 fn create_path_object_ellipse(
241 &mut self,
242 rect: PdfRect,
243 stroke_color: Option<PdfColor>,
244 stroke_width: Option<PdfPoints>,
245 fill_color: Option<PdfColor>,
246 ) -> Result<PdfPageObject<'a>, PdfiumError>;
247
248 #[allow(clippy::too_many_arguments)]
258 fn create_path_object_ellipse_at(
259 &mut self,
260 center_x: PdfPoints,
261 center_y: PdfPoints,
262 x_radius: PdfPoints,
263 y_radius: PdfPoints,
264 stroke_color: Option<PdfColor>,
265 stroke_width: Option<PdfPoints>,
266 fill_color: Option<PdfColor>,
267 ) -> Result<PdfPageObject<'a>, PdfiumError>;
268
269 #[inline]
276 fn add_image_object(&mut self, object: PdfPageImageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
277 self.add_object(PdfPageObject::Image(object))
278 }
279
280 #[cfg(feature = "image_025")]
294 fn create_image_object(
295 &mut self,
296 x: PdfPoints,
297 y: PdfPoints,
298 image: &DynamicImage,
299 width: Option<PdfPoints>,
300 height: Option<PdfPoints>,
301 ) -> Result<PdfPageObject<'a>, PdfiumError>;
302
303 fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
313
314 fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
324}
325
326impl<'a, T> PdfPageObjectsCommon<'a> for T
327where
328 T: PdfPageObjectsPrivate<'a>,
329{
330 #[inline]
331 fn len(&self) -> PdfPageObjectIndex {
332 self.len_impl()
333 }
334
335 #[inline]
336 fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
337 self.get_impl(index)
338 }
339
340 #[inline]
341 fn iter(&'a self) -> PdfPageObjectsIterator<'a> {
342 self.iter_impl()
343 }
344
345 #[inline]
346 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
347 self.add_object_impl(object)
348 }
349
350 #[inline]
351 fn create_text_object(
352 &mut self,
353 x: PdfPoints,
354 y: PdfPoints,
355 text: impl ToString,
356 font: impl ToPdfFontToken,
357 font_size: PdfPoints,
358 ) -> Result<PdfPageObject<'a>, PdfiumError> {
359 let document_handle = match self.ownership() {
360 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
361 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
362 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
363 _ => None,
364 };
365
366 if let Some(document_handle) = document_handle {
367 let mut object = PdfPageTextObject::new_from_handles(
368 document_handle,
369 text,
370 font.token().handle(),
371 font_size,
372 self.bindings(),
373 )?;
374
375 object.translate(x, y)?;
376
377 self.add_text_object(object)
378 } else {
379 Err(PdfiumError::OwnershipNotAttachedToPage)
380 }
381 }
382
383 #[inline]
384 fn create_path_object_line(
385 &mut self,
386 x1: PdfPoints,
387 y1: PdfPoints,
388 x2: PdfPoints,
389 y2: PdfPoints,
390 stroke_color: PdfColor,
391 stroke_width: PdfPoints,
392 ) -> Result<PdfPageObject<'a>, PdfiumError> {
393 let object = PdfPagePathObject::new_line_from_bindings(
394 self.bindings(),
395 x1,
396 y1,
397 x2,
398 y2,
399 PathStroke {
400 color: stroke_color,
401 width: stroke_width,
402 },
403 )?;
404
405 self.add_path_object(object)
406 }
407
408 #[inline]
409 fn create_path_object_bezier(
410 &mut self,
411 x1: PdfPoints,
412 y1: PdfPoints,
413 x2: PdfPoints,
414 y2: PdfPoints,
415 control1_x: PdfPoints,
416 control1_y: PdfPoints,
417 control2_x: PdfPoints,
418 control2_y: PdfPoints,
419 stroke_color: PdfColor,
420 stroke_width: PdfPoints,
421 ) -> Result<PdfPageObject<'a>, PdfiumError> {
422 let object = PdfPagePathObject::new_bezier_from_bindings(
423 self.bindings(),
424 BezierPoints {
425 start: (x1, y1),
426 end: (x2, y2),
427 control1: (control1_x, control1_y),
428 control2: (control2_x, control2_y),
429 },
430 PathStroke {
431 color: stroke_color,
432 width: stroke_width,
433 },
434 )?;
435
436 self.add_path_object(object)
437 }
438
439 #[inline]
440 fn create_path_object_rect(
441 &mut self,
442 rect: PdfRect,
443 stroke_color: Option<PdfColor>,
444 stroke_width: Option<PdfPoints>,
445 fill_color: Option<PdfColor>,
446 ) -> Result<PdfPageObject<'a>, PdfiumError> {
447 let object =
448 PdfPagePathObject::new_rect_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
449
450 self.add_path_object(object)
451 }
452
453 #[inline]
454 fn create_path_object_circle(
455 &mut self,
456 rect: PdfRect,
457 stroke_color: Option<PdfColor>,
458 stroke_width: Option<PdfPoints>,
459 fill_color: Option<PdfColor>,
460 ) -> Result<PdfPageObject<'a>, PdfiumError> {
461 let object =
462 PdfPagePathObject::new_circle_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
463
464 self.add_path_object(object)
465 }
466
467 #[inline]
468 fn create_path_object_circle_at(
469 &mut self,
470 center_x: PdfPoints,
471 center_y: PdfPoints,
472 radius: PdfPoints,
473 stroke_color: Option<PdfColor>,
474 stroke_width: Option<PdfPoints>,
475 fill_color: Option<PdfColor>,
476 ) -> Result<PdfPageObject<'a>, PdfiumError> {
477 let object = PdfPagePathObject::new_circle_at_from_bindings(
478 self.bindings(),
479 center_x,
480 center_y,
481 radius,
482 PathFillStroke {
483 stroke_color,
484 stroke_width,
485 fill_color,
486 },
487 )?;
488
489 self.add_path_object(object)
490 }
491
492 #[inline]
493 fn create_path_object_ellipse(
494 &mut self,
495 rect: PdfRect,
496 stroke_color: Option<PdfColor>,
497 stroke_width: Option<PdfPoints>,
498 fill_color: Option<PdfColor>,
499 ) -> Result<PdfPageObject<'a>, PdfiumError> {
500 let object = PdfPagePathObject::new_ellipse_from_bindings(
501 self.bindings(),
502 rect,
503 stroke_color,
504 stroke_width,
505 fill_color,
506 )?;
507
508 self.add_path_object(object)
509 }
510
511 #[inline]
512 fn create_path_object_ellipse_at(
513 &mut self,
514 center_x: PdfPoints,
515 center_y: PdfPoints,
516 x_radius: PdfPoints,
517 y_radius: PdfPoints,
518 stroke_color: Option<PdfColor>,
519 stroke_width: Option<PdfPoints>,
520 fill_color: Option<PdfColor>,
521 ) -> Result<PdfPageObject<'a>, PdfiumError> {
522 let object = PdfPagePathObject::new_ellipse_at_from_bindings(
523 self.bindings(),
524 center_x,
525 center_y,
526 x_radius,
527 y_radius,
528 PathFillStroke {
529 stroke_color,
530 stroke_width,
531 fill_color,
532 },
533 )?;
534
535 self.add_path_object(object)
536 }
537
538 #[cfg(feature = "image_025")]
539 fn create_image_object(
540 &mut self,
541 x: PdfPoints,
542 y: PdfPoints,
543 image: &DynamicImage,
544 width: Option<PdfPoints>,
545 height: Option<PdfPoints>,
546 ) -> Result<PdfPageObject<'a>, PdfiumError> {
547 let document_handle = match self.ownership() {
548 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
549 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
550 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
551 _ => None,
552 };
553
554 if let Some(document_handle) = document_handle {
555 let image_width = image.width();
556
557 let image_height = image.height();
558
559 let mut object = PdfPageImageObject::new_from_handle(document_handle, self.bindings())?;
560
561 object.set_image(image)?;
562
563 match (width, height) {
564 (Some(width), Some(height)) => {
565 object.scale(width.value, height.value)?;
566 }
567 (Some(width), None) => {
568 let aspect_ratio = image_height as f32 / image_width as f32;
569
570 let height = width * aspect_ratio;
571
572 object.scale(width.value, height.value)?;
573 }
574 (None, Some(height)) => {
575 let aspect_ratio = image_height as f32 / image_width as f32;
576
577 let width = height / aspect_ratio;
578
579 object.scale(width.value, height.value)?;
580 }
581 (None, None) => {}
582 }
583
584 object.translate(x, y)?;
585
586 self.add_image_object(object)
587 } else {
588 Err(PdfiumError::OwnershipNotAttachedToPage)
589 }
590 }
591
592 #[inline]
593 fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
594 self.remove_object_impl(object)
595 }
596
597 fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
598 if index >= self.len() {
599 return Err(PdfiumError::PageObjectIndexOutOfBounds);
600 }
601
602 if let Ok(object) = self.get(index) {
603 self.remove_object(object)
604 } else {
605 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
606 }
607 }
608}
609
610fn first_of<'a, T>(objects: &T) -> Result<PdfPageObject<'a>, PdfiumError>
611where
612 T: PdfPageObjectsCommon<'a> + ?Sized,
613{
614 if !objects.is_empty() {
615 objects.get(0)
616 } else {
617 Err(PdfiumError::NoPageObjectsInCollection)
618 }
619}
620
621fn last_of<'a, T>(objects: &T) -> Result<PdfPageObject<'a>, PdfiumError>
622where
623 T: PdfPageObjectsCommon<'a> + ?Sized,
624{
625 if !objects.is_empty() {
626 objects.get(objects.len() - 1)
627 } else {
628 Err(PdfiumError::NoPageObjectsInCollection)
629 }
630}
631
632fn bounds_of<'a, T>(objects: &'a T) -> PdfRect
633where
634 T: PdfPageObjectsCommon<'a> + ?Sized,
635{
636 let mut bottom: f32 = 0.0;
637 let mut top: f32 = 0.0;
638 let mut left: f32 = 0.0;
639 let mut right: f32 = 0.0;
640
641 for object in objects.iter() {
642 if let Ok(bounds) = object.bounds() {
643 bottom = bottom.min(bounds.bottom().value);
644 top = top.max(bounds.top().value);
645 left = left.min(bounds.left().value);
646 right = right.max(bounds.right().value);
647 }
648 }
649
650 PdfRect::new_from_values(bottom, left, top, right)
651}
652
653pub struct PdfPageObjectsIterator<'a> {
655 objects: &'a dyn PdfPageObjectsPrivate<'a>,
656 next_index: PdfPageObjectIndex,
657}
658
659impl<'a> PdfPageObjectsIterator<'a> {
660 #[inline]
661 pub(crate) fn new(objects: &'a dyn PdfPageObjectsPrivate<'a>) -> Self {
662 PdfPageObjectsIterator { objects, next_index: 0 }
663 }
664}
665
666impl<'a> Iterator for PdfPageObjectsIterator<'a> {
667 type Item = PdfPageObject<'a>;
668
669 fn next(&mut self) -> Option<Self::Item> {
670 let next = self.objects.get_impl(self.next_index);
671
672 self.next_index += 1;
673
674 next.ok()
675 }
676}