pdfium_render/pdf/document/page/
annotations.rs1use crate::bindgen::{FPDF_ANNOTATION, FPDF_DOCUMENT, FPDF_FORMHANDLE, FPDF_PAGE};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::error::{PdfiumError, PdfiumInternalError};
7use crate::pdf::color::PdfColor;
8use crate::pdf::document::page::annotation::free_text::PdfPageFreeTextAnnotation;
9use crate::pdf::document::page::annotation::highlight::PdfPageHighlightAnnotation;
10use crate::pdf::document::page::annotation::ink::PdfPageInkAnnotation;
11use crate::pdf::document::page::annotation::link::PdfPageLinkAnnotation;
12use crate::pdf::document::page::annotation::popup::PdfPagePopupAnnotation;
13use crate::pdf::document::page::annotation::private::internal::PdfPageAnnotationPrivate;
14use crate::pdf::document::page::annotation::square::PdfPageSquareAnnotation;
15use crate::pdf::document::page::annotation::squiggly::PdfPageSquigglyAnnotation;
16use crate::pdf::document::page::annotation::stamp::PdfPageStampAnnotation;
17use crate::pdf::document::page::annotation::strikeout::PdfPageStrikeoutAnnotation;
18use crate::pdf::document::page::annotation::text::PdfPageTextAnnotation;
19use crate::pdf::document::page::annotation::underline::PdfPageUnderlineAnnotation;
20use crate::pdf::document::page::annotation::{
21 PdfPageAnnotation, PdfPageAnnotationCommon, PdfPageAnnotationType,
22};
23use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
24use crate::pdf::document::page::{PdfPage, PdfPageContentRegenerationStrategy, PdfPageIndexCache};
25use crate::pdf::quad_points::PdfQuadPoints;
26use chrono::prelude::*;
27use std::ops::Range;
28use std::os::raw::c_int;
29
30pub type PdfPageAnnotationIndex = usize;
33
34pub struct PdfPageAnnotations<'a> {
36 document_handle: FPDF_DOCUMENT,
37 page_handle: FPDF_PAGE,
38 form_handle: Option<FPDF_FORMHANDLE>,
39 bindings: &'a dyn PdfiumLibraryBindings,
40}
41
42impl<'a> PdfPageAnnotations<'a> {
43 #[inline]
44 pub(crate) fn from_pdfium(
45 document_handle: FPDF_DOCUMENT,
46 page_handle: FPDF_PAGE,
47 form_handle: Option<FPDF_FORMHANDLE>,
48 bindings: &'a dyn PdfiumLibraryBindings,
49 ) -> Self {
50 PdfPageAnnotations {
51 document_handle,
52 page_handle,
53 form_handle,
54 bindings,
55 }
56 }
57
58 #[inline]
61 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
62 self.document_handle
63 }
64
65 #[inline]
68 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
69 self.page_handle
70 }
71
72 #[inline]
74 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
75 self.bindings
76 }
77
78 #[inline]
80 pub fn len(&self) -> PdfPageAnnotationIndex {
81 self.bindings().FPDFPage_GetAnnotCount(self.page_handle) as PdfPageAnnotationIndex
82 }
83
84 #[inline]
86 pub fn is_empty(&self) -> bool {
87 self.len() == 0
88 }
89
90 #[inline]
92 pub fn as_range(&self) -> Range<PdfPageAnnotationIndex> {
93 0..self.len()
94 }
95
96 pub fn get(&self, index: PdfPageAnnotationIndex) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
98 if index >= self.len() {
99 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
100 }
101
102 let annotation_handle = self
103 .bindings()
104 .FPDFPage_GetAnnot(self.page_handle, index as c_int);
105
106 if annotation_handle.is_null() {
107 Err(PdfiumError::PdfiumLibraryInternalError(
108 PdfiumInternalError::Unknown,
109 ))
110 } else {
111 Ok(PdfPageAnnotation::from_pdfium(
112 self.document_handle,
113 self.page_handle,
114 annotation_handle,
115 self.form_handle,
116 self.bindings,
117 ))
118 }
119 }
120
121 #[inline]
123 pub fn first(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
124 if !self.is_empty() {
125 self.get(0)
126 } else {
127 Err(PdfiumError::NoAnnotationsInCollection)
128 }
129 }
130
131 #[inline]
133 pub fn last(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
134 if !self.is_empty() {
135 self.get(self.len() - 1)
136 } else {
137 Err(PdfiumError::NoAnnotationsInCollection)
138 }
139 }
140
141 #[inline]
143 pub fn iter(&self) -> PdfPageAnnotationsIterator {
144 PdfPageAnnotationsIterator::new(self)
145 }
146
147 pub(crate) fn create_annotation<T: PdfPageAnnotationCommon>(
154 &mut self,
155 annotation_type: PdfPageAnnotationType,
156 constructor: fn(
157 FPDF_DOCUMENT,
158 FPDF_PAGE,
159 FPDF_ANNOTATION,
160 &'a dyn PdfiumLibraryBindings,
161 ) -> T,
162 ) -> Result<T, PdfiumError> {
163 let handle = self
164 .bindings()
165 .FPDFPage_CreateAnnot(self.page_handle(), annotation_type.as_pdfium());
166
167 if handle.is_null() {
168 Err(PdfiumError::PdfiumLibraryInternalError(
169 PdfiumInternalError::Unknown,
170 ))
171 } else {
172 let mut annotation = constructor(
173 self.document_handle(),
174 self.page_handle(),
175 handle,
176 self.bindings(),
177 );
178
179 annotation
180 .set_creation_date(Utc::now())
181 .and_then(|()| {
182 if let Some(content_regeneration_strategy) =
183 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
184 self.document_handle(),
185 self.page_handle(),
186 )
187 {
188 if content_regeneration_strategy
189 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
190 {
191 PdfPage::regenerate_content_immut_for_handle(
192 self.page_handle(),
193 self.bindings(),
194 )
195 } else {
196 Ok(())
197 }
198 } else {
199 Err(PdfiumError::SourcePageIndexNotInCache)
200 }
201 })
202 .map(|()| annotation)
203 }
204 }
205
206 #[inline]
213 pub fn create_free_text_annotation(
214 &mut self,
215 text: &str,
216 ) -> Result<PdfPageFreeTextAnnotation<'a>, PdfiumError> {
217 let mut annotation = self.create_annotation(
218 PdfPageAnnotationType::FreeText,
219 PdfPageFreeTextAnnotation::from_pdfium,
220 )?;
221
222 annotation.set_contents(text)?;
223
224 Ok(annotation)
225 }
226
227 #[inline]
234 pub fn create_highlight_annotation(
235 &mut self,
236 ) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
237 self.create_annotation(
238 PdfPageAnnotationType::Highlight,
239 PdfPageHighlightAnnotation::from_pdfium,
240 )
241 }
242
243 #[inline]
250 pub fn create_ink_annotation(&mut self) -> Result<PdfPageInkAnnotation<'a>, PdfiumError> {
251 self.create_annotation(
252 PdfPageAnnotationType::Ink,
253 PdfPageInkAnnotation::from_pdfium,
254 )
255 }
256
257 pub fn create_link_annotation(
264 &mut self,
265 uri: &str,
266 ) -> Result<PdfPageLinkAnnotation<'a>, PdfiumError> {
267 let mut annotation = self.create_annotation(
268 PdfPageAnnotationType::Link,
269 PdfPageLinkAnnotation::from_pdfium,
270 )?;
271
272 annotation.set_link(uri)?;
273
274 Ok(annotation)
275 }
276
277 #[inline]
284 pub fn create_popup_annotation(&mut self) -> Result<PdfPagePopupAnnotation<'a>, PdfiumError> {
285 self.create_annotation(
286 PdfPageAnnotationType::Popup,
287 PdfPagePopupAnnotation::from_pdfium,
288 )
289 }
290
291 #[inline]
298 pub fn create_square_annotation(&mut self) -> Result<PdfPageSquareAnnotation<'a>, PdfiumError> {
299 self.create_annotation(
300 PdfPageAnnotationType::Square,
301 PdfPageSquareAnnotation::from_pdfium,
302 )
303 }
304
305 #[inline]
312 pub fn create_squiggly_annotation(
313 &mut self,
314 ) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
315 self.create_annotation(
316 PdfPageAnnotationType::Squiggly,
317 PdfPageSquigglyAnnotation::from_pdfium,
318 )
319 }
320
321 #[inline]
328 pub fn create_stamp_annotation(&mut self) -> Result<PdfPageStampAnnotation<'a>, PdfiumError> {
329 self.create_annotation(
330 PdfPageAnnotationType::Stamp,
331 PdfPageStampAnnotation::from_pdfium,
332 )
333 }
334
335 #[inline]
342 pub fn create_strikeout_annotation(
343 &mut self,
344 ) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
345 self.create_annotation(
346 PdfPageAnnotationType::Strikeout,
347 PdfPageStrikeoutAnnotation::from_pdfium,
348 )
349 }
350
351 #[inline]
358 pub fn create_text_annotation(
359 &mut self,
360 text: &str,
361 ) -> Result<PdfPageTextAnnotation<'a>, PdfiumError> {
362 let mut annotation = self.create_annotation(
363 PdfPageAnnotationType::Text,
364 PdfPageTextAnnotation::from_pdfium,
365 )?;
366
367 annotation.set_contents(text)?;
368
369 Ok(annotation)
370 }
371
372 #[inline]
379 pub fn create_underline_annotation(
380 &mut self,
381 ) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
382 self.create_annotation(
383 PdfPageAnnotationType::Underline,
384 PdfPageUnderlineAnnotation::from_pdfium,
385 )
386 }
387
388 #[inline]
402 pub fn create_squiggly_annotation_under_object(
403 &mut self,
404 object: &PdfPageObject,
405 color: PdfColor,
406 contents: Option<&str>,
407 ) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
408 let mut annotation = self.create_squiggly_annotation()?;
409
410 let bounds = object.bounds()?;
413
414 annotation.set_position(bounds.left(), bounds.bottom())?;
415 annotation.set_stroke_color(color)?;
416
417 const SQUIGGLY_HEIGHT: f32 = 12.0;
418
419 let annotation_top = bounds.bottom().value - 5.0;
420 let annotation_bottom = annotation_top - SQUIGGLY_HEIGHT;
421
422 annotation
423 .attachment_points_mut()
424 .create_attachment_point_at_end(PdfQuadPoints::new_from_values(
425 bounds.left().value,
426 annotation_bottom,
427 bounds.right().value,
428 annotation_bottom,
429 bounds.right().value,
430 annotation_top,
431 bounds.left().value,
432 annotation_top,
433 ))?;
434
435 if let Some(contents) = contents {
436 annotation.set_width(bounds.width())?;
437 annotation.set_height(bounds.height())?;
438 annotation.set_contents(contents)?;
439 }
440
441 Ok(annotation)
442 }
443
444 #[inline]
455 pub fn create_underline_annotation_under_object(
456 &mut self,
457 object: &PdfPageObject,
458 color: PdfColor,
459 contents: Option<&str>,
460 ) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
461 let mut annotation = self.create_underline_annotation()?;
462
463 let bounds = object.bounds()?;
466
467 annotation.set_position(bounds.left(), bounds.bottom())?;
468 annotation.set_stroke_color(color)?;
469 annotation
470 .attachment_points_mut()
471 .create_attachment_point_at_end(bounds)?;
472
473 if let Some(contents) = contents {
474 annotation.set_width(bounds.width())?;
475 annotation.set_height(bounds.height())?;
476 annotation.set_contents(contents)?;
477 }
478
479 Ok(annotation)
480 }
481
482 #[inline]
493 pub fn create_strikeout_annotation_through_object(
494 &mut self,
495 object: &PdfPageObject,
496 color: PdfColor,
497 contents: Option<&str>,
498 ) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
499 let mut annotation = self.create_strikeout_annotation()?;
500
501 let bounds = object.bounds()?;
504
505 annotation.set_position(bounds.left(), bounds.bottom())?;
506 annotation.set_stroke_color(color)?;
507 annotation
508 .attachment_points_mut()
509 .create_attachment_point_at_end(bounds)?;
510
511 if let Some(contents) = contents {
512 annotation.set_width(bounds.width())?;
513 annotation.set_height(bounds.height())?;
514 annotation.set_contents(contents)?;
515 }
516
517 Ok(annotation)
518 }
519
520 #[inline]
531 pub fn create_highlight_annotation_over_object(
532 &mut self,
533 object: &PdfPageObject,
534 color: PdfColor,
535 contents: Option<&str>,
536 ) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
537 let mut annotation = self.create_highlight_annotation()?;
538
539 let bounds = object.bounds()?;
542
543 annotation.set_position(bounds.left(), bounds.bottom())?;
544 annotation.set_stroke_color(color)?;
545 annotation
546 .attachment_points_mut()
547 .create_attachment_point_at_end(bounds)?;
548
549 if let Some(contents) = contents {
550 annotation.set_width(bounds.width())?;
551 annotation.set_height(bounds.height())?;
552 annotation.set_contents(contents)?;
553 }
554
555 Ok(annotation)
556 }
557
558 pub fn delete_annotation(
565 &mut self,
566 annotation: PdfPageAnnotation<'a>,
567 ) -> Result<(), PdfiumError> {
568 let index = self
569 .bindings()
570 .FPDFPage_GetAnnotIndex(self.page_handle(), annotation.handle());
571
572 if index == -1 {
573 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
574 }
575
576 if self.bindings().is_true(
577 self.bindings()
578 .FPDFPage_RemoveAnnot(self.page_handle(), index),
579 ) {
580 if let Some(content_regeneration_strategy) =
581 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
582 self.document_handle(),
583 self.page_handle(),
584 )
585 {
586 if content_regeneration_strategy
587 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
588 {
589 PdfPage::regenerate_content_immut_for_handle(
590 self.page_handle(),
591 self.bindings(),
592 )
593 } else {
594 Ok(())
595 }
596 } else {
597 Err(PdfiumError::SourcePageIndexNotInCache)
598 }
599 } else {
600 Err(PdfiumError::PdfiumLibraryInternalError(
601 PdfiumInternalError::Unknown,
602 ))
603 }
604 }
605}
606
607pub struct PdfPageAnnotationsIterator<'a> {
609 annotations: &'a PdfPageAnnotations<'a>,
610 next_index: PdfPageAnnotationIndex,
611}
612
613impl<'a> PdfPageAnnotationsIterator<'a> {
614 #[inline]
615 pub(crate) fn new(annotations: &'a PdfPageAnnotations<'a>) -> Self {
616 PdfPageAnnotationsIterator {
617 annotations,
618 next_index: 0,
619 }
620 }
621}
622
623impl<'a> Iterator for PdfPageAnnotationsIterator<'a> {
624 type Item = PdfPageAnnotation<'a>;
625
626 fn next(&mut self) -> Option<Self::Item> {
627 let next = self.annotations.get(self.next_index);
628
629 self.next_index += 1;
630
631 next.ok()
632 }
633}