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