1 // Take a look at the license at the top of the repository in the LICENSE file. 2 3 #[cfg(any(all(feature = "svg", feature = "v1_16"), feature = "dox"))] 4 use crate::enums::SvgUnit; 5 use crate::enums::{SurfaceType, SvgVersion}; 6 use crate::error::Error; 7 use std::convert::TryFrom; 8 use std::ffi::{CStr, CString}; 9 use std::fmt; 10 use std::io; 11 use std::mem; 12 use std::ops::Deref; 13 #[cfg(not(windows))] 14 use std::os::unix::prelude::*; 15 use std::path::Path; 16 use std::ptr; 17 18 use crate::surface::Surface; 19 20 #[cfg(feature = "use_glib")] 21 use glib::translate::*; 22 23 impl SvgVersion { as_str(self) -> Option<&'static str>24 pub fn as_str(self) -> Option<&'static str> { 25 unsafe { 26 let res = ffi::cairo_svg_version_to_string(self.into()); 27 res.as_ref() 28 .and_then(|cstr| CStr::from_ptr(cstr as _).to_str().ok()) 29 } 30 } 31 } 32 33 declare_surface!(SvgSurface, SurfaceType::Svg); 34 35 impl SvgSurface { 36 #[doc(alias = "cairo_svg_surface_create")] new<P: AsRef<Path>>( width: f64, height: f64, path: Option<P>, ) -> Result<SvgSurface, Error>37 pub fn new<P: AsRef<Path>>( 38 width: f64, 39 height: f64, 40 path: Option<P>, 41 ) -> Result<SvgSurface, Error> { 42 #[cfg(not(windows))] 43 let path = path.map(|p| { 44 CString::new(p.as_ref().as_os_str().as_bytes()).expect("Invalid path with NULL bytes") 45 }); 46 #[cfg(windows)] 47 let path = path.map(|p| { 48 let path_str = p 49 .as_ref() 50 .to_str() 51 .expect("Path can't be represented as UTF-8") 52 .to_owned(); 53 if path_str.starts_with("\\\\?\\") { 54 CString::new(path_str[4..].as_bytes()) 55 } else { 56 CString::new(path_str.as_bytes()) 57 } 58 .expect("Invalid path with NUL bytes") 59 }); 60 61 unsafe { 62 Ok(Self(Surface::from_raw_full( 63 ffi::cairo_svg_surface_create( 64 path.as_ref().map(|p| p.as_ptr()).unwrap_or(ptr::null()), 65 width, 66 height, 67 ), 68 )?)) 69 } 70 } 71 72 for_stream_constructors!(cairo_svg_surface_create_for_stream); 73 74 #[doc(alias = "cairo_svg_get_versions")] 75 #[doc(alias = "get_versions")] 76 pub fn versions() -> impl Iterator<Item = SvgVersion> { 77 let vers_slice = unsafe { 78 let mut vers_ptr = ptr::null_mut(); 79 let mut num_vers = mem::MaybeUninit::uninit(); 80 ffi::cairo_svg_get_versions(&mut vers_ptr, num_vers.as_mut_ptr()); 81 82 std::slice::from_raw_parts(vers_ptr, num_vers.assume_init() as _) 83 }; 84 85 vers_slice.iter().map(|v| SvgVersion::from(*v)) 86 } 87 88 #[doc(alias = "cairo_svg_surface_restrict_to_version")] 89 pub fn restrict(&self, version: SvgVersion) { 90 unsafe { 91 ffi::cairo_svg_surface_restrict_to_version(self.0.to_raw_none(), version.into()); 92 } 93 } 94 95 #[cfg(any(all(feature = "svg", feature = "v1_16"), feature = "dox"))] 96 #[doc(alias = "cairo_svg_surface_set_document_unit")] 97 pub fn set_document_unit(&mut self, unit: SvgUnit) { 98 unsafe { 99 ffi::cairo_svg_surface_set_document_unit(self.0.to_raw_none(), unit.into()); 100 } 101 } 102 103 #[cfg(any(all(feature = "svg", feature = "v1_16"), feature = "dox"))] 104 #[doc(alias = "cairo_svg_surface_get_document_unit")] 105 #[doc(alias = "get_document_unit")] 106 pub fn document_unit(&self) -> SvgUnit { 107 unsafe { 108 SvgUnit::from(ffi::cairo_svg_surface_get_document_unit( 109 self.0.to_raw_none(), 110 )) 111 } 112 } 113 } 114 115 #[cfg(test)] 116 mod test { 117 use super::*; 118 use crate::context::*; 119 use tempfile::{tempfile, NamedTempFile}; 120 121 fn draw(surface: &Surface) { 122 let cr = Context::new(surface).expect("Can't create a Cairo context"); 123 124 cr.set_line_width(25.0); 125 126 cr.set_source_rgba(1.0, 0.0, 0.0, 0.5); 127 cr.line_to(0., 0.); 128 cr.line_to(100., 100.); 129 cr.stroke().expect("Surface on an invalid state"); 130 131 cr.set_source_rgba(0.0, 0.0, 1.0, 0.5); 132 cr.line_to(0., 100.); 133 cr.line_to(100., 0.); 134 cr.stroke().expect("Surface on an invalid state"); 135 } 136 137 fn draw_in_buffer() -> Vec<u8> { 138 let buffer: Vec<u8> = vec![]; 139 140 let surface = SvgSurface::for_stream(100., 100., buffer).unwrap(); 141 draw(&surface); 142 *surface.finish_output_stream().unwrap().downcast().unwrap() 143 } 144 145 fn assert_len_close_enough(len_a: usize, len_b: usize) { 146 // It seems cairo randomizes some element IDs which might make one svg slightly 147 // larger than the other. Here we make sure the difference is within ~10%. 148 let len_diff = (len_a as isize - len_b as isize).abs() as usize; 149 assert!(len_diff < len_b / 10); 150 } 151 152 #[test] 153 fn versions() { 154 assert!(SvgSurface::versions().any(|v| v == SvgVersion::_1_1)); 155 } 156 157 #[test] 158 fn version_string() { 159 let ver_str = SvgVersion::_1_1.as_str().unwrap(); 160 assert_eq!(ver_str, "SVG 1.1"); 161 } 162 163 #[test] 164 fn without_file() { 165 let surface = SvgSurface::new(100., 100., None::<&Path>).unwrap(); 166 draw(&surface); 167 surface.finish(); 168 } 169 170 #[test] 171 fn file() { 172 let file = NamedTempFile::new().expect("tempfile failed"); 173 let surface = SvgSurface::new(100., 100., Some(&file.path())).unwrap(); 174 draw(&surface); 175 surface.finish(); 176 } 177 178 #[test] 179 fn writer() { 180 let file = tempfile().expect("tempfile failed"); 181 let surface = SvgSurface::for_stream(100., 100., file).unwrap(); 182 183 draw(&surface); 184 let stream = surface.finish_output_stream().unwrap(); 185 let file = stream.downcast::<std::fs::File>().unwrap(); 186 187 let buffer = draw_in_buffer(); 188 let file_size = file.metadata().unwrap().len(); 189 190 assert_len_close_enough(file_size as usize, buffer.len()); 191 } 192 193 #[test] 194 fn ref_writer() { 195 let mut file = tempfile().expect("tempfile failed"); 196 let surface = unsafe { SvgSurface::for_raw_stream(100., 100., &mut file).unwrap() }; 197 198 draw(&surface); 199 surface.finish_output_stream().unwrap(); 200 } 201 202 #[test] 203 fn buffer() { 204 let buffer = draw_in_buffer(); 205 206 let header = b"<?xml"; 207 assert_eq!(&buffer[..header.len()], header); 208 } 209 210 #[test] 211 fn custom_writer() { 212 use std::fs; 213 214 struct CustomWriter(usize, fs::File); 215 216 impl io::Write for CustomWriter { 217 fn write(&mut self, buf: &[u8]) -> io::Result<usize> { 218 self.1.write_all(buf)?; 219 220 self.0 += buf.len(); 221 Ok(buf.len()) 222 } 223 224 fn flush(&mut self) -> io::Result<()> { 225 Ok(()) 226 } 227 } 228 229 let file = tempfile().expect("tempfile failed"); 230 let custom_writer = CustomWriter(0, file); 231 232 let surface = SvgSurface::for_stream(100., 100., custom_writer).unwrap(); 233 draw(&surface); 234 let stream = surface.finish_output_stream().unwrap(); 235 let custom_writer = stream.downcast::<CustomWriter>().unwrap(); 236 237 let buffer = draw_in_buffer(); 238 239 assert_len_close_enough(custom_writer.0, buffer.len()); 240 } 241 242 fn with_panicky_stream() -> SvgSurface { 243 struct PanicWriter; 244 245 impl io::Write for PanicWriter { 246 fn write(&mut self, _buf: &[u8]) -> io::Result<usize> { 247 panic!("panic in writer"); 248 } 249 fn flush(&mut self) -> io::Result<()> { 250 Ok(()) 251 } 252 } 253 254 let surface = SvgSurface::for_stream(20., 20., PanicWriter).unwrap(); 255 surface.finish(); 256 surface 257 } 258 259 #[test] 260 #[should_panic] 261 fn finish_stream_propagates_panic() { 262 let _ = with_panicky_stream().finish_output_stream(); 263 } 264 } 265