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: *mut 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 std::io::ErrorKind;
156
157    use super::*;
158    use crate::enums::Format;
159
160    struct IoErrorReader;
161
162    // A reader that always returns an error
163    impl Read for IoErrorReader {
164        fn read(&mut self, _: &mut [u8]) -> Result<usize, io::Error> {
165            Err(io::Error::new(ErrorKind::Other, "yikes!"))
166        }
167    }
168
169    #[test]
170    fn valid_png_reads_correctly() {
171        // A 1x1 PNG, RGB, no alpha, with a single pixel with (42, 42, 42) values
172        let png_data: Vec<u8> = vec![
173            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
174            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
175            0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
176            0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02, 0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,
177            0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
178        ];
179
180        let r = ImageSurface::create_from_png(&mut &png_data[..]);
181        assert!(r.is_ok());
182
183        let mut surface = r.unwrap();
184        assert_eq!(surface.width(), 1);
185        assert_eq!(surface.height(), 1);
186        assert_eq!(surface.format(), Format::Rgb24);
187
188        let data = surface.data().unwrap();
189        assert!(data.len() >= 3);
190
191        let slice = &data[0..3];
192        assert_eq!(slice[0], 42);
193        assert_eq!(slice[1], 42);
194        assert_eq!(slice[2], 42);
195    }
196
197    #[cfg(not(target_os = "macos"))]
198    #[test]
199    fn invalid_png_yields_error() {
200        let png_data: Vec<u8> = vec![
201            //      v--- this byte is modified
202            0x89, 0x40, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48,
203            0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x00, 0x00,
204            0x00, 0x90, 0x77, 0x53, 0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08,
205            0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02, 0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,
206            0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
207        ];
208
209        match ImageSurface::create_from_png(&mut &png_data[..]) {
210            Err(IoError::Cairo(_)) => (),
211            _ => unreachable!(),
212        }
213    }
214
215    #[cfg(not(target_os = "macos"))]
216    #[test]
217    fn io_error_yields_cairo_read_error() {
218        let mut r = IoErrorReader;
219
220        match ImageSurface::create_from_png(&mut r) {
221            Err(IoError::Cairo(Error::ReadError)) => (),
222            _ => unreachable!(),
223        }
224    }
225}