//! Writing of documents in the _PDF_ format. use std::io::{self, Write}; use crate::doc::Document; use pdf::{PdfWriter, Id, Rect, Version, DocumentCatalog, PageTree, Page, PageData, Resource, font::Type1Font, Text, Trailer}; /// A type that is a sink for types that can be written conforming /// to the _PDF_ format (that may be things like sizes, other objects /// or whole documents). pub trait WritePdf { /// Write self into a byte sink, returning how many bytes were written. fn write_pdf(&mut self, object: &T) -> io::Result; } impl WritePdf for W { fn write_pdf(&mut self, doc: &Document) -> io::Result { let mut writer = PdfWriter::new(self); // Calculate unique id's for everything let catalog_id: Id = 1; let page_tree_id = catalog_id + 1; let pages_start = page_tree_id + 1; let pages_end = pages_start + doc.pages.len() as Id; let font_start = pages_end; let font_end = font_start + 1; let content_start = font_end; let content_end = content_start + doc.pages.iter().flat_map(|p| p.contents.iter()).count() as Id; writer.write_header(&Version::new(1, 7))?; // The document catalog writer.write_obj(catalog_id, &DocumentCatalog { page_tree: page_tree_id, })?; // Root page tree writer.write_obj(page_tree_id, &PageTree { parent: None, kids: (pages_start .. pages_end).collect(), data: PageData { resources: Some(vec![Resource::Font(1, font_start)]), .. PageData::none() }, })?; // The page objects let mut id = pages_start; for page in &doc.pages { let width = page.size[0].to_points(); let height = page.size[1].to_points(); writer.write_obj(id, &Page { parent: page_tree_id, data: PageData { media_box: Some(Rect::new(0.0, 0.0, width, height)), contents: Some((content_start .. content_end).collect()), .. PageData::none() }, })?; id += 1; } // The resources, currently only one hardcoded font writer.write_obj(font_start, &Type1Font { base_font: "Helvetica".to_owned(), })?; // The page contents let mut id = content_start; for page in &doc.pages { for content in &page.contents { let string = &content.0; writer.write_obj(id, &Text::new() .set_font(1, 13.0) .move_pos(108.0, 734.0) .write_str(&string) .to_stream() )?; id += 1; } } // Cross-reference table writer.write_xref_table()?; // Trailer writer.write_trailer(&Trailer { root: catalog_id, })?; Ok(writer.written()) } } #[cfg(test)] mod pdf_tests { use super::*; use crate::parsing::{Tokenize, Parse}; use crate::doc::Generate; /// Create a pdf with a name from the source code. fn test(name: &str, src: &str) { let mut file = std::fs::File::create(name).unwrap(); let doc = src.tokenize() .parse().unwrap() .generate().unwrap(); file.write_pdf(&doc).unwrap(); } #[test] fn pdf_simple() { test("../target/write1.pdf", "This is an example of a sentence."); test("../target/write2.pdf"," Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. "); } }