//! Writing of documents in the _PDF_ format. use std::collections::HashSet; use std::error; use std::fmt; use std::io::{self, Write}; use pdf::{PdfWriter, Reference, Rect, Version, Trailer, DocumentCatalog}; use pdf::{PageTree, Page, Resource, Text, Content}; use pdf::font::{Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo}; use pdf::font::{WidthRecord, FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit}; use crate::doc::{Document, Size, Text as DocText, TextCommand as DocTextCommand}; use crate::font::{Font, FontError}; /// Writes documents in the _PDF_ format. pub struct PdfCreator<'a, W: Write> { writer: PdfWriter<'a, W>, doc: &'a Document, offsets: Offsets, fonts: Vec, } /// Offsets for the various groups of ids. struct Offsets { catalog: Reference, page_tree: Reference, pages: (Reference, Reference), contents: (Reference, Reference), fonts: (Reference, Reference), } impl<'a, W: Write> PdfCreator<'a, W> { /// Create a new _PDF_ Creator. pub fn new(doc: &'a Document, target: &'a mut W) -> PdfResult> { // Calculate a unique id for all object to come let catalog = 1; let page_tree = catalog + 1; let pages = (page_tree + 1, page_tree + doc.pages.len() as Reference); let content_count = doc.pages.iter().flat_map(|p| p.text.iter()).count() as Reference; let contents = (pages.1 + 1, pages.1 + content_count); let fonts = (contents.1 + 1, contents.1 + 4 * doc.fonts.len() as Reference); let offsets = Offsets { catalog, page_tree, pages, contents, fonts, }; // Find out which chars are used in this document. let mut char_sets = vec![HashSet::new(); doc.fonts.len()]; let mut current_font: usize = 0; for page in &doc.pages { for text in &page.text { for command in &text.commands { match command { DocTextCommand::Text(string) => char_sets[current_font].extend(string.chars()), DocTextCommand::SetFont(id, _) => current_font = *id, _ => {}, } } } } // Create a subsetted pdf font. let fonts = doc.fonts.iter().enumerate().map(|(i, font)| { PdfFont::new(font, &char_sets[i]) }).collect::>>()?; Ok(PdfCreator { writer: PdfWriter::new(target), doc, offsets, fonts, }) } /// Write the complete document. pub fn write(&mut self) -> PdfResult { // Header self.writer.write_header(&Version::new(1, 7))?; // Document catalog, page tree and pages self.write_pages()?; // Contents self.write_contents()?; // Fonts self.write_fonts()?; // Cross-reference table self.writer.write_xref_table()?; // Trailer self.writer.write_trailer(&Trailer::new(self.offsets.catalog))?; Ok(self.writer.written()) } /// Write the document catalog, page tree and pages. fn write_pages(&mut self) -> PdfResult<()> { // The document catalog self.writer.write_obj(self.offsets.catalog, &DocumentCatalog::new(self.offsets.page_tree))?; // Root page tree self.writer.write_obj(self.offsets.page_tree, PageTree::new() .kids(self.offsets.pages.0 ..= self.offsets.pages.1) .resource(Resource::Font { nr: 1, id: self.offsets.fonts.0 }) )?; // The page objects let mut id = self.offsets.pages.0; for page in &self.doc.pages { self.writer.write_obj(id, Page::new(self.offsets.page_tree) .media_box(Rect::new( 0.0, 0.0, page.width.to_points(), page.height.to_points()) ) .contents(self.offsets.contents.0 ..= self.offsets.contents.1) )?; id += 1; } Ok(()) } /// Write the page contents. fn write_contents(&mut self) -> PdfResult<()> { let mut id = self.offsets.contents.0; for page in &self.doc.pages { for text in &page.text { self.write_text(id, text)?; id += 1; } } Ok(()) } fn write_text(&mut self, id: u32, text: &DocText) -> PdfResult<()> { let mut current_font = 0; let encoded = text.commands.iter().filter_map(|cmd| match cmd { DocTextCommand::Text(string) => Some(self.fonts[current_font].encode(&string)), DocTextCommand::SetFont(id, _) => { current_font = *id; None }, _ => None, }).collect::>(); let mut object = Text::new(); let mut nr = 0; for command in &text.commands { match command { DocTextCommand::Text(_) => { object.write_text(&encoded[nr]); nr += 1; }, DocTextCommand::SetFont(id, size) => { object.set_font(*id as u32 + 1, *size); }, DocTextCommand::Move(x, y) => { object.move_line(x.to_points(), y.to_points()); }, } } self.writer.write_obj(id, &object.to_stream())?; Ok(()) } /// Write the fonts. fn write_fonts(&mut self) -> PdfResult<()> { let mut id = self.offsets.fonts.0; for font in &self.fonts { self.writer.write_obj(id, &Type0Font::new( font.name.clone(), CMapEncoding::Predefined("Identity-H".to_owned()), id + 1 ))?; self.writer.write_obj(id + 1, CIDFont::new( CIDFontType::Type2, font.name.clone(), CIDSystemInfo::new("(Adobe)", "(Identity)", 0), id + 2, ).widths(vec![WidthRecord::start(0, font.widths.clone())]) )?; self.writer.write_obj(id + 2, FontDescriptor::new( font.name.clone(), font.flags, font.italic_angle, ) .font_bbox(font.bounding_box) .ascent(font.ascender) .descent(font.descender) .cap_height(font.cap_height) .stem_v(font.stem_v) .font_file_3(id + 3) )?; self.writer.write_obj(id + 3, &EmbeddedFont::OpenType(&font.program))?; id += 4; } Ok(()) } } /// The data we need from the font. struct PdfFont { font: Font, widths: Vec, flags: FontFlags, italic_angle: f32, bounding_box: Rect, ascender: GlyphUnit, descender: GlyphUnit, cap_height: GlyphUnit, stem_v: GlyphUnit, } impl PdfFont { /// Create a subetted version of the font and calculate some information /// needed for creating the _PDF_. pub fn new(font: &Font, chars: &HashSet) -> PdfResult { // Subset the font using the selected characters let subsetted = font.subsetted( chars.iter().cloned(), &["head", "hhea", "maxp", "hmtx", "loca", "glyf"], &["cvt ", "prep", "fpgm", /* "OS/2", "cmap", "name", "post" */], )?; // Specify flags for the font let mut flags = FontFlags::empty(); flags.set(FontFlags::FIXED_PITCH, font.metrics.is_fixed_pitch); flags.set(FontFlags::SERIF, font.name.contains("Serif")); flags.insert(FontFlags::SYMBOLIC); flags.set(FontFlags::ITALIC, font.metrics.is_italic); flags.insert(FontFlags::SMALL_CAP); // Transform the widths let widths = subsetted.widths.iter().map(|&x| size_to_glyph_unit(x)).collect(); Ok(PdfFont { font: subsetted, widths, flags, italic_angle: font.metrics.italic_angle, bounding_box: Rect::new( size_to_glyph_unit(font.metrics.bounding_box[0]), size_to_glyph_unit(font.metrics.bounding_box[1]), size_to_glyph_unit(font.metrics.bounding_box[2]), size_to_glyph_unit(font.metrics.bounding_box[3]), ), ascender: size_to_glyph_unit(font.metrics.ascender), descender: size_to_glyph_unit(font.metrics.descender), cap_height: size_to_glyph_unit(font.metrics.cap_height), stem_v: (10.0 + 0.244 * (font.metrics.weight_class as f32 - 50.0)) as GlyphUnit, }) } } /// Convert a size into a _PDF_ glyph unit. fn size_to_glyph_unit(size: Size) -> GlyphUnit { (1000.0 * size.to_points()).round() as GlyphUnit } impl std::ops::Deref for PdfFont { type Target = Font; fn deref(&self) -> &Font { &self.font } } /// Result type for _PDF_ creation. type PdfResult = std::result::Result; /// The error type for _PDF_ creation. pub enum PdfError { /// An error occured while subsetting the font for the _PDF_. Font(FontError), /// An I/O Error on the underlying writable occured. Io(io::Error), } impl error::Error for PdfError { #[inline] fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { PdfError::Font(err) => Some(err), PdfError::Io(err) => Some(err), } } } impl fmt::Display for PdfError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PdfError::Font(err) => write!(f, "font error: {}", err), PdfError::Io(err) => write!(f, "io error: {}", err), } } } impl fmt::Debug for PdfError { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } } impl From for PdfError { #[inline] fn from(err: io::Error) -> PdfError { PdfError::Io(err) } } impl From for PdfError { #[inline] fn from(err: FontError) -> PdfError { PdfError::Font(err) } }