Skip to main content

rust_pdf_box/interactive/
annotation.rs

1use crate::content::color::Color;
2use crate::cos::document::COSDocument;
3use crate::cos::object::{COSArray, COSDictionary, COSName, COSString, ObjectId};
4use crate::pd::page::PDPage;
5use crate::pd::rectangle::PDRectangle;
6
7#[derive(Debug, Clone, PartialEq)]
8pub enum AnnotationType {
9    Link { uri: String },
10    Highlight,
11    Underline,
12    Text { comment: String },
13    Stamp { name: String },
14}
15
16/// A PDF Page Annotation (/Subtype /Link, /Highlight, /Text, etc.).
17#[derive(Debug, Clone)]
18pub struct PDAnnotation {
19    pub rect: PDRectangle,
20    pub annotation_type: AnnotationType,
21    pub color: Option<Color>,
22    pub contents: Option<String>,
23}
24
25impl PDAnnotation {
26    /// Creates a clickable URI web link annotation.
27    pub fn link(rect: PDRectangle, uri: impl Into<String>) -> Self {
28        Self {
29            rect,
30            annotation_type: AnnotationType::Link { uri: uri.into() },
31            color: Some(Color::rgb(0.0, 0.4, 0.8)),
32            contents: None,
33        }
34    }
35
36    /// Creates a text highlight annotation.
37    pub fn highlight(rect: PDRectangle, comment: Option<String>) -> Self {
38        Self {
39            rect,
40            annotation_type: AnnotationType::Highlight,
41            color: Some(Color::rgb(1.0, 1.0, 0.0)), // Yellow highlight
42            contents: comment,
43        }
44    }
45
46    /// Attaches this annotation to a page dictionary and stores it in the COSDocument.
47    pub fn attach_to_page(&self, page: &mut PDPage, cos_doc: &mut COSDocument) -> ObjectId {
48        let mut dict = COSDictionary::new();
49        dict.set("Type", COSName::new("Annot"));
50        dict.set("Rect", self.rect);
51
52        if let Some(c) = self.color {
53            if let Color::Rgb(r, g, b) = c {
54                let mut c_arr = COSArray::new();
55                c_arr.push(r);
56                c_arr.push(g);
57                c_arr.push(b);
58                dict.set("C", c_arr);
59            }
60        }
61
62        if let Some(ref text) = self.contents {
63            dict.set("Contents", COSString::literal(text.clone().into_bytes()));
64        }
65
66        match &self.annotation_type {
67            AnnotationType::Link { uri } => {
68                dict.set("Subtype", COSName::new("Link"));
69                let mut action = COSDictionary::new();
70                action.set("Type", COSName::new("Action"));
71                action.set("S", COSName::new("URI"));
72                action.set("URI", COSString::literal(uri.clone().into_bytes()));
73                dict.set("A", action);
74                dict.set("F", 4); // Print flag
75            }
76            AnnotationType::Highlight => {
77                dict.set("Subtype", COSName::new("Highlight"));
78            }
79            AnnotationType::Underline => {
80                dict.set("Subtype", COSName::new("Underline"));
81            }
82            AnnotationType::Text { comment } => {
83                dict.set("Subtype", COSName::new("Text"));
84                dict.set("Contents", COSString::literal(comment.clone().into_bytes()));
85            }
86            AnnotationType::Stamp { name } => {
87                dict.set("Subtype", COSName::new("Stamp"));
88                dict.set("Name", COSName::new(name));
89            }
90        }
91
92        let annot_id = cos_doc.add_object(dict);
93
94        let mut annots = page.dict.get_array("Annots").cloned().unwrap_or_default();
95        annots.push(annot_id);
96        page.dict.set("Annots", annots);
97
98        annot_id
99    }
100}