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 #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))]
13 use crate::enums::{PdfMetadata, PdfOutline};
14 use crate::enums::{PdfVersion, SurfaceType};
15 use crate::error::Error;
16 use crate::surface::Surface;
17 
18 #[cfg(feature = "use_glib")]
19 use glib::translate::*;
20 
21 impl PdfVersion {
as_str(self) -> Option<&'static str>22     pub fn as_str(self) -> Option<&'static str> {
23         unsafe {
24             let res = ffi::cairo_pdf_version_to_string(self.into());
25             res.as_ref()
26                 .and_then(|cstr| CStr::from_ptr(cstr as _).to_str().ok())
27         }
28     }
29 }
30 
31 declare_surface!(PdfSurface, SurfaceType::Pdf);
32 
33 impl PdfSurface {
34     #[doc(alias = "cairo_pdf_surface_create")]
new<P: AsRef<Path>>(width: f64, height: f64, path: P) -> Result<Self, Error>35     pub fn new<P: AsRef<Path>>(width: f64, height: f64, path: P) -> Result<Self, Error> {
36         let path = path.as_ref().to_string_lossy().into_owned();
37         let path = CString::new(path).unwrap();
38 
39         unsafe { Self::from_raw_full(ffi::cairo_pdf_surface_create(path.as_ptr(), width, height)) }
40     }
41 
42     for_stream_constructors!(cairo_pdf_surface_create_for_stream);
43 
44     #[doc(alias = "cairo_pdf_get_versions")]
45     #[doc(alias = "get_versions")]
versions() -> impl Iterator<Item = PdfVersion>46     pub fn versions() -> impl Iterator<Item = PdfVersion> {
47         let vers_slice = unsafe {
48             let mut vers_ptr = ptr::null_mut();
49             let mut num_vers = mem::MaybeUninit::uninit();
50             ffi::cairo_pdf_get_versions(&mut vers_ptr, num_vers.as_mut_ptr());
51 
52             std::slice::from_raw_parts(vers_ptr, num_vers.assume_init() as _)
53         };
54         vers_slice.iter().map(|v| PdfVersion::from(*v))
55     }
56 
57     #[doc(alias = "cairo_pdf_surface_restrict_to_version")]
restrict(&self, version: PdfVersion) -> Result<(), Error>58     pub fn restrict(&self, version: PdfVersion) -> Result<(), Error> {
59         unsafe {
60             ffi::cairo_pdf_surface_restrict_to_version(self.0.to_raw_none(), version.into());
61         }
62         self.status()
63     }
64 
65     #[doc(alias = "cairo_pdf_surface_set_size")]
set_size(&self, width: f64, height: f64) -> Result<(), Error>66     pub fn set_size(&self, width: f64, height: f64) -> Result<(), Error> {
67         unsafe {
68             ffi::cairo_pdf_surface_set_size(self.0.to_raw_none(), width, height);
69         }
70         self.status()
71     }
72 
73     #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))]
74     #[doc(alias = "cairo_pdf_surface_set_metadata")]
set_metadata(&self, metadata: PdfMetadata, value: &str) -> Result<(), Error>75     pub fn set_metadata(&self, metadata: PdfMetadata, value: &str) -> Result<(), Error> {
76         let value = CString::new(value).unwrap();
77         unsafe {
78             ffi::cairo_pdf_surface_set_metadata(
79                 self.0.to_raw_none(),
80                 metadata.into(),
81                 value.as_ptr(),
82             );
83         }
84         self.status()
85     }
86 
87     #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))]
88     #[doc(alias = "cairo_pdf_surface_set_page_label")]
set_page_label(&self, label: &str) -> Result<(), Error>89     pub fn set_page_label(&self, label: &str) -> Result<(), Error> {
90         let label = CString::new(label).unwrap();
91         unsafe {
92             ffi::cairo_pdf_surface_set_page_label(self.0.to_raw_none(), label.as_ptr());
93         }
94         self.status()
95     }
96 
97     #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))]
98     #[doc(alias = "cairo_pdf_surface_set_thumbnail_size")]
set_thumbnail_size(&self, width: i32, height: i32) -> Result<(), Error>99     pub fn set_thumbnail_size(&self, width: i32, height: i32) -> Result<(), Error> {
100         unsafe {
101             ffi::cairo_pdf_surface_set_thumbnail_size(
102                 self.0.to_raw_none(),
103                 width as _,
104                 height as _,
105             );
106         }
107         self.status()
108     }
109 
110     #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))]
111     #[doc(alias = "cairo_pdf_surface_add_outline")]
add_outline( &self, parent_id: i32, name: &str, link_attribs: &str, flags: PdfOutline, ) -> Result<i32, Error>112     pub fn add_outline(
113         &self,
114         parent_id: i32,
115         name: &str,
116         link_attribs: &str,
117         flags: PdfOutline,
118     ) -> Result<i32, Error> {
119         let name = CString::new(name).unwrap();
120         let link_attribs = CString::new(link_attribs).unwrap();
121 
122         let res = unsafe {
123             ffi::cairo_pdf_surface_add_outline(
124                 self.0.to_raw_none(),
125                 parent_id,
126                 name.as_ptr(),
127                 link_attribs.as_ptr(),
128                 flags.bits() as _,
129             ) as _
130         };
131 
132         self.status()?;
133         Ok(res)
134     }
135 }
136 
137 #[cfg(test)]
138 mod test {
139     use super::*;
140     use crate::context::*;
141     use tempfile::tempfile;
142 
draw(surface: &Surface)143     fn draw(surface: &Surface) {
144         let cr = Context::new(surface).expect("Can't create a Cairo context");
145 
146         cr.set_line_width(25.0);
147 
148         cr.set_source_rgba(1.0, 0.0, 0.0, 0.5);
149         cr.line_to(0., 0.);
150         cr.line_to(100., 100.);
151         cr.stroke().expect("Surface on an invalid state");
152 
153         cr.set_source_rgba(0.0, 0.0, 1.0, 0.5);
154         cr.line_to(0., 100.);
155         cr.line_to(100., 0.);
156         cr.stroke().expect("Surface on an invalid state");
157     }
158 
draw_in_buffer() -> Vec<u8>159     fn draw_in_buffer() -> Vec<u8> {
160         let buffer: Vec<u8> = vec![];
161 
162         let surface = PdfSurface::for_stream(100., 100., buffer).unwrap();
163         draw(&surface);
164         *surface.finish_output_stream().unwrap().downcast().unwrap()
165     }
166 
167     #[test]
versions()168     fn versions() {
169         assert!(PdfSurface::versions().any(|v| v == PdfVersion::_1_4));
170     }
171 
172     #[test]
version_string()173     fn version_string() {
174         let ver_str = PdfVersion::_1_4.as_str().unwrap();
175         assert_eq!(ver_str, "PDF 1.4");
176     }
177 
178     #[test]
179     #[cfg(unix)]
file()180     fn file() {
181         let surface = PdfSurface::new(100., 100., "/dev/null").unwrap();
182         draw(&surface);
183         surface.finish();
184     }
185 
186     #[test]
writer()187     fn writer() {
188         let file = tempfile().expect("tempfile failed");
189         let surface = PdfSurface::for_stream(100., 100., file).unwrap();
190 
191         draw(&surface);
192         let stream = surface.finish_output_stream().unwrap();
193         let file = stream.downcast::<std::fs::File>().unwrap();
194 
195         let buffer = draw_in_buffer();
196         let file_size = file.metadata().unwrap().len();
197         assert_eq!(file_size, buffer.len() as u64);
198     }
199 
200     #[test]
ref_writer()201     fn ref_writer() {
202         let mut file = tempfile().expect("tempfile failed");
203         let surface = unsafe { PdfSurface::for_raw_stream(100., 100., &mut file).unwrap() };
204 
205         draw(&surface);
206         surface.finish_output_stream().unwrap();
207         drop(file);
208     }
209 
210     #[test]
buffer()211     fn buffer() {
212         let buffer = draw_in_buffer();
213 
214         let header = b"%PDF-1.5";
215         assert_eq!(&buffer[..header.len()], header);
216     }
217 
218     #[test]
custom_writer()219     fn custom_writer() {
220         struct CustomWriter(usize);
221 
222         impl io::Write for CustomWriter {
223             fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
224                 self.0 += buf.len();
225                 Ok(buf.len())
226             }
227 
228             fn flush(&mut self) -> io::Result<()> {
229                 Ok(())
230             }
231         }
232 
233         let custom_writer = CustomWriter(0);
234 
235         let surface = PdfSurface::for_stream(20., 20., custom_writer).unwrap();
236         surface.set_size(100., 100.).unwrap();
237         draw(&surface);
238         let stream = surface.finish_output_stream().unwrap();
239         let custom_writer = stream.downcast::<CustomWriter>().unwrap();
240 
241         let buffer = draw_in_buffer();
242 
243         assert_eq!(custom_writer.0, buffer.len());
244     }
245 
with_panicky_stream() -> PdfSurface246     fn with_panicky_stream() -> PdfSurface {
247         struct PanicWriter;
248 
249         impl io::Write for PanicWriter {
250             fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
251                 panic!("panic in writer");
252             }
253             fn flush(&mut self) -> io::Result<()> {
254                 Ok(())
255             }
256         }
257 
258         let surface = PdfSurface::for_stream(20., 20., PanicWriter).unwrap();
259         surface.finish();
260         surface
261     }
262 
263     #[test]
264     #[should_panic]
finish_stream_propagates_panic()265     fn finish_stream_propagates_panic() {
266         let _ = with_panicky_stream().finish_output_stream();
267     }
268 }
269