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