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