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}