cairo/
ps.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    ffi::{CStr, CString},
5    io, mem,
6    ops::Deref,
7    path::Path,
8    ptr,
9};
10
11#[cfg(feature = "use_glib")]
12use glib::translate::*;
13
14use crate::{ffi, Error, PsLevel, Surface, SurfaceType};
15
16impl PsLevel {
17    pub fn as_str(self) -> Option<&'static str> {
18        unsafe {
19            let res = ffi::cairo_ps_level_to_string(self.into());
20            res.as_ref()
21                .and_then(|cstr| CStr::from_ptr(cstr as _).to_str().ok())
22        }
23    }
24}
25
26declare_surface!(PsSurface, SurfaceType::Ps);
27
28impl PsSurface {
29    #[doc(alias = "cairo_ps_surface_create")]
30    pub fn new<P: AsRef<Path>>(width: f64, height: f64, path: P) -> Result<PsSurface, Error> {
31        let path = path.as_ref().to_string_lossy().into_owned();
32        let path = CString::new(path).unwrap();
33
34        unsafe { Self::from_raw_full(ffi::cairo_ps_surface_create(path.as_ptr(), width, height)) }
35    }
36
37    for_stream_constructors!(cairo_ps_surface_create_for_stream);
38
39    #[doc(alias = "cairo_ps_get_levels")]
40    #[doc(alias = "get_levels")]
41    pub fn levels() -> impl Iterator<Item = PsLevel> {
42        let lvls_slice = unsafe {
43            let mut vers_ptr = ptr::null_mut();
44            let mut num_vers = mem::MaybeUninit::uninit();
45            ffi::cairo_ps_get_levels(&mut vers_ptr, num_vers.as_mut_ptr());
46
47            let num_vers = num_vers.assume_init();
48            if num_vers == 0 {
49                &[]
50            } else {
51                std::slice::from_raw_parts(vers_ptr, num_vers as _)
52            }
53        };
54
55        lvls_slice.iter().map(|v| PsLevel::from(*v))
56    }
57
58    #[doc(alias = "cairo_ps_surface_restrict_to_level")]
59    pub fn restrict(&self, level: PsLevel) {
60        unsafe {
61            ffi::cairo_ps_surface_restrict_to_level(self.0.to_raw_none(), level.into());
62        }
63    }
64
65    #[doc(alias = "cairo_ps_surface_get_eps")]
66    #[doc(alias = "get_eps")]
67    pub fn is_eps(&self) -> bool {
68        unsafe { ffi::cairo_ps_surface_get_eps(self.0.to_raw_none()).as_bool() }
69    }
70
71    #[doc(alias = "cairo_ps_surface_set_eps")]
72    pub fn set_eps(&self, eps: bool) {
73        unsafe {
74            ffi::cairo_ps_surface_set_eps(self.0.to_raw_none(), eps.into());
75        }
76    }
77
78    #[doc(alias = "cairo_ps_surface_set_size")]
79    pub fn set_size(&self, width: f64, height: f64) {
80        unsafe {
81            ffi::cairo_ps_surface_set_size(self.0.to_raw_none(), width, height);
82        }
83    }
84
85    #[doc(alias = "cairo_ps_surface_dsc_begin_setup")]
86    pub fn dsc_begin_setup(&self) {
87        unsafe {
88            ffi::cairo_ps_surface_dsc_begin_setup(self.0.to_raw_none());
89        }
90    }
91
92    #[doc(alias = "cairo_ps_surface_dsc_begin_page_setup")]
93    pub fn begin_page_setup(&self) {
94        unsafe {
95            ffi::cairo_ps_surface_dsc_begin_page_setup(self.0.to_raw_none());
96        }
97    }
98
99    #[doc(alias = "cairo_ps_surface_dsc_comment")]
100    pub fn dsc_comment(&self, comment: &str) {
101        let comment = CString::new(comment).unwrap();
102        unsafe {
103            ffi::cairo_ps_surface_dsc_comment(self.0.to_raw_none(), comment.as_ptr());
104        }
105    }
106}
107
108#[cfg(test)]
109mod test {
110    use tempfile::tempfile;
111
112    use super::*;
113    use crate::context::*;
114
115    fn draw(surface: &Surface) {
116        let cr = Context::new(surface).expect("Can't create a Cairo context");
117
118        // Note: Not using RGBA here as PS doesn't natively support
119        // semi-transparency and Cairo would then embed a rasterized bitmap
120
121        cr.set_line_width(25.0);
122
123        cr.set_source_rgb(1.0, 0.0, 0.0);
124        cr.line_to(0., 0.);
125        cr.line_to(100., 100.);
126        cr.stroke().expect("Surface on an invalid state");
127
128        cr.set_source_rgb(0.0, 0.0, 1.0);
129        cr.line_to(0., 100.);
130        cr.line_to(100., 0.);
131        cr.stroke().expect("Surface on an invalid state");
132    }
133
134    fn draw_in_buffer() -> Vec<u8> {
135        let buffer: Vec<u8> = vec![];
136
137        let surface = PsSurface::for_stream(100., 100., buffer).unwrap();
138        draw(&surface);
139        *surface.finish_output_stream().unwrap().downcast().unwrap()
140    }
141
142    #[test]
143    fn levels() {
144        assert!(PsSurface::levels().any(|v| v == PsLevel::_2));
145    }
146
147    #[test]
148    fn level_string() {
149        let ver_str = PsLevel::_2.as_str().unwrap();
150        assert_eq!(ver_str, "PS Level 2");
151    }
152
153    #[test]
154    fn eps() {
155        let buffer: Vec<u8> = vec![];
156        let surface = PsSurface::for_stream(100., 100., buffer).unwrap();
157        surface.set_eps(true);
158        assert!(surface.is_eps());
159    }
160
161    #[test]
162    #[cfg(unix)]
163    fn file() {
164        let surface = PsSurface::new(100., 100., "/dev/null").unwrap();
165        draw(&surface);
166        surface.finish();
167    }
168
169    #[test]
170    fn writer() {
171        let file = tempfile().expect("tempfile failed");
172        let surface = PsSurface::for_stream(100., 100., file).unwrap();
173
174        draw(&surface);
175        let stream = surface.finish_output_stream().unwrap();
176        let file = stream.downcast::<std::fs::File>().unwrap();
177
178        let buffer = draw_in_buffer();
179        let file_size = file.metadata().unwrap().len();
180        assert_eq!(file_size, buffer.len() as u64);
181    }
182
183    #[test]
184    fn ref_writer() {
185        let mut file = tempfile().expect("tempfile failed");
186        let surface = unsafe { PsSurface::for_raw_stream(100., 100., &mut file).unwrap() };
187
188        draw(&surface);
189        surface.finish_output_stream().unwrap();
190    }
191
192    #[test]
193    fn buffer() {
194        let buffer = draw_in_buffer();
195
196        let header = b"%!PS-Adobe";
197        assert_eq!(&buffer[..header.len()], header);
198    }
199
200    #[test]
201    fn custom_writer() {
202        struct CustomWriter(usize);
203
204        impl io::Write for CustomWriter {
205            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
206                self.0 += buf.len();
207                Ok(buf.len())
208            }
209
210            fn flush(&mut self) -> io::Result<()> {
211                Ok(())
212            }
213        }
214
215        let custom_writer = CustomWriter(0);
216
217        let surface = PsSurface::for_stream(20., 20., custom_writer).unwrap();
218        surface.set_size(100., 100.);
219        draw(&surface);
220        let stream = surface.finish_output_stream().unwrap();
221        let custom_writer = stream.downcast::<CustomWriter>().unwrap();
222
223        let buffer = draw_in_buffer();
224
225        assert_eq!(custom_writer.0, buffer.len());
226    }
227
228    fn with_panicky_stream() -> PsSurface {
229        struct PanicWriter;
230
231        impl io::Write for PanicWriter {
232            fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
233                panic!("panic in writer");
234            }
235            fn flush(&mut self) -> io::Result<()> {
236                Ok(())
237            }
238        }
239
240        let surface = PsSurface::for_stream(20., 20., PanicWriter).unwrap();
241        surface.finish();
242        surface
243    }
244
245    #[test]
246    #[should_panic]
247    fn finish_stream_propagates_panic() {
248        let _ = with_panicky_stream().finish_output_stream();
249    }
250}