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