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