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