diff options
| author | Laurenz <laurmaedje@gmail.com> | 2019-03-30 16:01:45 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2019-03-30 16:01:45 +0100 |
| commit | 10994ebac34d027fb1937f72183859142b1b6180 (patch) | |
| tree | 9893bb6a7980637f7c546bcebfeb889b19241181 /src/export | |
| parent | 094648e86b71e8850e9218a30810d10d3922b122 (diff) | |
Move exporting into seperate module 🧱
Diffstat (limited to 'src/export')
| -rw-r--r-- | src/export/mod.rs | 3 | ||||
| -rw-r--r-- | src/export/pdf.rs | 332 |
2 files changed, 335 insertions, 0 deletions
diff --git a/src/export/mod.rs b/src/export/mod.rs new file mode 100644 index 00000000..71b87b03 --- /dev/null +++ b/src/export/mod.rs @@ -0,0 +1,3 @@ +//! Exporting into external formats. + +pub mod pdf; diff --git a/src/export/pdf.rs b/src/export/pdf.rs new file mode 100644 index 00000000..31aa39d5 --- /dev/null +++ b/src/export/pdf.rs @@ -0,0 +1,332 @@ +//! Exporting into _PDF_ documents. + +use std::collections::HashSet; +use std::fmt::{self, Display, Debug, Formatter}; +use std::io::{self, Write}; + +use pdf::{PdfWriter, Ref, Rect, Version, Trailer, Content}; +use pdf::doc::{Catalog, PageTree, Page, Resource, Text}; +use pdf::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags}; +use pdf::font::{GlyphUnit, CMapEncoding, WidthRecord, FontStream, EmbeddedFontType}; + +use crate::doc::{Document, Size, Text as DocText, TextCommand}; +use crate::font::{Font, FontError}; + + +/// Exports documents into _PDFs_. +#[derive(Debug)] +pub struct PdfExporter {} + +impl PdfExporter { + /// Create a new exporter. + #[inline] + pub fn new() -> PdfExporter { + PdfExporter {} + } + + /// Export a typesetted document into a writer. Returns how many bytes were written. + #[inline] + pub fn export<W: Write>(&self, document: &Document, target: W) -> PdfResult<usize> { + let mut engine = PdfEngine::new(document, target)?; + engine.write() + } +} + +/// Writes documents in the _PDF_ format. +#[derive(Debug)] +struct PdfEngine<'d, W: Write> { + writer: PdfWriter<W>, + doc: &'d Document, + offsets: Offsets, + fonts: Vec<PdfFont>, +} + +/// Offsets for the various groups of ids. +#[derive(Debug, Copy, Clone)] +struct Offsets { + catalog: Ref, + page_tree: Ref, + pages: (Ref, Ref), + contents: (Ref, Ref), + fonts: (Ref, Ref), +} + +impl<'d, W: Write> PdfEngine<'d, W> { + /// Create a new _PDF_ Creator. + fn new(doc: &'d Document, target: W) -> PdfResult<PdfEngine<'d, W>> { + // Calculate a unique id for all objects that will be written. + let catalog = 1; + let page_tree = catalog + 1; + let pages = (page_tree + 1, page_tree + doc.pages.len() as Ref); + let content_count = doc.pages.iter().flat_map(|p| p.text.iter()).count() as Ref; + let contents = (pages.1 + 1, pages.1 + content_count); + let fonts = (contents.1 + 1, contents.1 + 4 * doc.fonts.len() as Ref); + let offsets = Offsets { catalog, page_tree, pages, contents, fonts }; + + // Create a subsetted PDF font for each font in the document. + let fonts = { + let mut font = 0usize; + let mut chars = vec![HashSet::new(); doc.fonts.len()]; + + // Iterate through every text object on every page and find out + // which characters they use. + for text in doc.pages.iter().flat_map(|page| page.text.iter()) { + for command in &text.commands { + match command { + TextCommand::Text(string) => chars[font].extend(string.chars()), + TextCommand::SetFont(id, _) => font = *id, + _ => {}, + } + } + } + + doc.fonts.iter() + .enumerate() + .map(|(i, font)| PdfFont::new(font, &chars[i])) + .collect::<PdfResult<Vec<_>>>()? + }; + + Ok(PdfEngine { + writer: PdfWriter::new(target), + doc, + offsets, + fonts, + }) + } + + /// Write the complete document. + fn write(&mut self) -> PdfResult<usize> { + // Write all the things! + self.writer.write_header(&Version::new(1, 7))?; + self.write_pages()?; + self.write_contents()?; + self.write_fonts()?; + self.writer.write_xref_table()?; + self.writer.write_trailer(&Trailer::new(self.offsets.catalog))?; + Ok(self.writer.written()) + } + + /// Write the document catalog and page tree. + fn write_pages(&mut self) -> PdfResult<()> { + // The document catalog. + self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?; + + // The root page tree. + self.writer.write_obj(self.offsets.page_tree, PageTree::new() + .kids(ids(self.offsets.pages)) + .resource(Resource::Font(1, self.offsets.fonts.0)) + )?; + + // The page objects. + for (id, page) in ids(self.offsets.pages).zip(&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(ids(self.offsets.contents)) + )?; + } + + Ok(()) + } + + /// Write the contents of all pages. + 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(()) + } + + /// Write one text object. + fn write_text(&mut self, id: u32, doc_text: &DocText) -> PdfResult<()> { + let mut font = 0; + let mut text = Text::new(); + + for command in &doc_text.commands { + match command { + TextCommand::Text(string) => { text.tj(self.fonts[font].encode(&string)); }, + TextCommand::Move(x, y) => { text.td(x.to_points(), y.to_points()); }, + TextCommand::SetFont(id, size) => { + font = *id; + text.tf(*id as u32 + 1, *size); + }, + } + } + + self.writer.write_obj(id, &text.to_stream())?; + + Ok(()) + } + + /// Write all the fonts. + fn write_fonts(&mut self) -> PdfResult<()> { + let mut id = self.offsets.fonts.0; + + for font in &self.fonts { + // Write the base font object referencing the CID font. + self.writer.write_obj(id, &Type0Font::new( + font.name.clone(), + CMapEncoding::Predefined("Identity-H".to_owned()), + id + 1 + ))?; + + // Write the CID font referencing the font descriptor. + 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())]) + )?; + + // Write the font descriptor (contains the global information about the font). + 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) + )?; + + // Finally write the subsetted font program. + self.writer.write_obj(id + 3, &FontStream::new( + &font.program, + EmbeddedFontType::OpenType, + ))?; + + id += 4; + } + + Ok(()) + } +} + +/// Create an iterator from reference pair. +fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item=Ref> { + start ..= end +} + +/// The data we need from the font. +#[derive(Debug, Clone)] +struct PdfFont { + font: Font, + widths: Vec<GlyphUnit>, + flags: FontFlags, + italic_angle: f32, + bounding_box: Rect<GlyphUnit>, + 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_. + fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> { + /// Convert a size into a _PDF_ glyph unit. + fn size_to_glyph_unit(size: Size) -> GlyphUnit { + (1000.0 * size.to_points()).round() as GlyphUnit + } + + // 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, + }) + } +} + +impl std::ops::Deref for PdfFont { + type Target = Font; + + fn deref(&self) -> &Font { + &self.font + } +} + +/// Result type for _PDF_ creation. +type PdfResult<T> = std::result::Result<T, PdfExportError>; + +/// The error type for _PDF_ creation. +pub enum PdfExportError { + /// 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 std::error::Error for PdfExportError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + PdfExportError::Font(err) => Some(err), + PdfExportError::Io(err) => Some(err), + } + } +} + +impl Display for PdfExportError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + PdfExportError::Font(err) => write!(f, "font error: {}", err), + PdfExportError::Io(err) => write!(f, "io error: {}", err), + } + } +} + +impl Debug for PdfExportError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl From<io::Error> for PdfExportError { + fn from(err: io::Error) -> PdfExportError { + PdfExportError::Io(err) + } +} + +impl From<FontError> for PdfExportError { + fn from(err: FontError) -> PdfExportError { + PdfExportError::Font(err) + } +} |
