Skip to main content

pdfium_render/pdf/document/page/
links.rs

1//! Defines the [PdfPageLinks] struct, exposing functionality related to the
2//! links contained within a single `PdfPage`.
3
4use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE};
5use crate::error::PdfiumError;
6use crate::pdf::link::PdfLink;
7use crate::pdf::points::PdfPoints;
8use crate::pdfium::PdfiumLibraryBindingsAccessor;
9use std::marker::PhantomData;
10use std::ops::{Range, RangeInclusive};
11use std::os::raw::c_int;
12use std::ptr::null_mut;
13
14/// The zero-based index of a single [PdfLink] inside its containing [PdfPageLinks] collection.
15pub type PdfPageLinkIndex = usize;
16
17/// The links contained within a single `PdfPage`.
18pub struct PdfPageLinks<'a> {
19    page_handle: FPDF_PAGE,
20    document_handle: FPDF_DOCUMENT,
21    lifetime: PhantomData<&'a FPDF_PAGE>,
22}
23
24impl<'a> PdfPageLinks<'a> {
25    #[inline]
26    pub(crate) fn from_pdfium(page_handle: FPDF_PAGE, document_handle: FPDF_DOCUMENT) -> Self {
27        PdfPageLinks {
28            page_handle,
29            document_handle,
30            lifetime: PhantomData,
31        }
32    }
33
34    /// Returns the number of links in this [PdfPageLinks] collection.
35    #[inline]
36    pub fn len(&self) -> PdfPageLinkIndex {
37        // Since there is no FPDF_* function to return the number of links contained in a page,
38        // we must explore the entire collection. One option would be to simply iterate over
39        // all possible links, like so:
40
41        // self.iter().count() as PdfPageLinkIndex
42
43        // This works perfectly, but is inefficient for very large collections of links as it
44        // is O(n). Instead, we use a sliding interval search technique, conceptually similar
45        // to binary search, that should be closer to O(log n).
46
47        // Early exit if there are zero or one links.
48
49        if self.get(0).is_err() {
50            return 0;
51        }
52
53        if self.get(1).is_err() {
54            return 1;
55        }
56
57        // Establish a maximal upper bound for the range (0..len).
58
59        let mut range_start = 0;
60        let mut range_end = 50;
61
62        loop {
63            if self.get(range_end).is_err() {
64                break;
65            } else {
66                range_start = range_end;
67                range_end *= 2;
68            }
69        }
70
71        // Now probe the range between (range_start..range_end).
72
73        loop {
74            let midpoint = range_start + (range_end - range_start) / 2;
75
76            if midpoint == range_start {
77                // range_start and range_end now differ by a maximum of 1.
78
79                break;
80            }
81
82            if self.get(midpoint).is_err() {
83                range_end = midpoint;
84            } else {
85                range_start = midpoint;
86            }
87        }
88
89        range_end
90    }
91
92    /// Returns `true` if this [PdfPageLinks] collection is empty.
93    #[inline]
94    pub fn is_empty(&self) -> bool {
95        self.len() == 0
96    }
97
98    /// Returns a Range from `0..(number of links)` for this [PdfPageLinks] collection.
99    #[inline]
100    pub fn as_range(&self) -> Range<PdfPageLinkIndex> {
101        0..self.len()
102    }
103
104    /// Returns an inclusive Range from `0..=(number of links - 1)` for this [PdfPageLinks] collection.
105    #[inline]
106    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfPageLinkIndex> {
107        if self.is_empty() {
108            0..=0
109        } else {
110            0..=(self.len() - 1)
111        }
112    }
113
114    /// Returns a single [PdfLink] from this [PdfPageLinks] collection.
115    pub fn get(&'a self, index: PdfPageLinkIndex) -> Result<PdfLink<'a>, PdfiumError> {
116        let mut start_pos = index as c_int;
117
118        let mut handle = null_mut();
119
120        if self.bindings().is_true(unsafe {
121            self.bindings()
122                .FPDFLink_Enumerate(self.page_handle, &mut start_pos, &mut handle)
123        }) && !handle.is_null()
124        {
125            Ok(PdfLink::from_pdfium(handle, self.document_handle))
126        } else {
127            Err(PdfiumError::LinkIndexOutOfBounds)
128        }
129    }
130
131    /// Returns the first [PdfLink] object in this [PdfPageLinks] collection.
132    #[inline]
133    pub fn first(&'a self) -> Result<PdfLink<'a>, PdfiumError> {
134        self.get(0)
135            .map_err(|_| PdfiumError::NoPageLinksInCollection)
136    }
137
138    /// Returns the last [PdfLink] object in this [PdfPageLinks] collection.
139    #[inline]
140    pub fn last(&'a self) -> Result<PdfLink<'a>, PdfiumError> {
141        self.get(self.len() - 1)
142            .map_err(|_| PdfiumError::NoPageLinksInCollection)
143    }
144
145    /// Returns the [PdfLink] object at the given position on the containing page, if any.
146    pub fn link_at_point(&self, x: PdfPoints, y: PdfPoints) -> Option<PdfLink<'_>> {
147        let handle = unsafe {
148            self.bindings().FPDFLink_GetLinkAtPoint(
149                self.page_handle,
150                x.value as f64,
151                y.value as f64,
152            )
153        };
154
155        if handle.is_null() {
156            None
157        } else {
158            Some(PdfLink::from_pdfium(handle, self.document_handle))
159        }
160    }
161
162    /// Returns an iterator over all the [PdfLink] objects in this [PdfPageLinks] collection.
163    #[inline]
164    pub fn iter(&self) -> PdfPageLinksIterator<'_> {
165        PdfPageLinksIterator::new(self)
166    }
167}
168
169impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageLinks<'a> {}
170
171#[cfg(feature = "thread_safe")]
172unsafe impl<'a> Send for PdfPageLinks<'a> {}
173
174#[cfg(feature = "thread_safe")]
175unsafe impl<'a> Sync for PdfPageLinks<'a> {}
176
177/// An iterator over all the [PdfLink] objects in a [PdfPageLinksIterator] collection.
178pub struct PdfPageLinksIterator<'a> {
179    links: &'a PdfPageLinks<'a>,
180    next_index: PdfPageLinkIndex,
181}
182
183impl<'a> PdfPageLinksIterator<'a> {
184    #[inline]
185    pub(crate) fn new(links: &'a PdfPageLinks<'a>) -> Self {
186        PdfPageLinksIterator {
187            links,
188            next_index: 0,
189        }
190    }
191}
192
193impl<'a> Iterator for PdfPageLinksIterator<'a> {
194    type Item = PdfLink<'a>;
195
196    fn next(&mut self) -> Option<Self::Item> {
197        let next = self.links.get(self.next_index);
198
199        self.next_index += 1;
200
201        next.ok()
202    }
203}