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