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::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 if !self.is_empty() {
61 self.get(0)
62 } else {
63 Err(PdfiumError::NoPageObjectsInCollection)
64 }
65 }
66
67 #[inline]
69 fn last(&self) -> Result<PdfPageObject<'a>, PdfiumError> {
70 if !self.is_empty() {
71 self.get(self.len() - 1)
72 } else {
73 Err(PdfiumError::NoPageObjectsInCollection)
74 }
75 }
76
77 fn iter(&'a self) -> PdfPageObjectsIterator<'a>;
79
80 fn bounds(&'a self) -> PdfRect {
83 let mut bottom: f32 = 0.0;
84 let mut top: f32 = 0.0;
85 let mut left: f32 = 0.0;
86 let mut right: f32 = 0.0;
87
88 for object in self.iter() {
89 if let Ok(bounds) = object.bounds() {
90 bottom = bottom.min(bounds.bottom().value);
91 top = top.max(bounds.top().value);
92 left = left.min(bounds.left().value);
93 right = right.max(bounds.right().value);
94 }
95 }
96
97 PdfRect::new_from_values(bottom, left, top, right)
98 }
99
100 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
108
109 #[inline]
116 fn add_text_object(&mut self, object: PdfPageTextObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
117 self.add_object(PdfPageObject::Text(object))
118 }
119
120 fn create_text_object(
128 &mut self,
129 x: PdfPoints,
130 y: PdfPoints,
131 text: impl ToString,
132 font: impl ToPdfFontToken,
133 font_size: PdfPoints,
134 ) -> Result<PdfPageObject<'a>, PdfiumError>;
135
136 #[inline]
143 fn add_path_object(&mut self, object: PdfPagePathObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
144 self.add_object(PdfPageObject::Path(object))
145 }
146
147 #[inline]
154 fn add_x_object_form_object(
155 &mut self,
156 object: PdfPageXObjectFormObject<'a>,
157 ) -> Result<PdfPageObject<'a>, PdfiumError> {
158 self.add_object(PdfPageObject::XObjectForm(object))
159 }
160
161 fn create_path_object_line(
169 &mut self,
170 x1: PdfPoints,
171 y1: PdfPoints,
172 x2: PdfPoints,
173 y2: PdfPoints,
174 stroke_color: PdfColor,
175 stroke_width: PdfPoints,
176 ) -> Result<PdfPageObject<'a>, PdfiumError>;
177
178 #[allow(clippy::too_many_arguments)]
186 fn create_path_object_bezier(
187 &mut self,
188 x1: PdfPoints,
189 y1: PdfPoints,
190 x2: PdfPoints,
191 y2: PdfPoints,
192 control1_x: PdfPoints,
193 control1_y: PdfPoints,
194 control2_x: PdfPoints,
195 control2_y: PdfPoints,
196 stroke_color: PdfColor,
197 stroke_width: PdfPoints,
198 ) -> Result<PdfPageObject<'a>, PdfiumError>;
199
200 fn create_path_object_rect(
210 &mut self,
211 rect: PdfRect,
212 stroke_color: Option<PdfColor>,
213 stroke_width: Option<PdfPoints>,
214 fill_color: Option<PdfColor>,
215 ) -> Result<PdfPageObject<'a>, PdfiumError>;
216
217 fn create_path_object_circle(
227 &mut self,
228 rect: PdfRect,
229 stroke_color: Option<PdfColor>,
230 stroke_width: Option<PdfPoints>,
231 fill_color: Option<PdfColor>,
232 ) -> Result<PdfPageObject<'a>, PdfiumError>;
233
234 fn create_path_object_circle_at(
244 &mut self,
245 center_x: PdfPoints,
246 center_y: PdfPoints,
247 radius: PdfPoints,
248 stroke_color: Option<PdfColor>,
249 stroke_width: Option<PdfPoints>,
250 fill_color: Option<PdfColor>,
251 ) -> Result<PdfPageObject<'a>, PdfiumError>;
252
253 fn create_path_object_ellipse(
263 &mut self,
264 rect: PdfRect,
265 stroke_color: Option<PdfColor>,
266 stroke_width: Option<PdfPoints>,
267 fill_color: Option<PdfColor>,
268 ) -> Result<PdfPageObject<'a>, PdfiumError>;
269
270 #[allow(clippy::too_many_arguments)]
280 fn create_path_object_ellipse_at(
281 &mut self,
282 center_x: PdfPoints,
283 center_y: PdfPoints,
284 x_radius: PdfPoints,
285 y_radius: PdfPoints,
286 stroke_color: Option<PdfColor>,
287 stroke_width: Option<PdfPoints>,
288 fill_color: Option<PdfColor>,
289 ) -> Result<PdfPageObject<'a>, PdfiumError>;
290
291 #[inline]
298 fn add_image_object(&mut self, object: PdfPageImageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
299 self.add_object(PdfPageObject::Image(object))
300 }
301
302 #[cfg(feature = "image_025")]
316 fn create_image_object(
317 &mut self,
318 x: PdfPoints,
319 y: PdfPoints,
320 image: &DynamicImage,
321 width: Option<PdfPoints>,
322 height: Option<PdfPoints>,
323 ) -> Result<PdfPageObject<'a>, PdfiumError>;
324
325 fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError>;
335
336 fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError>;
346}
347
348impl<'a, T> PdfPageObjectsCommon<'a> for T
349where
350 T: PdfPageObjectsPrivate<'a>,
351{
352 #[inline]
353 fn len(&self) -> PdfPageObjectIndex {
354 self.len_impl()
355 }
356
357 #[inline]
358 fn get(&self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
359 self.get_impl(index)
360 }
361
362 #[inline]
363 fn iter(&'a self) -> PdfPageObjectsIterator<'a> {
364 self.iter_impl()
365 }
366
367 #[inline]
368 fn add_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
369 self.add_object_impl(object)
370 }
371
372 #[inline]
373 fn create_text_object(
374 &mut self,
375 x: PdfPoints,
376 y: PdfPoints,
377 text: impl ToString,
378 font: impl ToPdfFontToken,
379 font_size: PdfPoints,
380 ) -> Result<PdfPageObject<'a>, PdfiumError> {
381 let document_handle = match self.ownership() {
382 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
383 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
384 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
385 _ => None,
386 };
387
388 if let Some(document_handle) = document_handle {
389 let mut object = PdfPageTextObject::new_from_handles(
390 document_handle,
391 text,
392 font.token().handle(),
393 font_size,
394 self.bindings(),
395 )?;
396
397 object.translate(x, y)?;
398
399 self.add_text_object(object)
400 } else {
401 Err(PdfiumError::OwnershipNotAttachedToPage)
402 }
403 }
404
405 #[inline]
406 fn create_path_object_line(
407 &mut self,
408 x1: PdfPoints,
409 y1: PdfPoints,
410 x2: PdfPoints,
411 y2: PdfPoints,
412 stroke_color: PdfColor,
413 stroke_width: PdfPoints,
414 ) -> Result<PdfPageObject<'a>, PdfiumError> {
415 let object =
416 PdfPagePathObject::new_line_from_bindings(self.bindings(), x1, y1, x2, y2, stroke_color, stroke_width)?;
417
418 self.add_path_object(object)
419 }
420
421 #[inline]
422 fn create_path_object_bezier(
423 &mut self,
424 x1: PdfPoints,
425 y1: PdfPoints,
426 x2: PdfPoints,
427 y2: PdfPoints,
428 control1_x: PdfPoints,
429 control1_y: PdfPoints,
430 control2_x: PdfPoints,
431 control2_y: PdfPoints,
432 stroke_color: PdfColor,
433 stroke_width: PdfPoints,
434 ) -> Result<PdfPageObject<'a>, PdfiumError> {
435 let object = PdfPagePathObject::new_bezier_from_bindings(
436 self.bindings(),
437 x1,
438 y1,
439 x2,
440 y2,
441 control1_x,
442 control1_y,
443 control2_x,
444 control2_y,
445 stroke_color,
446 stroke_width,
447 )?;
448
449 self.add_path_object(object)
450 }
451
452 #[inline]
453 fn create_path_object_rect(
454 &mut self,
455 rect: PdfRect,
456 stroke_color: Option<PdfColor>,
457 stroke_width: Option<PdfPoints>,
458 fill_color: Option<PdfColor>,
459 ) -> Result<PdfPageObject<'a>, PdfiumError> {
460 let object =
461 PdfPagePathObject::new_rect_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
462
463 self.add_path_object(object)
464 }
465
466 #[inline]
467 fn create_path_object_circle(
468 &mut self,
469 rect: PdfRect,
470 stroke_color: Option<PdfColor>,
471 stroke_width: Option<PdfPoints>,
472 fill_color: Option<PdfColor>,
473 ) -> Result<PdfPageObject<'a>, PdfiumError> {
474 let object =
475 PdfPagePathObject::new_circle_from_bindings(self.bindings(), rect, stroke_color, stroke_width, fill_color)?;
476
477 self.add_path_object(object)
478 }
479
480 #[inline]
481 fn create_path_object_circle_at(
482 &mut self,
483 center_x: PdfPoints,
484 center_y: PdfPoints,
485 radius: PdfPoints,
486 stroke_color: Option<PdfColor>,
487 stroke_width: Option<PdfPoints>,
488 fill_color: Option<PdfColor>,
489 ) -> Result<PdfPageObject<'a>, PdfiumError> {
490 let object = PdfPagePathObject::new_circle_at_from_bindings(
491 self.bindings(),
492 center_x,
493 center_y,
494 radius,
495 stroke_color,
496 stroke_width,
497 fill_color,
498 )?;
499
500 self.add_path_object(object)
501 }
502
503 #[inline]
504 fn create_path_object_ellipse(
505 &mut self,
506 rect: PdfRect,
507 stroke_color: Option<PdfColor>,
508 stroke_width: Option<PdfPoints>,
509 fill_color: Option<PdfColor>,
510 ) -> Result<PdfPageObject<'a>, PdfiumError> {
511 let object = PdfPagePathObject::new_ellipse_from_bindings(
512 self.bindings(),
513 rect,
514 stroke_color,
515 stroke_width,
516 fill_color,
517 )?;
518
519 self.add_path_object(object)
520 }
521
522 #[inline]
523 fn create_path_object_ellipse_at(
524 &mut self,
525 center_x: PdfPoints,
526 center_y: PdfPoints,
527 x_radius: PdfPoints,
528 y_radius: PdfPoints,
529 stroke_color: Option<PdfColor>,
530 stroke_width: Option<PdfPoints>,
531 fill_color: Option<PdfColor>,
532 ) -> Result<PdfPageObject<'a>, PdfiumError> {
533 let object = PdfPagePathObject::new_ellipse_at_from_bindings(
534 self.bindings(),
535 center_x,
536 center_y,
537 x_radius,
538 y_radius,
539 stroke_color,
540 stroke_width,
541 fill_color,
542 )?;
543
544 self.add_path_object(object)
545 }
546
547 #[cfg(feature = "image_025")]
548 fn create_image_object(
549 &mut self,
550 x: PdfPoints,
551 y: PdfPoints,
552 image: &DynamicImage,
553 width: Option<PdfPoints>,
554 height: Option<PdfPoints>,
555 ) -> Result<PdfPageObject<'a>, PdfiumError> {
556 let document_handle = match self.ownership() {
557 PdfPageObjectOwnership::Page(ownership) => Some(ownership.document_handle()),
558 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.document_handle()),
559 PdfPageObjectOwnership::UnattachedAnnotation(ownership) => Some(ownership.document_handle()),
560 _ => None,
561 };
562
563 if let Some(document_handle) = document_handle {
564 let image_width = image.width();
565
566 let image_height = image.height();
567
568 let mut object = PdfPageImageObject::new_from_handle(document_handle, self.bindings())?;
569
570 object.set_image(image)?;
571
572 match (width, height) {
573 (Some(width), Some(height)) => {
574 object.scale(width.value, height.value)?;
575 }
576 (Some(width), None) => {
577 let aspect_ratio = image_height as f32 / image_width as f32;
578
579 let height = width * aspect_ratio;
580
581 object.scale(width.value, height.value)?;
582 }
583 (None, Some(height)) => {
584 let aspect_ratio = image_height as f32 / image_width as f32;
585
586 let width = height / aspect_ratio;
587
588 object.scale(width.value, height.value)?;
589 }
590 (None, None) => {}
591 }
592
593 object.translate(x, y)?;
594
595 self.add_image_object(object)
596 } else {
597 Err(PdfiumError::OwnershipNotAttachedToPage)
598 }
599 }
600
601 #[inline]
602 fn remove_object(&mut self, object: PdfPageObject<'a>) -> Result<PdfPageObject<'a>, PdfiumError> {
603 self.remove_object_impl(object)
604 }
605
606 fn remove_object_at_index(&mut self, index: PdfPageObjectIndex) -> Result<PdfPageObject<'a>, PdfiumError> {
607 if index >= self.len() {
608 return Err(PdfiumError::PageObjectIndexOutOfBounds);
609 }
610
611 if let Ok(object) = self.get(index) {
612 self.remove_object(object)
613 } else {
614 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
615 }
616 }
617}
618
619pub struct PdfPageObjectsIterator<'a> {
621 objects: &'a dyn PdfPageObjectsPrivate<'a>,
622 next_index: PdfPageObjectIndex,
623}
624
625impl<'a> PdfPageObjectsIterator<'a> {
626 #[inline]
627 pub(crate) fn new(objects: &'a dyn PdfPageObjectsPrivate<'a>) -> Self {
628 PdfPageObjectsIterator { objects, next_index: 0 }
629 }
630}
631
632impl<'a> Iterator for PdfPageObjectsIterator<'a> {
633 type Item = PdfPageObject<'a>;
634
635 fn next(&mut self) -> Option<Self::Item> {
636 let next = self.objects.get_impl(self.next_index);
637
638 self.next_index += 1;
639
640 next.ok()
641 }
642}