pdfium_render/pdf/
link.rs

1//! Defines the [PdfLink] struct, exposing functionality related to a single link contained
2//! within a [PdfPage], a [PdfPageAnnotation], or a [PdfBookmark].
3
4use crate::bindgen::{FPDF_DOCUMENT, FPDF_LINK, FS_RECTF};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::error::PdfiumError;
7use crate::pdf::action::PdfAction;
8use crate::pdf::destination::PdfDestination;
9use crate::pdf::rect::PdfRect;
10
11#[cfg(doc)]
12use {
13    crate::pdf::action::PdfActionType, crate::pdf::document::bookmark::PdfBookmark,
14    crate::pdf::document::page::annotation::PdfPageAnnotation, crate::pdf::document::page::PdfPage,
15};
16
17/// A single link contained within a [PdfPage], a [PdfPageAnnotation], or a [PdfBookmark].
18///
19/// Each link may have a corresponding [PdfAction] that will be triggered when the user
20/// interacts with the link, and a [PdfDestination] that indicates the target of any behaviour
21/// triggered by the [PdfAction].
22pub struct PdfLink<'a> {
23    handle: FPDF_LINK,
24    document: FPDF_DOCUMENT,
25    bindings: &'a dyn PdfiumLibraryBindings,
26}
27
28impl<'a> PdfLink<'a> {
29    #[inline]
30    pub(crate) fn from_pdfium(
31        handle: FPDF_LINK,
32        document: FPDF_DOCUMENT,
33        bindings: &'a dyn PdfiumLibraryBindings,
34    ) -> Self {
35        PdfLink {
36            handle,
37            document,
38            bindings,
39        }
40    }
41
42    /// Returns the internal `FPDF_LINK` handle for this [PdfLink].
43    #[inline]
44    pub(crate) fn handle(&self) -> FPDF_LINK {
45        self.handle
46    }
47
48    /// Returns the [PdfiumLibraryBindings] used by this [PdfLink].
49    #[inline]
50    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
51        self.bindings
52    }
53
54    /// Returns the [PdfAction] associated with this [PdfLink], if any.
55    ///
56    /// The action indicates the behaviour that will occur when the user interacts with the
57    /// link in a PDF viewer. For most links, this will be a local navigation action
58    /// of type [PdfActionType::GoToDestinationInSameDocument], but the PDF file format supports
59    /// a variety of other actions.
60    pub fn action(&self) -> Option<PdfAction<'a>> {
61        let handle = self.bindings().FPDFLink_GetAction(self.handle());
62
63        if handle.is_null() {
64            None
65        } else {
66            Some(PdfAction::from_pdfium(
67                handle,
68                self.document,
69                self.bindings(),
70            ))
71        }
72    }
73
74    /// Returns the [PdfDestination] associated with this [PdfLink], if any.
75    ///
76    /// The destination specifies the page and region, if any, that will be the target
77    /// of any behaviour that will occur when the user interacts with the link in a PDF viewer.
78    pub fn destination(&self) -> Option<PdfDestination<'a>> {
79        let handle = self
80            .bindings()
81            .FPDFLink_GetDest(self.document, self.handle());
82
83        if handle.is_null() {
84            None
85        } else {
86            Some(PdfDestination::from_pdfium(
87                self.document,
88                handle,
89                self.bindings(),
90            ))
91        }
92    }
93
94    /// Returns the area on the page that the user can use to interact with this [PdfLink]
95    /// in a PDF viewer, if any.
96    pub fn rect(&self) -> Result<PdfRect, PdfiumError> {
97        let mut rect = FS_RECTF {
98            left: 0.0,
99            top: 0.0,
100            right: 0.0,
101            bottom: 0.0,
102        };
103
104        PdfRect::from_pdfium_as_result(
105            self.bindings()
106                .FPDFLink_GetAnnotRect(self.handle(), &mut rect),
107            rect,
108            self.bindings(),
109        )
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use crate::prelude::*;
116    use crate::utils::test::test_bind_to_pdfium;
117
118    #[test]
119    fn test_link_rect() -> Result<(), PdfiumError> {
120        let pdfium = test_bind_to_pdfium();
121
122        // The document under test contains a single page with a single link.
123
124        let document = pdfium.load_pdf_from_file("./test/links-test.pdf", None)?;
125
126        const EXPECTED: PdfRect = PdfRect::new_from_values(733.3627, 207.85417, 757.6127, 333.1458);
127
128        // Allow a little bit of error, because it's unreasonable to expect floating point
129        // calculations to be identical across builds and platforms.
130
131        const ABS_ERR: PdfPoints = PdfPoints::new(f32::EPSILON * 1000.);
132
133        let actual = document
134            .pages()
135            .iter()
136            .next()
137            .unwrap()
138            .links()
139            .iter()
140            .next()
141            .unwrap()
142            .rect()?;
143
144        assert!((actual.top() - EXPECTED.top()).abs() < ABS_ERR);
145        assert!((actual.bottom() - EXPECTED.bottom()).abs() < ABS_ERR);
146        assert!((actual.left() - EXPECTED.left()).abs() < ABS_ERR);
147        assert!((actual.right() - EXPECTED.right()).abs() < ABS_ERR);
148
149        Ok(())
150    }
151}