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::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() { 0..=0 } else { 0..=(self.len() - 1) }
117    }
118
119    /// Returns a single [PdfLink] from this [PdfPageLinks] collection.
120    pub fn get(&'a self, index: PdfPageLinkIndex) -> Result<PdfLink<'a>, PdfiumError> {
121        let mut start_pos = index as c_int;
122
123        let mut handle = null_mut();
124
125        if self.bindings.is_true(
126            self.bindings
127                .FPDFLink_Enumerate(self.page_handle, &mut start_pos, &mut handle),
128        ) && !handle.is_null()
129        {
130            Ok(PdfLink::from_pdfium(handle, self.document_handle, self.bindings))
131        } else {
132            Err(PdfiumError::LinkIndexOutOfBounds)
133        }
134    }
135
136    /// Returns the first [PdfLink] object in this [PdfPageLinks] collection.
137    #[inline]
138    pub fn first(&'a self) -> Result<PdfLink<'a>, PdfiumError> {
139        self.get(0).map_err(|_| PdfiumError::NoPageLinksInCollection)
140    }
141
142    /// Returns the last [PdfLink] object in this [PdfPageLinks] collection.
143    #[inline]
144    pub fn last(&'a self) -> Result<PdfLink<'a>, PdfiumError> {
145        self.get(self.len() - 1)
146            .map_err(|_| PdfiumError::NoPageLinksInCollection)
147    }
148
149    /// Returns the [PdfLink] object at the given position on the containing page, if any.
150    pub fn link_at_point(&self, x: PdfPoints, y: PdfPoints) -> Option<PdfLink<'_>> {
151        let handle = self
152            .bindings
153            .FPDFLink_GetLinkAtPoint(self.page_handle, x.value as f64, y.value as f64);
154
155        if handle.is_null() {
156            None
157        } else {
158            Some(PdfLink::from_pdfium(handle, self.document_handle, self.bindings))
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
169/// An iterator over all the [PdfLink] objects in a [PdfPageLinksIterator] collection.
170pub struct PdfPageLinksIterator<'a> {
171    links: &'a PdfPageLinks<'a>,
172    next_index: PdfPageLinkIndex,
173}
174
175impl<'a> PdfPageLinksIterator<'a> {
176    #[inline]
177    pub(crate) fn new(links: &'a PdfPageLinks<'a>) -> Self {
178        PdfPageLinksIterator { links, next_index: 0 }
179    }
180}
181
182impl<'a> Iterator for PdfPageLinksIterator<'a> {
183    type Item = PdfLink<'a>;
184
185    fn next(&mut self) -> Option<Self::Item> {
186        let next = self.links.get(self.next_index);
187
188        self.next_index += 1;
189
190        next.ok()
191    }
192}