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