Skip to main content

pdfium_render/pdf/
destination.rs

1//! Defines the [PdfDestination] struct, exposing functionality related to the target destination
2//! of a link contained within a single [PdfPage].
3
4use crate::bindgen::{
5    FPDF_DEST, FPDF_DOCUMENT, FS_FLOAT, PDFDEST_VIEW_FIT, PDFDEST_VIEW_FITB, PDFDEST_VIEW_FITBH,
6    PDFDEST_VIEW_FITBV, PDFDEST_VIEW_FITH, PDFDEST_VIEW_FITR, PDFDEST_VIEW_FITV,
7    PDFDEST_VIEW_UNKNOWN_MODE, PDFDEST_VIEW_XYZ,
8};
9use crate::error::PdfiumError;
10use crate::pdf::document::pages::PdfPageIndex;
11use crate::pdf::points::PdfPoints;
12use crate::pdf::rect::PdfRect;
13use crate::pdfium::PdfiumLibraryBindingsAccessor;
14use crate::utils::mem::create_sized_buffer;
15use std::marker::PhantomData;
16
17#[cfg(doc)]
18use crate::pdf::document::page::PdfPage;
19
20/// The view settings that a PDF viewer should apply when displaying the target
21/// [PdfPage] nominated by a [PdfDestination] in its display window.
22#[derive(Debug, Copy, Clone)]
23pub enum PdfDestinationViewSettings {
24    /// The view settings are unknown.
25    Unknown,
26
27    /// Display the target `PdfPage` with the given (x, y) coordinates positioned at the
28    /// upper-left corner of the window and the contents of the page magnified by the given
29    /// zoom factor. A missing value for any of the parameters indicates that the current value
30    /// of that parameter is to be retained unchanged.
31    SpecificCoordinatesAndZoom(Option<PdfPoints>, Option<PdfPoints>, Option<f32>),
32
33    /// Display the target `PdfPage` with its contents magnified just enough
34    /// to fit the entire page within the window both horizontally and vertically. If
35    /// the required horizontal and vertical magnification factors are different, use
36    /// the smaller of the two, centering the page within the window in the other
37    /// dimension.
38    FitPageToWindow,
39
40    /// Display the target `PdfPage` with the given y coordinate positioned at the top edge of
41    /// the window and the contents of the page magnified just enough to fit the entire width
42    /// of the page within the window. A missing value for the y coordinate indicates
43    /// that the current value of that parameter is to be retained unchanged.
44    FitPageHorizontallyToWindow(Option<PdfPoints>),
45
46    /// Display the target `PdfPage` with the given x coordinate positioned at the left edge of
47    /// the window and the contents of the page magnified just enough to fit the entire height
48    /// of the page within the window. A missing value for the x coordinate indicates
49    /// that the current value of that parameter is to be retained unchanged.
50    FitPageVerticallyToWindow(Option<PdfPoints>),
51
52    /// Display the target `PdfPage` with its contents magnified just enough to fit the
53    /// given rectangle entirely within the window both horizontally and vertically.
54    /// If the required horizontal and vertical magnification factors are different, then use
55    /// the smaller of the two, centering the rectangle within the window in the other dimension.
56    FitPageToRectangle(PdfRect),
57
58    /// Display the target `PdfPage` with its contents magnified just enough to fit its bounding box
59    /// entirely within the window both horizontally and vertically. If the required horizontal and
60    /// vertical magnification factors are different, then use the smaller of the two,
61    /// centering the bounding box within the window in the other dimension.
62    ///
63    /// This variant was added in PDF version 1.1.
64    FitBoundsToWindow,
65
66    /// Display the target `PdfPage` with the given y coordinate positioned at the top edge of
67    /// the window and the contents of the page magnified just enough to fit the entire width
68    /// of its bounding box within the window. A missing value for the y coordinate indicates
69    /// that the current value of that parameter is to be retained unchanged.
70    ///
71    /// This variant was added in PDF version 1.1.
72    FitBoundsHorizontallyToWindow(Option<PdfPoints>),
73
74    /// Display the target `PdfPage` with the given x coordinate positioned at the left edge of
75    /// the window and the contents of the page magnified just enough to fit the entire height
76    /// of its bounding box within the window. A missing value for the x coordinate indicates
77    /// that the current value of that parameter is to be retained unchanged.
78    ///
79    /// This variant was added in PDF version 1.1.
80    FitBoundsVerticallyToWindow(Option<PdfPoints>),
81}
82
83impl PdfDestinationViewSettings {
84    pub(crate) fn from_pdfium(
85        destination: &PdfDestination,
86    ) -> Result<PdfDestinationViewSettings, PdfiumError> {
87        // We use a combination of calls to FPDFDest_GetLocationInPage() and
88        // FPDFDest_GetView() to account for all supported view settings
89        // in a null-safe manner.
90
91        let mut has_x_value = destination.bindings().FALSE();
92
93        let mut has_y_value = destination.bindings().FALSE();
94
95        let mut has_zoom_value = destination.bindings().FALSE();
96
97        let mut x_value: FS_FLOAT = 0.0;
98
99        let mut y_value: FS_FLOAT = 0.0;
100
101        let mut zoom_value: FS_FLOAT = 0.0;
102
103        let (x, y, zoom) = if destination.bindings().is_true(unsafe {
104            destination.bindings().FPDFDest_GetLocationInPage(
105                destination.destination_handle,
106                &mut has_x_value,
107                &mut has_y_value,
108                &mut has_zoom_value,
109                &mut x_value,
110                &mut y_value,
111                &mut zoom_value,
112            )
113        }) {
114            let x = if destination.bindings().is_true(has_x_value) {
115                Some(PdfPoints::new(x_value))
116            } else {
117                None
118            };
119
120            let y = if destination.bindings().is_true(has_y_value) {
121                Some(PdfPoints::new(y_value))
122            } else {
123                None
124            };
125
126            let zoom = if destination.bindings().is_true(has_zoom_value) {
127                // The PDF specification states that a zoom value of 0 has the same meaning
128                // as a null value.
129
130                if zoom_value != 0.0 {
131                    Some(zoom_value)
132                } else {
133                    None
134                }
135            } else {
136                None
137            };
138
139            (x, y, zoom)
140        } else {
141            (None, None, None)
142        };
143
144        let mut p_num_params = 0;
145
146        let mut p_params: Vec<FS_FLOAT> = create_sized_buffer(4);
147
148        let view = unsafe {
149            destination.bindings().FPDFDest_GetView(
150                destination.destination_handle,
151                &mut p_num_params,
152                p_params.as_mut_ptr(),
153            )
154        };
155
156        match view as u32 {
157            PDFDEST_VIEW_UNKNOWN_MODE => Ok(PdfDestinationViewSettings::Unknown),
158            PDFDEST_VIEW_XYZ => {
159                if p_num_params == 3 {
160                    Ok(PdfDestinationViewSettings::SpecificCoordinatesAndZoom(
161                        x, y, zoom,
162                    ))
163                } else {
164                    Err(PdfiumError::PdfDestinationViewInvalidParameters)
165                }
166            }
167            PDFDEST_VIEW_FIT => {
168                if p_num_params == 0 {
169                    Ok(PdfDestinationViewSettings::FitPageToWindow)
170                } else {
171                    Err(PdfiumError::PdfDestinationViewInvalidParameters)
172                }
173            }
174            PDFDEST_VIEW_FITH => match p_num_params {
175                0 => Ok(PdfDestinationViewSettings::FitPageHorizontallyToWindow(
176                    None,
177                )),
178                1 => Ok(PdfDestinationViewSettings::FitPageHorizontallyToWindow(
179                    Some(PdfPoints::new(p_params[0])),
180                )),
181                _ => Err(PdfiumError::PdfDestinationViewInvalidParameters),
182            },
183            PDFDEST_VIEW_FITV => match p_num_params {
184                0 => Ok(PdfDestinationViewSettings::FitPageVerticallyToWindow(None)),
185                1 => Ok(PdfDestinationViewSettings::FitPageVerticallyToWindow(Some(
186                    PdfPoints::new(p_params[0]),
187                ))),
188                _ => Err(PdfiumError::PdfDestinationViewInvalidParameters),
189            },
190            PDFDEST_VIEW_FITR => {
191                if p_num_params == 4 {
192                    // Rectangle values are defined in p_params[] in the order
193                    // left, bottom, right, top.
194
195                    let left = p_params[0];
196                    let bottom = p_params[1];
197                    let right = p_params[2];
198                    let top = p_params[3];
199
200                    Ok(PdfDestinationViewSettings::FitPageToRectangle(
201                        PdfRect::new_from_values(bottom, left, top, right),
202                    ))
203                } else {
204                    Err(PdfiumError::PdfDestinationViewInvalidParameters)
205                }
206            }
207            PDFDEST_VIEW_FITB => {
208                if p_num_params == 0 {
209                    Ok(PdfDestinationViewSettings::FitBoundsToWindow)
210                } else {
211                    Err(PdfiumError::PdfDestinationViewInvalidParameters)
212                }
213            }
214            PDFDEST_VIEW_FITBH => match p_num_params {
215                0 => Ok(PdfDestinationViewSettings::FitBoundsHorizontallyToWindow(
216                    None,
217                )),
218                1 => Ok(PdfDestinationViewSettings::FitBoundsHorizontallyToWindow(
219                    Some(PdfPoints::new(p_params[0])),
220                )),
221                _ => Err(PdfiumError::PdfDestinationViewInvalidParameters),
222            },
223            PDFDEST_VIEW_FITBV => match p_num_params {
224                0 => Ok(PdfDestinationViewSettings::FitBoundsVerticallyToWindow(
225                    None,
226                )),
227                1 => Ok(PdfDestinationViewSettings::FitBoundsVerticallyToWindow(
228                    Some(PdfPoints::new(p_params[0])),
229                )),
230                _ => Err(PdfiumError::PdfDestinationViewInvalidParameters),
231            },
232            _ => Err(PdfiumError::UnknownPdfDestinationViewType),
233        }
234    }
235}
236
237/// The page and region, if any, that will be the target of any behaviour that will occur
238/// when the user interacts with a link in a PDF viewer.
239pub struct PdfDestination<'a> {
240    document_handle: FPDF_DOCUMENT,
241    destination_handle: FPDF_DEST,
242    lifetime: PhantomData<&'a FPDF_DEST>,
243}
244
245impl<'a> PdfDestination<'a> {
246    // TODO: AJRC - 18/2/23 - as the PdfDestination struct is fleshed out, the example at
247    // examples/links.rs should be expanded to demonstrate the new functionality.
248
249    pub(crate) fn from_pdfium(
250        document_handle: FPDF_DOCUMENT,
251        destination_handle: FPDF_DEST,
252    ) -> Self {
253        PdfDestination {
254            document_handle,
255            destination_handle,
256            lifetime: PhantomData,
257        }
258    }
259
260    /// Returns the internal `FPDF_DEST` handle for this [PdfDestination].
261    #[inline]
262    #[allow(unused)]
263    pub(crate) fn destination_handle(&self) -> FPDF_DEST {
264        self.destination_handle
265    }
266
267    /// Returns the internal `FPDF_DOCUMENT` handle for this [PdfDestination].
268    #[inline]
269    #[allow(unused)]
270    pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
271        self.document_handle
272    }
273
274    /// Returns the zero-based index of the `PdfPage` containing this [PdfDestination].
275    #[inline]
276    pub fn page_index(&self) -> Result<PdfPageIndex, PdfiumError> {
277        match unsafe {
278            self.bindings()
279                .FPDFDest_GetDestPageIndex(self.document_handle, self.destination_handle)
280        } {
281            -1 => Err(PdfiumError::DestinationPageIndexNotAvailable),
282            index => Ok(index as PdfPageIndex),
283        }
284    }
285
286    /// Returns the view settings that a PDF viewer should apply when displaying the target
287    ///`PdfPage` containing this [PdfDestination].
288    #[inline]
289    pub fn view_settings(&self) -> Result<PdfDestinationViewSettings, PdfiumError> {
290        PdfDestinationViewSettings::from_pdfium(self)
291    }
292}
293
294impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfDestination<'a> {}
295
296#[cfg(feature = "thread_safe")]
297unsafe impl<'a> Send for PdfDestination<'a> {}
298
299#[cfg(feature = "thread_safe")]
300unsafe impl<'a> Sync for PdfDestination<'a> {}