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::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::{PdfPageAnnotation, PdfPageAnnotationCommon, PdfPageAnnotationType};
20use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon};
21use crate::pdf::document::page::{PdfPage, PdfPageContentRegenerationStrategy, PdfPageIndexCache};
22use crate::pdf::quad_points::PdfQuadPoints;
23use chrono::prelude::*;
24use std::ops::Range;
25use std::os::raw::c_int;
26
27pub type PdfPageAnnotationIndex = usize;
30
31pub struct PdfPageAnnotations<'a> {
33 document_handle: FPDF_DOCUMENT,
34 page_handle: FPDF_PAGE,
35 form_handle: Option<FPDF_FORMHANDLE>,
36 bindings: &'a dyn PdfiumLibraryBindings,
37}
38
39impl<'a> PdfPageAnnotations<'a> {
40 #[inline]
41 pub(crate) fn from_pdfium(
42 document_handle: FPDF_DOCUMENT,
43 page_handle: FPDF_PAGE,
44 form_handle: Option<FPDF_FORMHANDLE>,
45 bindings: &'a dyn PdfiumLibraryBindings,
46 ) -> Self {
47 PdfPageAnnotations {
48 document_handle,
49 page_handle,
50 form_handle,
51 bindings,
52 }
53 }
54
55 #[inline]
58 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
59 self.document_handle
60 }
61
62 #[inline]
65 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
66 self.page_handle
67 }
68
69 #[inline]
71 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
72 self.bindings
73 }
74
75 #[inline]
77 pub fn len(&self) -> PdfPageAnnotationIndex {
78 self.bindings().FPDFPage_GetAnnotCount(self.page_handle) as PdfPageAnnotationIndex
79 }
80
81 #[inline]
83 pub fn is_empty(&self) -> bool {
84 self.len() == 0
85 }
86
87 #[inline]
89 pub fn as_range(&self) -> Range<PdfPageAnnotationIndex> {
90 0..self.len()
91 }
92
93 pub fn get(&self, index: PdfPageAnnotationIndex) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
95 if index >= self.len() {
96 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
97 }
98
99 let annotation_handle = self.bindings().FPDFPage_GetAnnot(self.page_handle, index as c_int);
100
101 if annotation_handle.is_null() {
102 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
103 } else {
104 Ok(PdfPageAnnotation::from_pdfium(
105 self.document_handle,
106 self.page_handle,
107 annotation_handle,
108 self.form_handle,
109 self.bindings,
110 ))
111 }
112 }
113
114 #[inline]
116 pub fn first(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
117 if !self.is_empty() {
118 self.get(0)
119 } else {
120 Err(PdfiumError::NoAnnotationsInCollection)
121 }
122 }
123
124 #[inline]
126 pub fn last(&self) -> Result<PdfPageAnnotation<'a>, PdfiumError> {
127 if !self.is_empty() {
128 self.get(self.len() - 1)
129 } else {
130 Err(PdfiumError::NoAnnotationsInCollection)
131 }
132 }
133
134 #[inline]
136 pub fn iter(&self) -> PdfPageAnnotationsIterator<'_> {
137 PdfPageAnnotationsIterator::new(self)
138 }
139
140 pub(crate) fn create_annotation<T: PdfPageAnnotationCommon>(
147 &mut self,
148 annotation_type: PdfPageAnnotationType,
149 constructor: fn(FPDF_DOCUMENT, FPDF_PAGE, FPDF_ANNOTATION, &'a dyn PdfiumLibraryBindings) -> T,
150 ) -> Result<T, PdfiumError> {
151 let handle = self
152 .bindings()
153 .FPDFPage_CreateAnnot(self.page_handle(), annotation_type.as_pdfium());
154
155 if handle.is_null() {
156 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
157 } else {
158 let mut annotation = constructor(self.document_handle(), self.page_handle(), handle, self.bindings());
159
160 annotation
161 .set_creation_date(Utc::now())
162 .and_then(|()| {
163 if let Some(content_regeneration_strategy) =
164 PdfPageIndexCache::get_content_regeneration_strategy_for_page(
165 self.document_handle(),
166 self.page_handle(),
167 )
168 {
169 if content_regeneration_strategy == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange {
170 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())
171 } else {
172 Ok(())
173 }
174 } else {
175 Err(PdfiumError::SourcePageIndexNotInCache)
176 }
177 })
178 .map(|()| annotation)
179 }
180 }
181
182 #[inline]
189 pub fn create_free_text_annotation(&mut self, text: &str) -> Result<PdfPageFreeTextAnnotation<'a>, PdfiumError> {
190 let mut annotation =
191 self.create_annotation(PdfPageAnnotationType::FreeText, PdfPageFreeTextAnnotation::from_pdfium)?;
192
193 annotation.set_contents(text)?;
194
195 Ok(annotation)
196 }
197
198 #[inline]
205 pub fn create_highlight_annotation(&mut self) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
206 self.create_annotation(
207 PdfPageAnnotationType::Highlight,
208 PdfPageHighlightAnnotation::from_pdfium,
209 )
210 }
211
212 #[inline]
219 pub fn create_ink_annotation(&mut self) -> Result<PdfPageInkAnnotation<'a>, PdfiumError> {
220 self.create_annotation(PdfPageAnnotationType::Ink, PdfPageInkAnnotation::from_pdfium)
221 }
222
223 pub fn create_link_annotation(&mut self, uri: &str) -> Result<PdfPageLinkAnnotation<'a>, PdfiumError> {
230 let mut annotation = self.create_annotation(PdfPageAnnotationType::Link, PdfPageLinkAnnotation::from_pdfium)?;
231
232 annotation.set_link(uri)?;
233
234 Ok(annotation)
235 }
236
237 #[inline]
244 pub fn create_square_annotation(&mut self) -> Result<PdfPageSquareAnnotation<'a>, PdfiumError> {
245 self.create_annotation(PdfPageAnnotationType::Square, PdfPageSquareAnnotation::from_pdfium)
246 }
247
248 #[inline]
255 pub fn create_squiggly_annotation(&mut self) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
256 self.create_annotation(PdfPageAnnotationType::Squiggly, PdfPageSquigglyAnnotation::from_pdfium)
257 }
258
259 #[inline]
266 pub fn create_stamp_annotation(&mut self) -> Result<PdfPageStampAnnotation<'a>, PdfiumError> {
267 self.create_annotation(PdfPageAnnotationType::Stamp, PdfPageStampAnnotation::from_pdfium)
268 }
269
270 #[inline]
277 pub fn create_strikeout_annotation(&mut self) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
278 self.create_annotation(
279 PdfPageAnnotationType::Strikeout,
280 PdfPageStrikeoutAnnotation::from_pdfium,
281 )
282 }
283
284 #[inline]
291 pub fn create_text_annotation(&mut self, text: &str) -> Result<PdfPageTextAnnotation<'a>, PdfiumError> {
292 let mut annotation = self.create_annotation(PdfPageAnnotationType::Text, PdfPageTextAnnotation::from_pdfium)?;
293
294 annotation.set_contents(text)?;
295
296 Ok(annotation)
297 }
298
299 #[inline]
306 pub fn create_underline_annotation(&mut self) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
307 self.create_annotation(
308 PdfPageAnnotationType::Underline,
309 PdfPageUnderlineAnnotation::from_pdfium,
310 )
311 }
312
313 #[inline]
324 pub fn create_squiggly_annotation_under_object(
325 &mut self,
326 object: &PdfPageObject,
327 color: PdfColor,
328 contents: Option<&str>,
329 ) -> Result<PdfPageSquigglyAnnotation<'a>, PdfiumError> {
330 let mut annotation = self.create_squiggly_annotation()?;
331
332 let bounds = object.bounds()?;
333
334 annotation.set_position(bounds.left(), bounds.bottom())?;
335 annotation.set_stroke_color(color)?;
336
337 const SQUIGGLY_HEIGHT: f32 = 12.0;
338
339 let annotation_top = bounds.bottom().value - 5.0;
340 let annotation_bottom = annotation_top - SQUIGGLY_HEIGHT;
341
342 annotation
343 .attachment_points_mut()
344 .create_attachment_point_at_end(PdfQuadPoints::new_from_values(
345 bounds.left().value,
346 annotation_bottom,
347 bounds.right().value,
348 annotation_bottom,
349 bounds.right().value,
350 annotation_top,
351 bounds.left().value,
352 annotation_top,
353 ))?;
354
355 if let Some(contents) = contents {
356 annotation.set_width(bounds.width())?;
357 annotation.set_height(bounds.height())?;
358 annotation.set_contents(contents)?;
359 }
360
361 Ok(annotation)
362 }
363
364 #[inline]
375 pub fn create_underline_annotation_under_object(
376 &mut self,
377 object: &PdfPageObject,
378 color: PdfColor,
379 contents: Option<&str>,
380 ) -> Result<PdfPageUnderlineAnnotation<'a>, PdfiumError> {
381 let mut annotation = self.create_underline_annotation()?;
382
383 let bounds = object.bounds()?;
384
385 annotation.set_position(bounds.left(), bounds.bottom())?;
386 annotation.set_stroke_color(color)?;
387 annotation
388 .attachment_points_mut()
389 .create_attachment_point_at_end(bounds)?;
390
391 if let Some(contents) = contents {
392 annotation.set_width(bounds.width())?;
393 annotation.set_height(bounds.height())?;
394 annotation.set_contents(contents)?;
395 }
396
397 Ok(annotation)
398 }
399
400 #[inline]
411 pub fn create_strikeout_annotation_through_object(
412 &mut self,
413 object: &PdfPageObject,
414 color: PdfColor,
415 contents: Option<&str>,
416 ) -> Result<PdfPageStrikeoutAnnotation<'a>, PdfiumError> {
417 let mut annotation = self.create_strikeout_annotation()?;
418
419 let bounds = object.bounds()?;
420
421 annotation.set_position(bounds.left(), bounds.bottom())?;
422 annotation.set_stroke_color(color)?;
423 annotation
424 .attachment_points_mut()
425 .create_attachment_point_at_end(bounds)?;
426
427 if let Some(contents) = contents {
428 annotation.set_width(bounds.width())?;
429 annotation.set_height(bounds.height())?;
430 annotation.set_contents(contents)?;
431 }
432
433 Ok(annotation)
434 }
435
436 #[inline]
447 pub fn create_highlight_annotation_over_object(
448 &mut self,
449 object: &PdfPageObject,
450 color: PdfColor,
451 contents: Option<&str>,
452 ) -> Result<PdfPageHighlightAnnotation<'a>, PdfiumError> {
453 let mut annotation = self.create_highlight_annotation()?;
454
455 let bounds = object.bounds()?;
456
457 annotation.set_position(bounds.left(), bounds.bottom())?;
458 annotation.set_stroke_color(color)?;
459 annotation
460 .attachment_points_mut()
461 .create_attachment_point_at_end(bounds)?;
462
463 if let Some(contents) = contents {
464 annotation.set_width(bounds.width())?;
465 annotation.set_height(bounds.height())?;
466 annotation.set_contents(contents)?;
467 }
468
469 Ok(annotation)
470 }
471
472 pub fn delete_annotation(&mut self, annotation: PdfPageAnnotation<'a>) -> Result<(), PdfiumError> {
479 let index = self
480 .bindings()
481 .FPDFPage_GetAnnotIndex(self.page_handle(), annotation.handle());
482
483 if index == -1 {
484 return Err(PdfiumError::PageAnnotationIndexOutOfBounds);
485 }
486
487 if self
488 .bindings()
489 .is_true(self.bindings().FPDFPage_RemoveAnnot(self.page_handle(), index))
490 {
491 if let Some(content_regeneration_strategy) = PdfPageIndexCache::get_content_regeneration_strategy_for_page(
492 self.document_handle(),
493 self.page_handle(),
494 ) {
495 if content_regeneration_strategy == PdfPageContentRegenerationStrategy::AutomaticOnEveryChange {
496 PdfPage::regenerate_content_immut_for_handle(self.page_handle(), self.bindings())
497 } else {
498 Ok(())
499 }
500 } else {
501 Err(PdfiumError::SourcePageIndexNotInCache)
502 }
503 } else {
504 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
505 }
506 }
507}
508
509pub struct PdfPageAnnotationsIterator<'a> {
511 annotations: &'a PdfPageAnnotations<'a>,
512 next_index: PdfPageAnnotationIndex,
513}
514
515impl<'a> PdfPageAnnotationsIterator<'a> {
516 #[inline]
517 pub(crate) fn new(annotations: &'a PdfPageAnnotations<'a>) -> Self {
518 PdfPageAnnotationsIterator {
519 annotations,
520 next_index: 0,
521 }
522 }
523}
524
525impl<'a> Iterator for PdfPageAnnotationsIterator<'a> {
526 type Item = PdfPageAnnotation<'a>;
527
528 fn next(&mut self) -> Option<Self::Item> {
529 let next = self.annotations.get(self.next_index);
530
531 self.next_index += 1;
532
533 next.ok()
534 }
535}