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> {}