Skip to main content

pdfkit/
page_overlay_view_provider.rs

1use std::fmt;
2use std::os::raw::c_void;
3use std::panic::{catch_unwind, AssertUnwindSafe};
4use std::ptr;
5
6use crate::error::Result;
7use crate::ffi;
8use crate::handle::ObjectHandle;
9use crate::page::PdfPage;
10use crate::page_overlay_view::PdfPageOverlayView;
11use crate::view::PdfView;
12
13/// Mirrors the `PDFPageOverlayViewProvider` callback surface.
14pub trait PdfPageOverlayViewProvider: 'static {
15    /// Mirrors the corresponding `PDFPageOverlayViewProvider` callback.
16    fn overlay_view_for_page(
17        &mut self,
18        _view: PdfView,
19        _page: PdfPage,
20    ) -> Option<PdfPageOverlayView> {
21        None
22    }
23
24    /// Mirrors the corresponding `PDFPageOverlayViewProvider` callback.
25    fn will_display_overlay_view(
26        &mut self,
27        _view: PdfView,
28        _overlay_view: PdfPageOverlayView,
29        _page: PdfPage,
30    ) {
31    }
32
33    /// Mirrors the corresponding `PDFPageOverlayViewProvider` callback.
34    fn will_end_displaying_overlay_view(
35        &mut self,
36        _view: PdfView,
37        _overlay_view: PdfPageOverlayView,
38        _page: PdfPage,
39    ) {
40    }
41}
42
43struct ProviderState {
44    provider: Box<dyn PdfPageOverlayViewProvider>,
45}
46
47/// Wraps `PDFPageOverlayViewProviderHandle`.
48pub struct PdfPageOverlayViewProviderHandle {
49    handle: ObjectHandle,
50    _state: Box<ProviderState>,
51}
52
53impl PdfPageOverlayViewProviderHandle {
54    /// Registers a Rust implementation of `PDFPageOverlayViewProvider`.
55    pub fn new(provider: impl PdfPageOverlayViewProvider) -> Result<Self> {
56        let mut state = Box::new(ProviderState {
57            provider: Box::new(provider),
58        });
59        let context = ptr::addr_of_mut!(*state).cast::<c_void>();
60        let mut out_provider = ptr::null_mut();
61        let mut out_error = ptr::null_mut();
62        let status = unsafe {
63            ffi::pdf_page_overlay_view_provider_new(
64                context,
65                Some(pdf_page_overlay_view_provider_overlay_trampoline),
66                Some(pdf_page_overlay_view_provider_will_display_trampoline),
67                Some(pdf_page_overlay_view_provider_will_end_displaying_trampoline),
68                &mut out_provider,
69                &mut out_error,
70            )
71        };
72        crate::util::status_result(status, out_error)?;
73        Ok(Self {
74            handle: crate::util::required_handle(out_provider, "PDFPageOverlayViewProvider")?,
75            _state: state,
76        })
77    }
78
79    pub(crate) fn as_handle_ptr(&self) -> *mut c_void {
80        self.handle.as_ptr()
81    }
82}
83
84impl fmt::Debug for PdfPageOverlayViewProviderHandle {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.debug_struct("PdfPageOverlayViewProviderHandle")
87            .finish_non_exhaustive()
88    }
89}
90
91/// # Safety
92/// The caller must ensure that `context` is either null or a valid pointer to `ProviderState`
93/// that was obtained from `Box::into_raw()` and has not yet been freed.
94unsafe fn provider_state(context: *mut c_void) -> Option<&'static mut ProviderState> {
95    context.cast::<ProviderState>().as_mut()
96}
97
98/// Helper to convert a retained PDFView pointer to a PdfView.
99///
100/// # Safety
101/// `handle` must be either null or a valid, retained pointer to a PDFView object from Swift.
102unsafe fn retained_view(handle: *mut c_void) -> Option<PdfView> {
103    // SAFETY: caller guarantees valid handle or null
104    unsafe { ObjectHandle::from_retained_ptr(handle) }.map(PdfView::from_handle)
105}
106
107/// Helper to convert a retained PDFPage pointer to a PdfPage.
108///
109/// # Safety
110/// `handle` must be either null or a valid, retained pointer to a PDFPage object from Swift.
111unsafe fn retained_page(handle: *mut c_void) -> Option<PdfPage> {
112    // SAFETY: caller guarantees valid handle or null
113    unsafe { ObjectHandle::from_retained_ptr(handle) }.map(PdfPage::from_handle)
114}
115
116/// Helper to convert a retained PDFPageOverlayView pointer to a PdfPageOverlayView.
117///
118/// # Safety
119/// `handle` must be either null or a valid, retained pointer to a PDFPageOverlayView object from Swift.
120unsafe fn retained_overlay_view(handle: *mut c_void) -> Option<PdfPageOverlayView> {
121    // SAFETY: caller guarantees valid handle or null
122    unsafe { ObjectHandle::from_retained_ptr(handle) }.map(PdfPageOverlayView::from_handle)
123}
124
125/// # Safety
126/// This is an extern "C" callback invoked by Swift. The caller must pass a valid, non-null
127/// `context` pointer that points to `ProviderState`, and `view_handle` and `page_handle`
128/// must be retained PDFView and PDFPage pointers from Swift (or null). The returned pointer
129/// should be a retained PDFPageOverlayView or null. Panics are caught to prevent unwinding
130/// across the FFI boundary.
131unsafe extern "C" fn pdf_page_overlay_view_provider_overlay_trampoline(
132    context: *mut c_void,
133    view_handle: *mut c_void,
134    page_handle: *mut c_void,
135) -> *mut c_void {
136    catch_unwind(AssertUnwindSafe(|| {
137        // SAFETY: caller is responsible for providing valid context, view_handle, and page_handle pointers
138        let Some(state) = (unsafe { provider_state(context) }) else {
139            return ptr::null_mut();
140        };
141        let Some(view) = (unsafe { retained_view(view_handle) }) else {
142            return ptr::null_mut();
143        };
144        let Some(page) = (unsafe { retained_page(page_handle) }) else {
145            return ptr::null_mut();
146        };
147        state
148            .provider
149            .overlay_view_for_page(view, page)
150            .map_or(ptr::null_mut(), PdfPageOverlayView::into_handle_ptr)
151    }))
152    .unwrap_or(ptr::null_mut())
153}
154
155/// # Safety
156/// This is an extern "C" callback invoked by Swift. The caller must pass valid, non-null
157/// `context`, `view_handle`, `overlay_view_handle`, and `page_handle` pointers. They must
158/// all point to valid PDFPageOverlayViewProvider/PDFView/PDFPageOverlayView/PDFPage objects
159/// respectively, or be null (except context). Panics are caught to prevent unwinding across
160/// the FFI boundary.
161unsafe extern "C" fn pdf_page_overlay_view_provider_will_display_trampoline(
162    context: *mut c_void,
163    view_handle: *mut c_void,
164    overlay_view_handle: *mut c_void,
165    page_handle: *mut c_void,
166) {
167    let _ = catch_unwind(AssertUnwindSafe(|| {
168        // SAFETY: caller is responsible for providing valid context, view_handle, overlay_view_handle, and page_handle pointers
169        let Some(state) = (unsafe { provider_state(context) }) else {
170            return;
171        };
172        let Some(view) = (unsafe { retained_view(view_handle) }) else {
173            return;
174        };
175        let Some(overlay_view) = (unsafe { retained_overlay_view(overlay_view_handle) }) else {
176            return;
177        };
178        let Some(page) = (unsafe { retained_page(page_handle) }) else {
179            return;
180        };
181        state
182            .provider
183            .will_display_overlay_view(view, overlay_view, page);
184    }));
185}
186
187/// # Safety
188/// This is an extern "C" callback invoked by Swift. The caller must pass valid, non-null
189/// `context`, `view_handle`, `overlay_view_handle`, and `page_handle` pointers. They must
190/// all point to valid PDFPageOverlayViewProvider/PDFView/PDFPageOverlayView/PDFPage objects
191/// respectively, or be null (except context). Panics are caught to prevent unwinding across
192/// the FFI boundary.
193unsafe extern "C" fn pdf_page_overlay_view_provider_will_end_displaying_trampoline(
194    context: *mut c_void,
195    view_handle: *mut c_void,
196    overlay_view_handle: *mut c_void,
197    page_handle: *mut c_void,
198) {
199    let _ = catch_unwind(AssertUnwindSafe(|| {
200        // SAFETY: caller is responsible for providing valid context, view_handle, overlay_view_handle, and page_handle pointers
201        let Some(state) = (unsafe { provider_state(context) }) else {
202            return;
203        };
204        let Some(view) = (unsafe { retained_view(view_handle) }) else {
205            return;
206        };
207        let Some(overlay_view) = (unsafe { retained_overlay_view(overlay_view_handle) }) else {
208            return;
209        };
210        let Some(page) = (unsafe { retained_page(page_handle) }) else {
211            return;
212        };
213        state
214            .provider
215            .will_end_displaying_overlay_view(view, overlay_view, page);
216    }));
217}