Skip to main content

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