pdfium_render/pdf/document/page/
annotations.rs1use crate::bindgen::{FPDF_ANNOTATION, FPDF_DOCUMENT, FPDF_FORMHANDLE, FPDF_PAGE};
5use crate::error::{PdfiumError, PdfiumInternalError};
6use crate::pdf::color::PdfColor;
7use crate::pdf::document::page::annotation::free_text::PdfPageFreeTextAnnotation;
8use crate::pdf::document::page::annotation::highlight::PdfPageHighlightAnnotation;
9use crate::pdf::document::page::annotation::ink::PdfPageInkAnnotation;
10use crate::pdf::document::page::annotation::link::PdfPageLinkAnnotation;
11use crate::pdf::document::page::annotation::popup::PdfPagePopupAnnotation;
12use crate::pdf::document::page::annotation::private::internal::PdfPageAnnotationPrivate;
13use crate::pdf::document::page::annotation::square::PdfPageSquareAnnotation;
14use crate::pdf::document::page::annotation::squiggly::PdfPageSquigglyAnnotation;
15use crate::pdf::document::page::annotation::stamp::PdfPageStampAnnotation;
16use crate::pdf::document::page::annotation::strikeout::PdfPageStrikeoutAnnotation;
17use crate::pdf::document::page::annotation::text::PdfPageTextAnnotation;
18use crate::pdf::document::page::annotation::underline::PdfPageUnderlineAnnotation;
19use crate::pdf::document::page::annotation::{
20 PdfPageAnnotation, PdfPageAnnotationCommon, PdfPageAnnotationType,
21};
22use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
23use crate::pdf::document::page::{PdfPage, PdfPageContentRegenerationStrategy, PdfPageIndexCache};
24use crate::pdf::quad_points::PdfQuadPoints;
25use crate::pdfium::PdfiumLibraryBindingsAccessor;
26use chrono::prelude::*;
27use std::marker::PhantomData;
28use std::ops::Range;
29use std::os::raw::c_int;
30
31pub type PdfPageAnnotationIndex = usize;
34
35pub struct PdfPageAnnotations<'a> {
37 document_handle: FPDF_DOCUMENT,
38 page_handle: FPDF_PAGE,
39 form_handle: Option<FPDF_FORMHANDLE>,
40 lifetime: PhantomData<&'a FPDF_PAGE>,
41}
42
43impl<'a> PdfPageAnnotations<'a> {
44 #[inline]
45 pub(crate) fn from_pdfium(
46 document_handle: FPDF_DOCUMENT,
47 page_handle: FPDF_PAGE,
48 form_handle: Option<FPDF_FORMHANDLE>,
49 ) -> Self {
50 PdfPageAnnotations {
51 document_handle,
52 page_handle,
53 form_handle,
54 lifetime: PhantomData,
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 len(&self) -> PdfPageAnnotationIndex {
75 (unsafe { self.bindings().FPDFPage_GetAnnotCount(self.page_handle) })
76 as PdfPageAnnotationIndex
77 }
78
79 #[inline]
81 pub fn is_empty(&self) -> bool {
82 self.len() == 0
83 }
84
85 #[inline]
87 pub fn as_range(&self) -> Range<PdfPageAnnotationIndex> {
88 0..self.len()
89 }
90
91 pub fn get(&self, index: PdfPageAnnotationIndex) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
93 if index >= self.len() {
94 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
95 }
96
97 let annotation_handle = unsafe {
98 self.bindings()
99 .FPDFPage_GetAnnot(self.page_handle, index as c_int)
100 };
101
102 if annotation_handle.is_null() {
103 Err(PdfiumError::PdfiumLibraryInternalError(
104 PdfiumInternalError::Unknown,
105 ))
106 } else {
107 Ok(PdfPageAnnotation::from_pdfium(
108 self.document_handle,
109 self.page_handle,
110 annotation_handle,
111 self.form_handle,
112 self.bindings(),
113 ))
114 }
115 }
116
117 #[inline]
119 pub fn first(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
120 if !self.is_empty() {
121 self.get(0)
122 } else {
123 Err(PdfiumError::NoAnnotationsInCollection)
124 }
125 }
126
127 #[inline]
129 pub fn last(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
130 if !self.is_empty() {
131 self.get(self.len() - 1)
132 } else {
133 Err(PdfiumError::NoAnnotationsInCollection)
134 }
135 }
136
137 #[inline]
139 pub fn iter(&self) -> PdfPageAnnotationsIterator<'_> {
140 PdfPageAnnotationsIterator::new(self)
141 }
142
143 pub(crate) fn create_annotation<T: PdfPageAnnotationCommon>(
150 &mut self,
151 annotation_type: PdfPageAnnotationType,
152 constructor: fn(FPDF_DOCUMENT, FPDF_PAGE, FPDF_ANNOTATION) -> T,
153 ) -> Result<T, PdfiumError> {
154 let handle = unsafe {
155 self.bindings()
156 .FPDFPage_CreateAnnot(self.page_handle(), annotation_type.as_pdfium())
157 };
158
159 if handle.is_null() {
160 Err(PdfiumError::PdfiumLibraryInternalError(
161 PdfiumInternalError::Unknown,
162 ))
163 } else {
164 let mut annotation = constructor(self.document_handle(), self.page_handle(), handle);
165
166 annotation
167 .set_creation_date(Utc::now())
168 .and_then(|()| {
169 if let Some(content_regeneration_strategy) =
170 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
171 self.document_handle(),
172 self.page_handle(),
173 )
174 {
175 if content_regeneration_strategy
176 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
177 {
178 PdfPage::regenerate_content_immut_for_handle(
179 self.page_handle(),
180 self.bindings(),
181 )
182 } else {
183 Ok(())
184 }
185 } else {
186 Err(PdfiumError::SourcePageIndexNotInCache)
187 }
188 })
189 .map(|()| annotation)
190 }
191 }
192
193 #[inline]
200 pub fn create_free_text_annotation(
201 &mut self,
202 text: &str,
203 ) -> Result<PdfPageFreeTextAnnotation<'a>, PdfiumError> {
204 let mut annotation = self.create_annotation(
205 PdfPageAnnotationType::FreeText,
206 PdfPageFreeTextAnnotation::from_pdfium,
207 )?;
208
209 annotation.set_contents(text)?;
210
211 Ok(annotation)
212 }
213
214 #[inline]
221 pub fn create_highlight_annotation(
222 &mut self,
223 ) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
224 self.create_annotation(
225 PdfPageAnnotationType::Highlight,
226 PdfPageHighlightAnnotation::from_pdfium,
227 )
228 }
229
230 #[inline]
237 pub fn create_ink_annotation(&mut self) -> Result<PdfPageInkAnnotation<'a>, PdfiumError> {
238 self.create_annotation(
239 PdfPageAnnotationType::Ink,
240 PdfPageInkAnnotation::from_pdfium,
241 )
242 }
243
244 pub fn create_link_annotation(
251 &mut self,
252 uri: &str,
253 ) -> Result<PdfPageLinkAnnotation<'a>, PdfiumError> {
254 let mut annotation = self.create_annotation(
255 PdfPageAnnotationType::Link,
256 PdfPageLinkAnnotation::from_pdfium,
257 )?;
258
259 annotation.set_link(uri)?;
260
261 Ok(annotation)
262 }
263
264 #[inline]
271 pub fn create_popup_annotation(&mut self) -> Result<PdfPagePopupAnnotation<'a>, PdfiumError> {
272 self.create_annotation(
273 PdfPageAnnotationType::Popup,
274 PdfPagePopupAnnotation::from_pdfium,
275 )
276 }
277
278 #[inline]
285 pub fn create_square_annotation(&mut self) -> Result<PdfPageSquareAnnotation<'a>, PdfiumError> {
286 self.create_annotation(
287 PdfPageAnnotationType::Square,
288 PdfPageSquareAnnotation::from_pdfium,
289 )
290 }
291
292 #[inline]
299 pub fn create_squiggly_annotation(
300 &mut self,
301 ) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
302 self.create_annotation(
303 PdfPageAnnotationType::Squiggly,
304 PdfPageSquigglyAnnotation::from_pdfium,
305 )
306 }
307
308 #[inline]
315 pub fn create_stamp_annotation(&mut self) -> Result<PdfPageStampAnnotation<'a>, PdfiumError> {
316 self.create_annotation(
317 PdfPageAnnotationType::Stamp,
318 PdfPageStampAnnotation::from_pdfium,
319 )
320 }
321
322 #[inline]
329 pub fn create_strikeout_annotation(
330 &mut self,
331 ) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
332 self.create_annotation(
333 PdfPageAnnotationType::Strikeout,
334 PdfPageStrikeoutAnnotation::from_pdfium,
335 )
336 }
337
338 #[inline]
345 pub fn create_text_annotation(
346 &mut self,
347 text: &str,
348 ) -> Result<PdfPageTextAnnotation<'a>, PdfiumError> {
349 let mut annotation = self.create_annotation(
350 PdfPageAnnotationType::Text,
351 PdfPageTextAnnotation::from_pdfium,
352 )?;
353
354 annotation.set_contents(text)?;
355
356 Ok(annotation)
357 }
358
359 #[inline]
366 pub fn create_underline_annotation(
367 &mut self,
368 ) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
369 self.create_annotation(
370 PdfPageAnnotationType::Underline,
371 PdfPageUnderlineAnnotation::from_pdfium,
372 )
373 }
374
375 #[inline]
389 pub fn create_squiggly_annotation_under_object(
390 &mut self,
391 object: &PdfPageObject,
392 color: PdfColor,
393 contents: Option<&str>,
394 ) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
395 let mut annotation = self.create_squiggly_annotation()?;
396
397 let bounds = object.bounds()?;
400
401 annotation.set_position(bounds.left(), bounds.bottom())?;
402 annotation.set_stroke_color(color)?;
403
404 const SQUIGGLY_HEIGHT: f32 = 12.0;
405
406 let annotation_top = bounds.bottom().value - 5.0;
407 let annotation_bottom = annotation_top - SQUIGGLY_HEIGHT;
408
409 annotation
410 .attachment_points_mut()
411 .create_attachment_point_at_end(PdfQuadPoints::new_from_values(
412 bounds.left().value,
413 annotation_bottom,
414 bounds.right().value,
415 annotation_bottom,
416 bounds.right().value,
417 annotation_top,
418 bounds.left().value,
419 annotation_top,
420 ))?;
421
422 if let Some(contents) = contents {
423 annotation.set_width(bounds.width())?;
424 annotation.set_height(bounds.height())?;
425 annotation.set_contents(contents)?;
426 }
427
428 Ok(annotation)
429 }
430
431 #[inline]
442 pub fn create_underline_annotation_under_object(
443 &mut self,
444 object: &PdfPageObject,
445 color: PdfColor,
446 contents: Option<&str>,
447 ) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
448 let mut annotation = self.create_underline_annotation()?;
449
450 let bounds = object.bounds()?;
453
454 annotation.set_position(bounds.left(), bounds.bottom())?;
455 annotation.set_stroke_color(color)?;
456 annotation
457 .attachment_points_mut()
458 .create_attachment_point_at_end(bounds)?;
459
460 if let Some(contents) = contents {
461 annotation.set_width(bounds.width())?;
462 annotation.set_height(bounds.height())?;
463 annotation.set_contents(contents)?;
464 }
465
466 Ok(annotation)
467 }
468
469 #[inline]
480 pub fn create_strikeout_annotation_through_object(
481 &mut self,
482 object: &PdfPageObject,
483 color: PdfColor,
484 contents: Option<&str>,
485 ) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
486 let mut annotation = self.create_strikeout_annotation()?;
487
488 let bounds = object.bounds()?;
491
492 annotation.set_position(bounds.left(), bounds.bottom())?;
493 annotation.set_stroke_color(color)?;
494 annotation
495 .attachment_points_mut()
496 .create_attachment_point_at_end(bounds)?;
497
498 if let Some(contents) = contents {
499 annotation.set_width(bounds.width())?;
500 annotation.set_height(bounds.height())?;
501 annotation.set_contents(contents)?;
502 }
503
504 Ok(annotation)
505 }
506
507 #[inline]
518 pub fn create_highlight_annotation_over_object(
519 &mut self,
520 object: &PdfPageObject,
521 color: PdfColor,
522 contents: Option<&str>,
523 ) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
524 let mut annotation = self.create_highlight_annotation()?;
525
526 let bounds = object.bounds()?;
529
530 annotation.set_position(bounds.left(), bounds.bottom())?;
531 annotation.set_stroke_color(color)?;
532 annotation
533 .attachment_points_mut()
534 .create_attachment_point_at_end(bounds)?;
535
536 if let Some(contents) = contents {
537 annotation.set_width(bounds.width())?;
538 annotation.set_height(bounds.height())?;
539 annotation.set_contents(contents)?;
540 }
541
542 Ok(annotation)
543 }
544
545 pub fn delete_annotation(
552 &mut self,
553 annotation: PdfPageAnnotation<'a>,
554 ) -> Result<(), PdfiumError> {
555 let index = unsafe {
556 self.bindings()
557 .FPDFPage_GetAnnotIndex(self.page_handle(), annotation.handle())
558 };
559
560 if index == -1 {
561 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
562 }
563
564 if self.bindings().is_true(unsafe {
565 self.bindings()
566 .FPDFPage_RemoveAnnot(self.page_handle(), index)
567 }) {
568 if let Some(content_regeneration_strategy) =
569 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
570 self.document_handle(),
571 self.page_handle(),
572 )
573 {
574 if content_regeneration_strategy
575 == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange
576 {
577 PdfPage::regenerate_content_immut_for_handle(
578 self.page_handle(),
579 self.bindings(),
580 )
581 } else {
582 Ok(())
583 }
584 } else {
585 Err(PdfiumError::SourcePageIndexNotInCache)
586 }
587 } else {
588 Err(PdfiumError::PdfiumLibraryInternalError(
589 PdfiumInternalError::Unknown,
590 ))
591 }
592 }
593}
594
595impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageAnnotations<'a> {}
596
597#[cfg(feature = "thread_safe")]
598unsafe impl<'a> Send for PdfPageAnnotations<'a> {}
599
600#[cfg(feature = "thread_safe")]
601unsafe impl<'a> Sync for PdfPageAnnotations<'a> {}
602
603pub struct PdfPageAnnotationsIterator<'a> {
605 annotations: &'a PdfPageAnnotations<'a>,
606 next_index: PdfPageAnnotationIndex,
607}
608
609impl<'a> PdfPageAnnotationsIterator<'a> {
610 #[inline]
611 pub(crate) fn new(annotations: &'a PdfPageAnnotations<'a>) -> Self {
612 PdfPageAnnotationsIterator {
613 annotations,
614 next_index: 0,
615 }
616 }
617}
618
619impl<'a> Iterator for PdfPageAnnotationsIterator<'a> {
620 type Item = PdfPageAnnotation<'a>;
621
622 fn next(&mut self) -> Option<Self::Item> {
623 let next = self.annotations.get(self.next_index);
624
625 self.next_index += 1;
626
627 next.ok()
628 }
629}