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