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")
70            .finish_non_exhaustive()
71    }
72}
73
74fn duplicate_string(value: Option<String>) -> *mut c_char {
75    value
76        .and_then(|value| CString::new(value).ok())
77        .map_or(ptr::null_mut(), |value| unsafe {
78            libc::strdup(value.as_ptr())
79        })
80}
81
82/// # Safety
83/// The caller must ensure that `context` is either null or a valid pointer to `DelegateState`
84/// that was obtained from `Box::into_raw()` and has not yet been freed.
85unsafe fn delegate_state(context: *mut c_void) -> Option<&'static mut DelegateState> {
86    context.cast::<DelegateState>().as_mut()
87}
88
89/// # Safety
90/// This is an extern "C" callback invoked by Swift. The caller must pass a valid, non-null
91/// `context` pointer that points to `DelegateState`. Panics are caught to prevent unwinding
92/// across the FFI boundary.
93unsafe extern "C" fn pdf_document_delegate_notification_trampoline(
94    context: *mut c_void,
95    raw_notification: i32,
96) {
97    let _ = catch_unwind(AssertUnwindSafe(|| {
98        // SAFETY: caller is responsible for providing valid context pointer
99        let Some(state) = (unsafe { delegate_state(context) }) else {
100            return;
101        };
102        let Some(notification) = PdfDocumentNotification::from_raw(raw_notification) else {
103            return;
104        };
105        state.delegate.handle_notification(notification);
106    }));
107}
108
109/// # Safety
110/// This is an extern "C" callback invoked by Swift. The caller must pass a valid, non-null
111/// `context` pointer that points to `DelegateState`, and `selection_handle` must be a
112/// retained PDFSelection pointer from Swift (or null). Panics are caught to prevent unwinding
113/// across the FFI boundary.
114unsafe extern "C" fn pdf_document_delegate_match_trampoline(
115    context: *mut c_void,
116    selection_handle: *mut c_void,
117) {
118    let _ = catch_unwind(AssertUnwindSafe(|| {
119        // SAFETY: caller is responsible for providing valid context and selection_handle pointers
120        let Some(state) = (unsafe { delegate_state(context) }) else {
121            return;
122        };
123        let Some(handle) = (unsafe { ObjectHandle::from_retained_ptr(selection_handle) }) else {
124            return;
125        };
126        state
127            .delegate
128            .did_match_string(PdfSelection::from_handle(handle));
129    }));
130}
131
132/// # Safety
133/// This is an extern "C" callback invoked by Swift. The caller must pass a valid, non-null
134/// `context` pointer that points to `DelegateState`. The returned pointer must be freed by
135/// the Swift caller. Panics are caught to prevent unwinding across the FFI boundary.
136unsafe extern "C" fn pdf_document_delegate_page_class_name_trampoline(
137    context: *mut c_void,
138) -> *mut c_char {
139    catch_unwind(AssertUnwindSafe(|| {
140        // SAFETY: caller is responsible for providing valid context pointer
141        let Some(state) = (unsafe { delegate_state(context) }) else {
142            return ptr::null_mut();
143        };
144        duplicate_string(state.delegate.page_class_name())
145    }))
146    .unwrap_or(ptr::null_mut())
147}
148
149/// # Safety
150/// This is an extern "C" callback invoked by Swift. The caller must pass a valid, non-null
151/// `context` pointer that points to `DelegateState`, and `annotation_type` must be either
152/// null or a valid C string pointer. The returned pointer must be freed by the Swift caller.
153/// Panics are caught to prevent unwinding across the FFI boundary.
154unsafe extern "C" fn pdf_document_delegate_annotation_class_name_trampoline(
155    context: *mut c_void,
156    annotation_type: *const c_char,
157) -> *mut c_char {
158    catch_unwind(AssertUnwindSafe(|| {
159        // SAFETY: caller is responsible for providing valid context and annotation_type pointers
160        let Some(state) = (unsafe { delegate_state(context) }) else {
161            return ptr::null_mut();
162        };
163        let Some(annotation_type) = (!annotation_type.is_null()).then(|| unsafe {
164            // SAFETY: checked for null above; Swift guarantees valid C string
165            CStr::from_ptr(annotation_type)
166                .to_string_lossy()
167                .into_owned()
168        }) else {
169            return ptr::null_mut();
170        };
171        duplicate_string(state.delegate.annotation_class_name(&annotation_type))
172    }))
173    .unwrap_or(ptr::null_mut())
174}