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