cairo/
surface_png.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    io::{self, Read, Write},
6    panic::AssertUnwindSafe,
7    slice,
8};
9
10use crate::ffi;
11use libc::{c_uint, c_void};
12
13use crate::{utils::status_to_result, Error, ImageSurface, IoError, Surface};
14
15struct ReadEnv<'a, R: 'a + Read> {
16    reader: &'a mut R,
17    io_error: Option<io::Error>,
18    unwind_payload: Option<Box<dyn Any + Send + 'static>>,
19}
20
21unsafe extern "C" fn read_func<R: Read>(
22    closure: *mut c_void,
23    data: *mut u8,
24    len: c_uint,
25) -> crate::ffi::cairo_status_t {
26    let read_env: &mut ReadEnv<R> = &mut *(closure as *mut ReadEnv<R>);
27
28    // Don’t attempt another read, if a previous one errored or panicked:
29    if read_env.io_error.is_some() || read_env.unwind_payload.is_some() {
30        return Error::ReadError.into();
31    }
32
33    let buffer = if data.is_null() || len == 0 {
34        &mut []
35    } else {
36        slice::from_raw_parts_mut(data, len as usize)
37    };
38    let result = std::panic::catch_unwind(AssertUnwindSafe(|| read_env.reader.read_exact(buffer)));
39    match result {
40        Ok(Ok(())) => ffi::STATUS_SUCCESS,
41        Ok(Err(error)) => {
42            read_env.io_error = Some(error);
43            Error::ReadError.into()
44        }
45        Err(payload) => {
46            read_env.unwind_payload = Some(payload);
47            Error::ReadError.into()
48        }
49    }
50}
51
52struct WriteEnv<'a, W: 'a + Write> {
53    writer: &'a mut W,
54    io_error: Option<io::Error>,
55    unwind_payload: Option<Box<dyn Any + Send + 'static>>,
56}
57
58unsafe extern "C" fn write_func<W: Write>(
59    closure: *mut c_void,
60    data: *const u8,
61    len: c_uint,
62) -> crate::ffi::cairo_status_t {
63    let write_env: &mut WriteEnv<W> = &mut *(closure as *mut WriteEnv<W>);
64
65    // Don’t attempt another write, if a previous one errored or panicked:
66    if write_env.io_error.is_some() || write_env.unwind_payload.is_some() {
67        return Error::WriteError.into();
68    }
69
70    let buffer = if data.is_null() || len == 0 {
71        &[]
72    } else {
73        slice::from_raw_parts(data, len as usize)
74    };
75    let result = std::panic::catch_unwind(AssertUnwindSafe(|| write_env.writer.write_all(buffer)));
76    match result {
77        Ok(Ok(())) => ffi::STATUS_SUCCESS,
78        Ok(Err(error)) => {
79            write_env.io_error = Some(error);
80            Error::WriteError.into()
81        }
82        Err(payload) => {
83            write_env.unwind_payload = Some(payload);
84            Error::WriteError.into()
85        }
86    }
87}
88
89impl ImageSurface {
90    #[doc(alias = "cairo_image_surface_create_from_png_stream")]
91    pub fn create_from_png<R: Read>(stream: &mut R) -> Result<ImageSurface, IoError> {
92        let mut env = ReadEnv {
93            reader: stream,
94            io_error: None,
95            unwind_payload: None,
96        };
97        unsafe {
98            let raw_surface = ffi::cairo_image_surface_create_from_png_stream(
99                Some(read_func::<R>),
100                &mut env as *mut ReadEnv<R> as *mut c_void,
101            );
102
103            let surface = ImageSurface::from_raw_full(raw_surface)?;
104
105            if let Some(payload) = env.unwind_payload {
106                std::panic::resume_unwind(payload)
107            }
108
109            match env.io_error {
110                None => Ok(surface),
111                Some(err) => Err(IoError::Io(err)),
112            }
113        }
114    }
115}
116
117impl Surface {
118    // rustdoc-stripper-ignore-next
119    /// This function writes the surface as a PNG image to the given stream.
120    ///
121    /// If the underlying surface does not support being written as a PNG, this will return
122    /// [`Error::SurfaceTypeMismatch`]
123    #[doc(alias = "cairo_surface_write_to_png_stream")]
124    #[doc(alias = "cairo_surface_write_to_png")]
125    pub fn write_to_png<W: Write>(&self, stream: &mut W) -> Result<(), IoError> {
126        let mut env = WriteEnv {
127            writer: stream,
128            io_error: None,
129            unwind_payload: None,
130        };
131        let status = unsafe {
132            ffi::cairo_surface_write_to_png_stream(
133                self.to_raw_none(),
134                Some(write_func::<W>),
135                &mut env as *mut WriteEnv<W> as *mut c_void,
136            )
137        };
138
139        if let Some(payload) = env.unwind_payload {
140            std::panic::resume_unwind(payload)
141        }
142
143        match env.io_error {
144            None => match status_to_result(status) {
145                Err(err) => Err(IoError::Cairo(err)),
146                Ok(_) => Ok(()),
147            },
148            Some(err) => Err(IoError::Io(err)),
149        }
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156    use crate::enums::Format;
157
158    struct IoErrorReader;
159
160    // A reader that always returns an error
161    impl Read for IoErrorReader {
162        fn read(&mut self, _: &mut [u8]) -> Result<usize, io::Error> {
163            Err(io::Error::other("yikes!"))
164        }
165    }
166
167    #[test]
168    fn valid_png_reads_correctly() {
169        // A 1x1 PNG, RGB, no alpha, with a single pixel with (42, 42, 42) values
170        let png_data: Vec<u8> = vec![
171            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
172            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
173            0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
174            0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02, 0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,
175            0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
176        ];
177
178        let r = ImageSurface::create_from_png(&mut &png_data[..]);
179        assert!(r.is_ok());
180
181        let mut surface = r.unwrap();
182        assert_eq!(surface.width(), 1);
183        assert_eq!(surface.height(), 1);
184        assert_eq!(surface.format(), Format::Rgb24);
185
186        let data = surface.data().unwrap();
187        assert!(data.len() >= 3);
188
189        let slice = &data[0..3];
190        assert_eq!(slice[0], 42);
191        assert_eq!(slice[1], 42);
192        assert_eq!(slice[2], 42);
193    }
194
195    #[cfg(not(target_os = "macos"))]
196    #[test]
197    fn invalid_png_yields_error() {
198        let png_data: Vec<u8> = vec![
199            //      v--- this byte is modified
200            0x89, 0x40, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
201            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
202            0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
203            0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02, 0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,
204            0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
205        ];
206
207        match ImageSurface::create_from_png(&mut &png_data[..]) {
208            Err(IoError::Cairo(_)) => (),
209            _ => unreachable!(),
210        }
211    }
212
213    #[cfg(not(target_os = "macos"))]
214    #[test]
215    fn io_error_yields_cairo_read_error() {
216        let mut r = IoErrorReader;
217
218        match ImageSurface::create_from_png(&mut r) {
219            Err(IoError::Cairo(Error::ReadError)) => (),
220            _ => unreachable!(),
221        }
222    }
223}