// Take a look at the license at the top of the repository in the LICENSE file. use std::convert::TryFrom; use std::ffi::{CStr, CString}; use std::fmt; use std::io; use std::mem; use std::ops::Deref; use std::path::Path; use std::ptr; #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))] use crate::enums::{PdfMetadata, PdfOutline}; use crate::enums::{PdfVersion, SurfaceType}; use crate::error::Error; use crate::surface::Surface; #[cfg(feature = "use_glib")] use glib::translate::*; impl PdfVersion { pub fn as_str(self) -> Option<&'static str> { unsafe { let res = ffi::cairo_pdf_version_to_string(self.into()); res.as_ref() .and_then(|cstr| CStr::from_ptr(cstr as _).to_str().ok()) } } } declare_surface!(PdfSurface, SurfaceType::Pdf); impl PdfSurface { #[doc(alias = "cairo_pdf_surface_create")] pub fn new>(width: f64, height: f64, path: P) -> Result { let path = path.as_ref().to_string_lossy().into_owned(); let path = CString::new(path).unwrap(); unsafe { Self::from_raw_full(ffi::cairo_pdf_surface_create(path.as_ptr(), width, height)) } } for_stream_constructors!(cairo_pdf_surface_create_for_stream); #[doc(alias = "cairo_pdf_get_versions")] #[doc(alias = "get_versions")] pub fn versions() -> impl Iterator { let vers_slice = unsafe { let mut vers_ptr = ptr::null_mut(); let mut num_vers = mem::MaybeUninit::uninit(); ffi::cairo_pdf_get_versions(&mut vers_ptr, num_vers.as_mut_ptr()); std::slice::from_raw_parts(vers_ptr, num_vers.assume_init() as _) }; vers_slice.iter().map(|v| PdfVersion::from(*v)) } #[doc(alias = "cairo_pdf_surface_restrict_to_version")] pub fn restrict(&self, version: PdfVersion) -> Result<(), Error> { unsafe { ffi::cairo_pdf_surface_restrict_to_version(self.0.to_raw_none(), version.into()); } self.status() } #[doc(alias = "cairo_pdf_surface_set_size")] pub fn set_size(&self, width: f64, height: f64) -> Result<(), Error> { unsafe { ffi::cairo_pdf_surface_set_size(self.0.to_raw_none(), width, height); } self.status() } #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))] #[doc(alias = "cairo_pdf_surface_set_metadata")] pub fn set_metadata(&self, metadata: PdfMetadata, value: &str) -> Result<(), Error> { let value = CString::new(value).unwrap(); unsafe { ffi::cairo_pdf_surface_set_metadata( self.0.to_raw_none(), metadata.into(), value.as_ptr(), ); } self.status() } #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))] #[doc(alias = "cairo_pdf_surface_set_page_label")] pub fn set_page_label(&self, label: &str) -> Result<(), Error> { let label = CString::new(label).unwrap(); unsafe { ffi::cairo_pdf_surface_set_page_label(self.0.to_raw_none(), label.as_ptr()); } self.status() } #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))] #[doc(alias = "cairo_pdf_surface_set_thumbnail_size")] pub fn set_thumbnail_size(&self, width: i32, height: i32) -> Result<(), Error> { unsafe { ffi::cairo_pdf_surface_set_thumbnail_size( self.0.to_raw_none(), width as _, height as _, ); } self.status() } #[cfg(any(all(feature = "pdf", feature = "v1_16"), feature = "dox"))] #[doc(alias = "cairo_pdf_surface_add_outline")] pub fn add_outline( &self, parent_id: i32, name: &str, link_attribs: &str, flags: PdfOutline, ) -> Result { let name = CString::new(name).unwrap(); let link_attribs = CString::new(link_attribs).unwrap(); let res = unsafe { ffi::cairo_pdf_surface_add_outline( self.0.to_raw_none(), parent_id, name.as_ptr(), link_attribs.as_ptr(), flags.bits() as _, ) as _ }; self.status()?; Ok(res) } } #[cfg(test)] mod test { use super::*; use crate::context::*; use tempfile::tempfile; fn draw(surface: &Surface) { let cr = Context::new(surface).expect("Can't create a Cairo context"); cr.set_line_width(25.0); cr.set_source_rgba(1.0, 0.0, 0.0, 0.5); cr.line_to(0., 0.); cr.line_to(100., 100.); cr.stroke().expect("Surface on an invalid state"); cr.set_source_rgba(0.0, 0.0, 1.0, 0.5); cr.line_to(0., 100.); cr.line_to(100., 0.); cr.stroke().expect("Surface on an invalid state"); } fn draw_in_buffer() -> Vec { let buffer: Vec = vec![]; let surface = PdfSurface::for_stream(100., 100., buffer).unwrap(); draw(&surface); *surface.finish_output_stream().unwrap().downcast().unwrap() } #[test] fn versions() { assert!(PdfSurface::versions().any(|v| v == PdfVersion::_1_4)); } #[test] fn version_string() { let ver_str = PdfVersion::_1_4.as_str().unwrap(); assert_eq!(ver_str, "PDF 1.4"); } #[test] #[cfg(unix)] fn file() { let surface = PdfSurface::new(100., 100., "/dev/null").unwrap(); draw(&surface); surface.finish(); } #[test] fn writer() { let file = tempfile().expect("tempfile failed"); let surface = PdfSurface::for_stream(100., 100., file).unwrap(); draw(&surface); let stream = surface.finish_output_stream().unwrap(); let file = stream.downcast::().unwrap(); let buffer = draw_in_buffer(); let file_size = file.metadata().unwrap().len(); assert_eq!(file_size, buffer.len() as u64); } #[test] fn ref_writer() { let mut file = tempfile().expect("tempfile failed"); let surface = unsafe { PdfSurface::for_raw_stream(100., 100., &mut file).unwrap() }; draw(&surface); surface.finish_output_stream().unwrap(); drop(file); } #[test] fn buffer() { let buffer = draw_in_buffer(); let header = b"%PDF-1.5"; assert_eq!(&buffer[..header.len()], header); } #[test] fn custom_writer() { struct CustomWriter(usize); impl io::Write for CustomWriter { fn write(&mut self, buf: &[u8]) -> io::Result { self.0 += buf.len(); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } let custom_writer = CustomWriter(0); let surface = PdfSurface::for_stream(20., 20., custom_writer).unwrap(); surface.set_size(100., 100.).unwrap(); draw(&surface); let stream = surface.finish_output_stream().unwrap(); let custom_writer = stream.downcast::().unwrap(); let buffer = draw_in_buffer(); assert_eq!(custom_writer.0, buffer.len()); } fn with_panicky_stream() -> PdfSurface { struct PanicWriter; impl io::Write for PanicWriter { fn write(&mut self, _buf: &[u8]) -> io::Result { panic!("panic in writer"); } fn flush(&mut self) -> io::Result<()> { Ok(()) } } let surface = PdfSurface::for_stream(20., 20., PanicWriter).unwrap(); surface.finish(); surface } #[test] #[should_panic] fn finish_stream_propagates_panic() { let _ = with_panicky_stream().finish_output_stream(); } }