cairo/
stream.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    any::Any,
5    cell::{Cell, RefCell},
6    io,
7    panic::AssertUnwindSafe,
8    ptr,
9    rc::Rc,
10};
11
12use crate::ffi;
13use libc::{c_double, c_uchar, c_uint, c_void};
14
15use crate::{Error, Surface, UserDataKey};
16
17macro_rules! for_stream_constructors {
18    ($constructor_ffi: ident) => {
19        /// Takes full ownership of the output stream,
20        /// which is not allowed to borrow any lifetime shorter than `'static`.
21        ///
22        /// Because the underlying `cairo_surface_t` is reference-counted,
23        /// a lifetime parameter in a Rust wrapper type would not be enough to track
24        /// how long it can keep writing to the stream.
25        pub fn for_stream<W: io::Write + 'static>(
26            width: f64,
27            height: f64,
28            stream: W,
29        ) -> Result<Self, crate::error::Error> {
30            Ok(Self(Surface::_for_stream(
31                ffi::$constructor_ffi,
32                width,
33                height,
34                stream,
35            )?))
36        }
37
38        /// Allows writing to a borrowed stream. The lifetime of the borrow is not tracked.
39        ///
40        /// # Safety
41        ///
42        /// The value that `stream` points to must live at least until the underlying `cairo_surface_t`
43        /// (which maybe be longer then the Rust `PdfSurface` wrapper, because of reference-counting),
44        /// or until the output stream is removed from the surface with [`Surface::finish_output_stream`].
45        ///
46        /// Since the former is hard to track for sure, the latter is strongly recommended.
47        /// The concrete type behind the `Box<dyn Any>` value returned by `finish_output_stream`
48        /// is private, so you won’t be able to downcast it.
49        /// But removing it anyway ensures that later writes do not go through a dangling pointer.
50        pub unsafe fn for_raw_stream<W: io::Write + 'static>(
51            width: f64,
52            height: f64,
53            stream: *mut W,
54        ) -> Result<Self, crate::error::Error> {
55            Ok(Self(Surface::_for_raw_stream(
56                ffi::$constructor_ffi,
57                width,
58                height,
59                stream,
60            )?))
61        }
62    };
63}
64
65impl Surface {
66    pub(crate) fn _for_stream<W: io::Write + 'static>(
67        constructor: Constructor,
68        width: f64,
69        height: f64,
70        stream: W,
71    ) -> Result<Self, Error> {
72        let env_rc = Rc::new(CallbackEnvironment {
73            mutable: RefCell::new(MutableCallbackEnvironment {
74                stream: Some((Box::new(stream), None)),
75                unwind_payload: None,
76            }),
77            saw_already_borrowed: Cell::new(false),
78        });
79        let env: *const CallbackEnvironment = &*env_rc;
80        unsafe {
81            let ptr = constructor(Some(write_callback::<W>), env as *mut c_void, width, height);
82            let surface = Surface::from_raw_full(ptr)?;
83            surface.set_user_data(&STREAM_CALLBACK_ENVIRONMENT, env_rc)?;
84            Ok(surface)
85        }
86    }
87
88    pub(crate) unsafe fn _for_raw_stream<W: io::Write + 'static>(
89        constructor: Constructor,
90        width: f64,
91        height: f64,
92        stream: *mut W,
93    ) -> Result<Self, Error> {
94        Self::_for_stream(
95            constructor,
96            width,
97            height,
98            RawStream(ptr::NonNull::new(stream).expect("NULL stream passed")),
99        )
100    }
101
102    /// Finish the surface, then remove and return the output stream if any.
103    ///
104    /// This calls [`Surface::finish`], to make sure pending writes are done.
105    ///
106    /// This is relevant for surfaces created for example with [`crate::PdfSurface::for_stream`].
107    ///
108    /// Use [`Box::downcast`] to recover the concrete stream type.
109    ///
110    /// # Panics
111    ///
112    /// This method panics if:
113    ///
114    /// * This method was already called for this surface, or
115    /// * This surface was not created with an output stream in the first place, or
116    /// * A previous write to this surface panicked, or
117    /// * A previous write happened while another write was ongoing, or
118    /// * A write is ongoing now.
119    ///
120    /// The latter two cases can only occur with a pathological output stream type
121    /// that accesses the same surface again from `Write::write_all`.
122    pub fn finish_output_stream(&self) -> Result<Box<dyn Any>, StreamWithError> {
123        self.finish();
124
125        let env = self
126            .user_data_ptr(&STREAM_CALLBACK_ENVIRONMENT)
127            .expect("surface without an output stream");
128
129        // Safety: since `STREAM_CALLBACK_ENVIRONMENT` is private and we never
130        // call `set_user_data` again or `remove_user_data` with it,
131        // the contract of `get_user_data_ptr` says that the user data entry
132        // lives as long as the underlying `cairo_surface_t`
133        // which is at least as long as `self`.
134        let env = unsafe { &*env.as_ptr() };
135
136        if env.saw_already_borrowed.get() {
137            panic!("The output stream’s RefCell was already borrowed when cairo attempted a write")
138        }
139
140        let mut mutable = env.mutable.borrow_mut();
141        if let Some(payload) = mutable.unwind_payload.take() {
142            std::panic::resume_unwind(payload)
143        }
144
145        let (stream, io_error) = mutable
146            .stream
147            .take()
148            .expect("output stream was already taken");
149        if let Some(error) = io_error {
150            Err(StreamWithError { stream, error })
151        } else {
152            Ok(stream)
153        }
154    }
155}
156
157pub struct StreamWithError {
158    pub stream: Box<dyn Any>,
159    pub error: io::Error,
160}
161
162impl std::fmt::Debug for StreamWithError {
163    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
164        self.error.fmt(f)
165    }
166}
167
168impl std::fmt::Display for StreamWithError {
169    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
170        self.error.fmt(f)
171    }
172}
173
174impl From<StreamWithError> for io::Error {
175    fn from(e: StreamWithError) -> Self {
176        e.error
177    }
178}
179
180pub(crate) type Constructor = unsafe extern "C" fn(
181    ffi::cairo_write_func_t,
182    *mut c_void,
183    c_double,
184    c_double,
185) -> *mut ffi::cairo_surface_t;
186
187static STREAM_CALLBACK_ENVIRONMENT: UserDataKey<CallbackEnvironment> = UserDataKey::new();
188
189struct CallbackEnvironment {
190    mutable: RefCell<MutableCallbackEnvironment>,
191    saw_already_borrowed: Cell<bool>,
192}
193
194struct MutableCallbackEnvironment {
195    stream: Option<(Box<dyn Any>, Option<io::Error>)>,
196    unwind_payload: Option<Box<dyn Any + Send + 'static>>,
197}
198
199// Safety: unwinding into C is undefined behavior (https://github.com/rust-lang/rust/issues/58794)
200// so code outside of the `catch_unwind` call must never panic.
201extern "C" fn write_callback<W: io::Write + 'static>(
202    env: *mut c_void,
203    data: *mut c_uchar,
204    length: c_uint,
205) -> ffi::cairo_status_t {
206    // This is consistent with the type of `env` in `Surface::_for_stream`.
207    let env: *const CallbackEnvironment = env as _;
208
209    // Safety: the user data entry keeps `Rc<CallbackEnvironment>` alive
210    // until the surface is destroyed.
211    // If this is called by cairo, the surface is still alive.
212    let env: &CallbackEnvironment = unsafe { &*env };
213
214    if let Ok(mut mutable) = env.mutable.try_borrow_mut() {
215        if let MutableCallbackEnvironment {
216            stream:
217                Some((
218                    stream,
219                    // Don’t attempt another write, if a previous one errored or panicked:
220                    io_error @ None,
221                )),
222            unwind_payload: unwind_payload @ None,
223        } = &mut *mutable
224        {
225            // Safety: `write_callback<W>` was instantiated in `Surface::_for_stream`
226            // with a W parameter consistent with the box that was unsized to `Box<dyn Any>`.
227            let stream = unsafe { AnyExt::downcast_mut_unchecked::<W>(&mut **stream) };
228            // Safety: this is the callback contract from cairo’s API
229            let data = unsafe {
230                if data.is_null() || length == 0 {
231                    &[]
232                } else {
233                    std::slice::from_raw_parts(data, length as usize)
234                }
235            };
236            // Because `<W as Write>::write_all` is a generic,
237            // we must conservatively assume that it can panic.
238            let result = std::panic::catch_unwind(AssertUnwindSafe(|| stream.write_all(data)));
239            match result {
240                Ok(Ok(())) => return ffi::STATUS_SUCCESS,
241                Ok(Err(error)) => {
242                    *io_error = Some(error);
243                }
244                Err(payload) => {
245                    *unwind_payload = Some(payload);
246                }
247            }
248        }
249    } else {
250        env.saw_already_borrowed.set(true)
251    }
252    Error::WriteError.into()
253}
254
255struct RawStream<W>(ptr::NonNull<W>);
256
257impl<W: io::Write> io::Write for RawStream<W> {
258    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
259        unsafe { (*self.0.as_ptr()).write(buf) }
260    }
261    fn flush(&mut self) -> io::Result<()> {
262        unsafe { (*self.0.as_ptr()).flush() }
263    }
264    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
265        unsafe { (*self.0.as_ptr()).write_all(buf) }
266    }
267}
268
269trait AnyExt {
270    /// Any::downcast_mut, but YOLO
271    unsafe fn downcast_mut_unchecked<T>(&mut self) -> &mut T {
272        let ptr = self as *mut Self as *mut T;
273        &mut *ptr
274    }
275}
276impl AnyExt for dyn Any {}