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