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