Skip to main content

pdfkit/
document_delegate.rs

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::error::Result;
8use crate::ffi;
9use crate::handle::ObjectHandle;
10use crate::notifications::PdfDocumentNotification;
11use crate::selection::PdfSelection;
12
13pub trait PdfDocumentDelegate: 'static {
14    fn handle_notification(&mut self, _notification: PdfDocumentNotification) {}
15
16    fn did_match_string(&mut self, _instance: PdfSelection) {}
17
18    fn page_class_name(&mut self) -> Option<String> {
19        None
20    }
21
22    fn annotation_class_name(&mut self, _annotation_type: &str) -> Option<String> {
23        None
24    }
25}
26
27struct DelegateState {
28    delegate: Box<dyn PdfDocumentDelegate>,
29}
30
31pub struct PdfDocumentDelegateHandle {
32    handle: ObjectHandle,
33    _state: Box<DelegateState>,
34}
35
36impl PdfDocumentDelegateHandle {
37    pub fn new(delegate: impl PdfDocumentDelegate) -> Result<Self> {
38        let mut state = Box::new(DelegateState {
39            delegate: Box::new(delegate),
40        });
41        let context = ptr::addr_of_mut!(*state).cast::<c_void>();
42        let mut out_delegate = ptr::null_mut();
43        let mut out_error = ptr::null_mut();
44        let status = unsafe {
45            ffi::pdf_document_delegate_new(
46                context,
47                Some(pdf_document_delegate_notification_trampoline),
48                Some(pdf_document_delegate_match_trampoline),
49                Some(pdf_document_delegate_page_class_name_trampoline),
50                Some(pdf_document_delegate_annotation_class_name_trampoline),
51                &mut out_delegate,
52                &mut out_error,
53            )
54        };
55        crate::util::status_result(status, out_error)?;
56        Ok(Self {
57            handle: crate::util::required_handle(out_delegate, "PDFDocumentDelegate")?,
58            _state: state,
59        })
60    }
61
62    pub(crate) fn as_handle_ptr(&self) -> *mut c_void {
63        self.handle.as_ptr()
64    }
65}
66
67impl fmt::Debug for PdfDocumentDelegateHandle {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.debug_struct("PdfDocumentDelegateHandle").finish_non_exhaustive()
70    }
71}
72
73fn duplicate_string(value: Option<String>) -> *mut c_char {
74    value.and_then(|value| CString::new(value).ok()).map_or(ptr::null_mut(), |value| unsafe {
75        libc::strdup(value.as_ptr())
76    })
77}
78
79unsafe fn delegate_state(context: *mut c_void) -> Option<&'static mut DelegateState> {
80    context.cast::<DelegateState>().as_mut()
81}
82
83unsafe extern "C" fn pdf_document_delegate_notification_trampoline(
84    context: *mut c_void,
85    raw_notification: i32,
86) {
87    let _ = catch_unwind(AssertUnwindSafe(|| {
88        let Some(state) = (unsafe { delegate_state(context) }) else {
89            return;
90        };
91        let Some(notification) = PdfDocumentNotification::from_raw(raw_notification) else {
92            return;
93        };
94        state.delegate.handle_notification(notification);
95    }));
96}
97
98unsafe extern "C" fn pdf_document_delegate_match_trampoline(
99    context: *mut c_void,
100    selection_handle: *mut c_void,
101) {
102    let _ = catch_unwind(AssertUnwindSafe(|| {
103        let Some(state) = (unsafe { delegate_state(context) }) else {
104            return;
105        };
106        let Some(handle) = (unsafe { ObjectHandle::from_retained_ptr(selection_handle) }) else {
107            return;
108        };
109        state.delegate.did_match_string(PdfSelection::from_handle(handle));
110    }));
111}
112
113unsafe extern "C" fn pdf_document_delegate_page_class_name_trampoline(
114    context: *mut c_void,
115) -> *mut c_char {
116    catch_unwind(AssertUnwindSafe(|| {
117        let Some(state) = (unsafe { delegate_state(context) }) else {
118            return ptr::null_mut();
119        };
120        duplicate_string(state.delegate.page_class_name())
121    }))
122    .unwrap_or(ptr::null_mut())
123}
124
125unsafe extern "C" fn pdf_document_delegate_annotation_class_name_trampoline(
126    context: *mut c_void,
127    annotation_type: *const c_char,
128) -> *mut c_char {
129    catch_unwind(AssertUnwindSafe(|| {
130        let Some(state) = (unsafe { delegate_state(context) }) else {
131            return ptr::null_mut();
132        };
133        let Some(annotation_type) = (!annotation_type.is_null())
134            .then(|| unsafe { CStr::from_ptr(annotation_type).to_string_lossy().into_owned() })
135        else {
136            return ptr::null_mut();
137        };
138        duplicate_string(state.delegate.annotation_class_name(&annotation_type))
139    }))
140    .unwrap_or(ptr::null_mut())
141}