1use std::ffi::{CStr, CString};
2use std::fmt;
3use std::os::raw::{c_char, c_void};
4use std::panic::{catch_unwind, AssertUnwindSafe};
5use std::ptr;
6
7use crate::action_remote_goto::PdfActionRemoteGoTo;
8use crate::error::Result;
9use crate::ffi;
10use crate::handle::ObjectHandle;
11use crate::view::PdfView;
12
13pub trait PdfViewDelegate: 'static {
15 fn handle_link_click(&mut self, _view: PdfView, _url: &str) -> bool {
17 false
18 }
19
20 fn will_change_scale_factor(&mut self, _view: PdfView, scale_factor: f64) -> f64 {
22 scale_factor.clamp(0.1, 10.0)
23 }
24
25 fn print_job_title(&mut self, _view: PdfView) -> Option<String> {
27 None
28 }
29
30 fn perform_print(&mut self, _view: PdfView) -> bool {
32 false
33 }
34
35 fn perform_find(&mut self, _view: PdfView) -> bool {
37 false
38 }
39
40 fn perform_go_to_page(&mut self, _view: PdfView) -> bool {
42 false
43 }
44
45 fn open_pdf_for_remote_goto_action(
47 &mut self,
48 _view: PdfView,
49 _action: PdfActionRemoteGoTo,
50 ) -> bool {
51 false
52 }
53}
54
55struct DelegateState {
56 delegate: Box<dyn PdfViewDelegate>,
57}
58
59pub struct PdfViewDelegateHandle {
61 handle: ObjectHandle,
62 _state: Box<DelegateState>,
63}
64
65impl PdfViewDelegateHandle {
66 pub fn new(delegate: impl PdfViewDelegate) -> Result<Self> {
68 let mut state = Box::new(DelegateState {
69 delegate: Box::new(delegate),
70 });
71 let context = ptr::addr_of_mut!(*state).cast::<c_void>();
72 let mut out_delegate = ptr::null_mut();
73 let mut out_error = ptr::null_mut();
74 let status = unsafe {
75 ffi::pdf_view_delegate_new(
76 context,
77 Some(pdf_view_delegate_link_click_trampoline),
78 Some(pdf_view_delegate_scale_factor_trampoline),
79 Some(pdf_view_delegate_print_job_title_trampoline),
80 Some(pdf_view_delegate_perform_print_trampoline),
81 Some(pdf_view_delegate_perform_find_trampoline),
82 Some(pdf_view_delegate_perform_go_to_page_trampoline),
83 Some(pdf_view_delegate_remote_goto_trampoline),
84 &mut out_delegate,
85 &mut out_error,
86 )
87 };
88 crate::util::status_result(status, out_error)?;
89 Ok(Self {
90 handle: crate::util::required_handle(out_delegate, "PDFViewDelegate")?,
91 _state: state,
92 })
93 }
94
95 pub(crate) fn as_handle_ptr(&self) -> *mut c_void {
96 self.handle.as_ptr()
97 }
98}
99
100impl fmt::Debug for PdfViewDelegateHandle {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 f.debug_struct("PdfViewDelegateHandle")
103 .finish_non_exhaustive()
104 }
105}
106
107fn duplicate_string(value: Option<String>) -> *mut c_char {
108 value
109 .and_then(|value| CString::new(value).ok())
110 .map_or(ptr::null_mut(), |value| unsafe {
111 libc::strdup(value.as_ptr())
112 })
113}
114
115unsafe fn delegate_state(context: *mut c_void) -> Option<&'static mut DelegateState> {
119 context.cast::<DelegateState>().as_mut()
120}
121
122unsafe fn retained_view(handle: *mut c_void) -> Option<PdfView> {
127 unsafe { ObjectHandle::from_retained_ptr(handle) }.map(PdfView::from_handle)
129}
130
131unsafe fn retained_remote_goto_action(handle: *mut c_void) -> Option<PdfActionRemoteGoTo> {
136 unsafe { ObjectHandle::from_retained_ptr(handle) }.map(PdfActionRemoteGoTo::from_handle)
138}
139
140unsafe extern "C" fn pdf_view_delegate_link_click_trampoline(
146 context: *mut c_void,
147 view_handle: *mut c_void,
148 url: *const c_char,
149) -> i32 {
150 catch_unwind(AssertUnwindSafe(|| {
151 let Some(state) = (unsafe { delegate_state(context) }) else {
153 return 0;
154 };
155 let Some(view) = (unsafe { retained_view(view_handle) }) else {
156 return 0;
157 };
158 let Some(url) = (!url.is_null()).then(|| unsafe {
159 CStr::from_ptr(url).to_string_lossy().into_owned()
161 }) else {
162 return 0;
163 };
164 i32::from(state.delegate.handle_link_click(view, &url))
165 }))
166 .unwrap_or(0)
167}
168
169unsafe extern "C" fn pdf_view_delegate_scale_factor_trampoline(
175 context: *mut c_void,
176 view_handle: *mut c_void,
177 scale_factor: f64,
178) -> f64 {
179 catch_unwind(AssertUnwindSafe(|| {
180 let Some(state) = (unsafe { delegate_state(context) }) else {
182 return scale_factor.clamp(0.1, 10.0);
183 };
184 let Some(view) = (unsafe { retained_view(view_handle) }) else {
185 return scale_factor.clamp(0.1, 10.0);
186 };
187 state.delegate.will_change_scale_factor(view, scale_factor)
188 }))
189 .unwrap_or_else(|_| scale_factor.clamp(0.1, 10.0))
190}
191
192unsafe extern "C" fn pdf_view_delegate_print_job_title_trampoline(
193 context: *mut c_void,
194 view_handle: *mut c_void,
195) -> *mut c_char {
196 catch_unwind(AssertUnwindSafe(|| {
197 let Some(state) = (unsafe { delegate_state(context) }) else {
198 return ptr::null_mut();
199 };
200 let Some(view) = (unsafe { retained_view(view_handle) }) else {
201 return ptr::null_mut();
202 };
203 duplicate_string(state.delegate.print_job_title(view))
204 }))
205 .unwrap_or(ptr::null_mut())
206}
207
208unsafe extern "C" fn pdf_view_delegate_perform_print_trampoline(
209 context: *mut c_void,
210 view_handle: *mut c_void,
211) -> i32 {
212 catch_unwind(AssertUnwindSafe(|| {
213 let Some(state) = (unsafe { delegate_state(context) }) else {
214 return 0;
215 };
216 let Some(view) = (unsafe { retained_view(view_handle) }) else {
217 return 0;
218 };
219 i32::from(state.delegate.perform_print(view))
220 }))
221 .unwrap_or(0)
222}
223
224unsafe extern "C" fn pdf_view_delegate_perform_find_trampoline(
225 context: *mut c_void,
226 view_handle: *mut c_void,
227) -> i32 {
228 catch_unwind(AssertUnwindSafe(|| {
229 let Some(state) = (unsafe { delegate_state(context) }) else {
230 return 0;
231 };
232 let Some(view) = (unsafe { retained_view(view_handle) }) else {
233 return 0;
234 };
235 i32::from(state.delegate.perform_find(view))
236 }))
237 .unwrap_or(0)
238}
239
240unsafe extern "C" fn pdf_view_delegate_perform_go_to_page_trampoline(
241 context: *mut c_void,
242 view_handle: *mut c_void,
243) -> i32 {
244 catch_unwind(AssertUnwindSafe(|| {
245 let Some(state) = (unsafe { delegate_state(context) }) else {
246 return 0;
247 };
248 let Some(view) = (unsafe { retained_view(view_handle) }) else {
249 return 0;
250 };
251 i32::from(state.delegate.perform_go_to_page(view))
252 }))
253 .unwrap_or(0)
254}
255
256unsafe extern "C" fn pdf_view_delegate_remote_goto_trampoline(
257 context: *mut c_void,
258 view_handle: *mut c_void,
259 action_handle: *mut c_void,
260) -> i32 {
261 catch_unwind(AssertUnwindSafe(|| {
262 let Some(state) = (unsafe { delegate_state(context) }) else {
263 return 0;
264 };
265 let Some(view) = (unsafe { retained_view(view_handle) }) else {
266 return 0;
267 };
268 let Some(action) = (unsafe { retained_remote_goto_action(action_handle) }) else {
269 return 0;
270 };
271 i32::from(state.delegate.open_pdf_for_remote_goto_action(view, action))
272 }))
273 .unwrap_or(0)
274}